blob: e38167671b8c54a7fbbbbbca4ad9673c1969d573 [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
Doug Zongkereef39442009-04-02 12:14:19 -070023import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080024import platform
Doug Zongkereef39442009-04-02 12:14:19 -070025import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070026import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070027import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080028import string
Doug Zongkereef39442009-04-02 12:14:19 -070029import subprocess
30import sys
31import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070032import threading
33import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070034import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080035from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070037import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080038import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070039
Dan Albert8b72aef2015-03-23 19:13:21 -070040class Options(object):
41 def __init__(self):
42 platform_search_path = {
43 "linux2": "out/host/linux-x86",
44 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070045 }
Doug Zongker85448772014-09-09 14:59:20 -070046
Tao Bao76def242017-11-21 09:25:31 -080047 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080049 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070050 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080052 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070053 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070057 self.boot_signer_args = []
58 self.verity_signer_path = None
59 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.verbose = False
61 self.tempfiles = []
62 self.device_specific = None
63 self.extras = {}
64 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070065 self.source_info_dict = None
66 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070068 # Stash size cannot exceed cache_size * threshold.
69 self.cache_size = None
70 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070071
72
73OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
76# Values for "certificate" in apkcerts that mean special things.
77SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
78
Tao Bao9dd909e2017-11-14 11:27:32 -080079
80# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010081AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010082 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080083
84
Tianjie Xu861f4132018-09-12 11:49:33 -070085# Partitions that should have their care_map added to META/care_map.pb
86PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
87 'odm')
88
89
Tianjie Xu209db462016-05-24 17:34:52 -070090class ErrorCode(object):
91 """Define error_codes for failures that happen during the actual
92 update package installation.
93
94 Error codes 0-999 are reserved for failures before the package
95 installation (i.e. low battery, package verification failure).
96 Detailed code in 'bootable/recovery/error_code.h' """
97
98 SYSTEM_VERIFICATION_FAILURE = 1000
99 SYSTEM_UPDATE_FAILURE = 1001
100 SYSTEM_UNEXPECTED_CONTENTS = 1002
101 SYSTEM_NONZERO_CONTENTS = 1003
102 SYSTEM_RECOVER_FAILURE = 1004
103 VENDOR_VERIFICATION_FAILURE = 2000
104 VENDOR_UPDATE_FAILURE = 2001
105 VENDOR_UNEXPECTED_CONTENTS = 2002
106 VENDOR_NONZERO_CONTENTS = 2003
107 VENDOR_RECOVER_FAILURE = 2004
108 OEM_PROP_MISMATCH = 3000
109 FINGERPRINT_MISMATCH = 3001
110 THUMBPRINT_MISMATCH = 3002
111 OLDER_BUILD = 3003
112 DEVICE_MISMATCH = 3004
113 BAD_PATCH_FILE = 3005
114 INSUFFICIENT_CACHE_SPACE = 3006
115 TUNE_PARTITION_FAILURE = 3007
116 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800117
Tao Bao80921982018-03-21 21:02:19 -0700118
Dan Albert8b72aef2015-03-23 19:13:21 -0700119class ExternalError(RuntimeError):
120 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700121
122
Tao Bao39451582017-05-04 11:10:47 -0700123def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700124 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700125
Tao Bao73dd4f42018-10-04 16:25:33 -0700126 Args:
127 args: The command represented as a list of strings.
128 verbose: Whether the commands should be shown (default to OPTIONS.verbose
129 if unspecified).
130 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
131 stdin, etc. stdout and stderr will default to subprocess.PIPE and
132 subprocess.STDOUT respectively unless caller specifies any of them.
133
134 Returns:
135 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700136 """
137 if verbose is None:
138 verbose = OPTIONS.verbose
Tao Bao73dd4f42018-10-04 16:25:33 -0700139 if 'stdout' not in kwargs and 'stderr' not in kwargs:
140 kwargs['stdout'] = subprocess.PIPE
141 kwargs['stderr'] = subprocess.STDOUT
Tao Bao39451582017-05-04 11:10:47 -0700142 if verbose:
Tao Bao73dd4f42018-10-04 16:25:33 -0700143 print(" Running: \"{}\"".format(" ".join(args)))
Doug Zongkereef39442009-04-02 12:14:19 -0700144 return subprocess.Popen(args, **kwargs)
145
146
Tao Baoc765cca2018-01-31 17:32:40 -0800147def RoundUpTo4K(value):
148 rounded_up = value + 4095
149 return rounded_up - (rounded_up % 4096)
150
151
Ying Wang7e6d4e42010-12-13 16:25:36 -0800152def CloseInheritedPipes():
153 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
154 before doing other work."""
155 if platform.system() != "Darwin":
156 return
157 for d in range(3, 1025):
158 try:
159 stat = os.fstat(d)
160 if stat is not None:
161 pipebit = stat[0] & 0x1000
162 if pipebit != 0:
163 os.close(d)
164 except OSError:
165 pass
166
167
Tao Bao410ad8b2018-08-24 12:08:38 -0700168def LoadInfoDict(input_file, repacking=False):
169 """Loads the key/value pairs from the given input target_files.
170
171 It reads `META/misc_info.txt` file in the target_files input, does sanity
172 checks and returns the parsed key/value pairs for to the given build. It's
173 usually called early when working on input target_files files, e.g. when
174 generating OTAs, or signing builds. Note that the function may be called
175 against an old target_files file (i.e. from past dessert releases). So the
176 property parsing needs to be backward compatible.
177
178 In a `META/misc_info.txt`, a few properties are stored as links to the files
179 in the PRODUCT_OUT directory. It works fine with the build system. However,
180 they are no longer available when (re)generating images from target_files zip.
181 When `repacking` is True, redirect these properties to the actual files in the
182 unzipped directory.
183
184 Args:
185 input_file: The input target_files file, which could be an open
186 zipfile.ZipFile instance, or a str for the dir that contains the files
187 unzipped from a target_files file.
188 repacking: Whether it's trying repack an target_files file after loading the
189 info dict (default: False). If so, it will rewrite a few loaded
190 properties (e.g. selinux_fc, root_dir) to point to the actual files in
191 target_files file. When doing repacking, `input_file` must be a dir.
192
193 Returns:
194 A dict that contains the parsed key/value pairs.
195
196 Raises:
197 AssertionError: On invalid input arguments.
198 ValueError: On malformed input values.
199 """
200 if repacking:
201 assert isinstance(input_file, str), \
202 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700203
Doug Zongkerc9253822014-02-04 12:17:58 -0800204 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700205 if isinstance(input_file, zipfile.ZipFile):
206 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800207 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700208 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800209 try:
210 with open(path) as f:
211 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700212 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800213 if e.errno == errno.ENOENT:
214 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800215
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700216 try:
Michael Runge6e836112014-04-15 17:40:21 -0700217 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700218 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700219 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700220
Tao Bao410ad8b2018-08-24 12:08:38 -0700221 if "recovery_api_version" not in d:
222 raise ValueError("Failed to find 'recovery_api_version'")
223 if "fstab_version" not in d:
224 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225
Tao Bao410ad8b2018-08-24 12:08:38 -0700226 if repacking:
227 # We carry a copy of file_contexts.bin under META/. If not available, search
228 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
229 # images than the one running on device, in that case, we must have the one
230 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700231 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700232 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700233 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700234
Tom Cherryd14b8952018-08-09 14:26:00 -0700235 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700236
Tom Cherryd14b8952018-08-09 14:26:00 -0700237 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700238 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700239 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700240 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700241
Tao Baof54216f2016-03-29 15:12:37 -0700242 # Redirect {system,vendor}_base_fs_file.
243 if "system_base_fs_file" in d:
244 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700245 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700246 if os.path.exists(system_base_fs_file):
247 d["system_base_fs_file"] = system_base_fs_file
248 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800249 print("Warning: failed to find system base fs file: %s" % (
250 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700251 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700252
253 if "vendor_base_fs_file" in d:
254 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700255 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700256 if os.path.exists(vendor_base_fs_file):
257 d["vendor_base_fs_file"] = vendor_base_fs_file
258 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800259 print("Warning: failed to find vendor base fs file: %s" % (
260 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700261 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700262
Doug Zongker37974732010-09-16 17:44:38 -0700263 def makeint(key):
264 if key in d:
265 d[key] = int(d[key], 0)
266
267 makeint("recovery_api_version")
268 makeint("blocksize")
269 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700270 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700271 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700272 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700273 makeint("recovery_size")
274 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700276
Tao Baoa57ab9f2018-08-24 12:08:38 -0700277 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
278 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
279 # cases, since it may load the info_dict from an old build (e.g. when
280 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800281 system_root_image = d.get("system_root_image") == "true"
282 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700283 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700284 if isinstance(input_file, zipfile.ZipFile):
285 if recovery_fstab_path not in input_file.namelist():
286 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
287 else:
288 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
289 if not os.path.exists(path):
290 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800291 d["fstab"] = LoadRecoveryFSTab(
292 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700293
Tao Bao76def242017-11-21 09:25:31 -0800294 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700295 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700296 if isinstance(input_file, zipfile.ZipFile):
297 if recovery_fstab_path not in input_file.namelist():
298 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
299 else:
300 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
301 if not os.path.exists(path):
302 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800303 d["fstab"] = LoadRecoveryFSTab(
304 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700305
Tianjie Xucfa86222016-03-07 16:31:19 -0800306 else:
307 d["fstab"] = None
308
Tianjie Xu861f4132018-09-12 11:49:33 -0700309 # Tries to load the build props for all partitions with care_map, including
310 # system and vendor.
311 for partition in PARTITIONS_WITH_CARE_MAP:
312 d["{}.build.prop".format(partition)] = LoadBuildProp(
313 read_helper, "{}/build.prop".format(partition.upper()))
314 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800315
316 # Set up the salt (based on fingerprint or thumbprint) that will be used when
317 # adding AVB footer.
318 if d.get("avb_enable") == "true":
319 fp = None
320 if "build.prop" in d:
321 build_prop = d["build.prop"]
322 if "ro.build.fingerprint" in build_prop:
323 fp = build_prop["ro.build.fingerprint"]
324 elif "ro.build.thumbprint" in build_prop:
325 fp = build_prop["ro.build.thumbprint"]
326 if fp:
327 d["avb_salt"] = sha256(fp).hexdigest()
328
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700329 return d
330
Tao Baod1de6f32017-03-01 16:38:48 -0800331
Tao Baobcd1d162017-08-26 13:10:26 -0700332def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700333 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700334 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700335 except KeyError:
Tao Baobcd1d162017-08-26 13:10:26 -0700336 print("Warning: could not read %s" % (prop_file,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700337 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700338 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700339
Tao Baod1de6f32017-03-01 16:38:48 -0800340
Michael Runge6e836112014-04-15 17:40:21 -0700341def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700342 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700343 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700344 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700345 if not line or line.startswith("#"):
346 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700347 if "=" in line:
348 name, value = line.split("=", 1)
349 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700350 return d
351
Tao Baod1de6f32017-03-01 16:38:48 -0800352
Tianjie Xucfa86222016-03-07 16:31:19 -0800353def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
354 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700355 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800356 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700357 self.mount_point = mount_point
358 self.fs_type = fs_type
359 self.device = device
360 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700361 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700362
363 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800364 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700365 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800366 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700367 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700368
Tao Baod1de6f32017-03-01 16:38:48 -0800369 assert fstab_version == 2
370
371 d = {}
372 for line in data.split("\n"):
373 line = line.strip()
374 if not line or line.startswith("#"):
375 continue
376
377 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
378 pieces = line.split()
379 if len(pieces) != 5:
380 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
381
382 # Ignore entries that are managed by vold.
383 options = pieces[4]
384 if "voldmanaged=" in options:
385 continue
386
387 # It's a good line, parse it.
388 length = 0
389 options = options.split(",")
390 for i in options:
391 if i.startswith("length="):
392 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800393 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800394 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700395 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800396
Tao Baod1de6f32017-03-01 16:38:48 -0800397 mount_flags = pieces[3]
398 # Honor the SELinux context if present.
399 context = None
400 for i in mount_flags.split(","):
401 if i.startswith("context="):
402 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800403
Tao Baod1de6f32017-03-01 16:38:48 -0800404 mount_point = pieces[1]
405 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
406 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800407
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700408 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700409 # system. Other areas assume system is always at "/system" so point /system
410 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700411 if system_root_image:
412 assert not d.has_key("/system") and d.has_key("/")
413 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700414 return d
415
416
Doug Zongker37974732010-09-16 17:44:38 -0700417def DumpInfoDict(d):
418 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800419 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700420
Dan Albert8b72aef2015-03-23 19:13:21 -0700421
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800422def AppendAVBSigningArgs(cmd, partition):
423 """Append signing arguments for avbtool."""
424 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
425 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
426 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
427 if key_path and algorithm:
428 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700429 avb_salt = OPTIONS.info_dict.get("avb_salt")
430 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700431 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700432 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800433
434
Tao Bao02a08592018-07-22 12:40:45 -0700435def GetAvbChainedPartitionArg(partition, info_dict, key=None):
436 """Constructs and returns the arg to build or verify a chained partition.
437
438 Args:
439 partition: The partition name.
440 info_dict: The info dict to look up the key info and rollback index
441 location.
442 key: The key to be used for building or verifying the partition. Defaults to
443 the key listed in info_dict.
444
445 Returns:
446 A string of form "partition:rollback_index_location:key" that can be used to
447 build or verify vbmeta image.
448
449 Raises:
450 AssertionError: When it fails to extract the public key with avbtool.
451 """
452 if key is None:
453 key = info_dict["avb_" + partition + "_key_path"]
454 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
455 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
456 proc = Run(
Tao Bao73dd4f42018-10-04 16:25:33 -0700457 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700458 stdoutdata, _ = proc.communicate()
459 assert proc.returncode == 0, \
460 "Failed to extract pubkey for {}:\n{}".format(
461 partition, stdoutdata)
462
463 rollback_index_location = info_dict[
464 "avb_" + partition + "_rollback_index_location"]
465 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
466
467
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700468def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800469 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700470 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700471
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700472 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800473 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
474 we are building a two-step special image (i.e. building a recovery image to
475 be loaded into /boot in two-step OTAs).
476
477 Return the image data, or None if sourcedir does not appear to contains files
478 for building the requested image.
479 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700480
481 def make_ramdisk():
482 ramdisk_img = tempfile.NamedTemporaryFile()
483
484 if os.access(fs_config_file, os.F_OK):
485 cmd = ["mkbootfs", "-f", fs_config_file,
486 os.path.join(sourcedir, "RAMDISK")]
487 else:
488 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
489 p1 = Run(cmd, stdout=subprocess.PIPE)
490 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
491
492 p2.wait()
493 p1.wait()
494 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
495 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
496
497 return ramdisk_img
498
499 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
500 return None
501
502 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700503 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700504
Doug Zongkerd5131602012-08-02 14:46:42 -0700505 if info_dict is None:
506 info_dict = OPTIONS.info_dict
507
Doug Zongkereef39442009-04-02 12:14:19 -0700508 img = tempfile.NamedTemporaryFile()
509
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700510 if has_ramdisk:
511 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700512
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800513 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
514 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
515
516 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700517
Benoit Fradina45a8682014-07-14 21:00:43 +0200518 fn = os.path.join(sourcedir, "second")
519 if os.access(fn, os.F_OK):
520 cmd.append("--second")
521 cmd.append(fn)
522
Doug Zongker171f1cd2009-06-15 22:36:37 -0700523 fn = os.path.join(sourcedir, "cmdline")
524 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700525 cmd.append("--cmdline")
526 cmd.append(open(fn).read().rstrip("\n"))
527
528 fn = os.path.join(sourcedir, "base")
529 if os.access(fn, os.F_OK):
530 cmd.append("--base")
531 cmd.append(open(fn).read().rstrip("\n"))
532
Ying Wang4de6b5b2010-08-25 14:29:34 -0700533 fn = os.path.join(sourcedir, "pagesize")
534 if os.access(fn, os.F_OK):
535 cmd.append("--pagesize")
536 cmd.append(open(fn).read().rstrip("\n"))
537
Tao Bao76def242017-11-21 09:25:31 -0800538 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700539 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700540 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700541
Tao Bao76def242017-11-21 09:25:31 -0800542 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000543 if args and args.strip():
544 cmd.extend(shlex.split(args))
545
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700546 if has_ramdisk:
547 cmd.extend(["--ramdisk", ramdisk_img.name])
548
Tao Baod95e9fd2015-03-29 23:07:41 -0700549 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800550 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700551 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700552 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700553 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700554 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700555
Tao Baobf70c3182017-07-11 17:27:55 -0700556 # "boot" or "recovery", without extension.
557 partition_name = os.path.basename(sourcedir).lower()
558
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700559 if (partition_name == "recovery" and
560 info_dict.get("include_recovery_dtbo") == "true"):
561 fn = os.path.join(sourcedir, "recovery_dtbo")
562 cmd.extend(["--recovery_dtbo", fn])
563
Tao Bao73dd4f42018-10-04 16:25:33 -0700564 proc = Run(cmd)
565 output, _ = proc.communicate()
566 assert proc.returncode == 0, \
567 "Failed to run mkbootimg of {}:\n{}".format(partition_name, output)
Doug Zongkereef39442009-04-02 12:14:19 -0700568
Tao Bao76def242017-11-21 09:25:31 -0800569 if (info_dict.get("boot_signer") == "true" and
570 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800571 # Hard-code the path as "/boot" for two-step special recovery image (which
572 # will be loaded into /boot during the two-step OTA).
573 if two_step_image:
574 path = "/boot"
575 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700576 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700577 cmd = [OPTIONS.boot_signer_path]
578 cmd.extend(OPTIONS.boot_signer_args)
579 cmd.extend([path, img.name,
580 info_dict["verity_key"] + ".pk8",
581 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao73dd4f42018-10-04 16:25:33 -0700582 proc = Run(cmd)
583 output, _ = proc.communicate()
584 assert proc.returncode == 0, \
585 "Failed to run boot_signer of {} image:\n{}".format(path, output)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700586
Tao Baod95e9fd2015-03-29 23:07:41 -0700587 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800588 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700589 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700590 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800591 # We have switched from the prebuilt futility binary to using the tool
592 # (futility-host) built from the source. Override the setting in the old
593 # TF.zip.
594 futility = info_dict["futility"]
595 if futility.startswith("prebuilts/"):
596 futility = "futility-host"
597 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700598 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700599 info_dict["vboot_key"] + ".vbprivk",
600 info_dict["vboot_subkey"] + ".vbprivk",
601 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700602 img.name]
Tao Bao73dd4f42018-10-04 16:25:33 -0700603 proc = Run(cmd)
604 proc.communicate()
605 assert proc.returncode == 0, \
606 "Failed to run vboot_signer of {} image:\n{}".format(path, output)
Tao Baod95e9fd2015-03-29 23:07:41 -0700607
Tao Baof3282b42015-04-01 11:21:55 -0700608 # Clean up the temp files.
609 img_unsigned.close()
610 img_keyblock.close()
611
David Zeuthen8fecb282017-12-01 16:24:01 -0500612 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800613 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700614 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500615 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400616 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700617 "--partition_size", str(part_size), "--partition_name",
618 partition_name]
619 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500620 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400621 if args and args.strip():
622 cmd.extend(shlex.split(args))
Tao Bao73dd4f42018-10-04 16:25:33 -0700623 proc = Run(cmd)
624 output, _ = proc.communicate()
625 assert proc.returncode == 0, \
626 "Failed to run 'avbtool add_hash_footer' of {}:\n{}".format(
627 partition_name, output)
David Zeuthend995f4b2016-01-29 16:59:17 -0500628
629 img.seek(os.SEEK_SET, 0)
630 data = img.read()
631
632 if has_ramdisk:
633 ramdisk_img.close()
634 img.close()
635
636 return data
637
638
Doug Zongkerd5131602012-08-02 14:46:42 -0700639def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800640 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700641 """Return a File object with the desired bootable image.
642
643 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
644 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
645 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongker55d93282011-01-25 17:03:34 -0800647 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
648 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800649 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800650 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700651
652 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
653 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800654 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700655 return File.FromLocalFile(name, prebuilt_path)
656
Tao Bao89fbb0f2017-01-10 10:47:58 -0800657 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700658
659 if info_dict is None:
660 info_dict = OPTIONS.info_dict
661
662 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800663 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
664 # for recovery.
665 has_ramdisk = (info_dict.get("system_root_image") != "true" or
666 prebuilt_name != "boot.img" or
667 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700668
Doug Zongker6f1d0312014-08-22 08:07:12 -0700669 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400670 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
671 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800672 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700673 if data:
674 return File(name, data)
675 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800676
Doug Zongkereef39442009-04-02 12:14:19 -0700677
Narayan Kamatha07bf042017-08-14 14:49:21 +0100678def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800679 """Gunzips the given gzip compressed file to a given output file."""
680 with gzip.open(in_filename, "rb") as in_file, \
681 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100682 shutil.copyfileobj(in_file, out_file)
683
684
Doug Zongker75f17362009-12-08 13:46:44 -0800685def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800686 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800687
Tao Bao1c830bf2017-12-25 10:43:47 -0800688 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
689 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800690
Tao Bao1c830bf2017-12-25 10:43:47 -0800691 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800692 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800693 """
Doug Zongkereef39442009-04-02 12:14:19 -0700694
Doug Zongker55d93282011-01-25 17:03:34 -0800695 def unzip_to_dir(filename, dirname):
696 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
697 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800698 cmd.extend(pattern)
Tao Bao73dd4f42018-10-04 16:25:33 -0700699 proc = Run(cmd)
700 stdoutdata, _ = proc.communicate()
701 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700702 raise ExternalError(
703 "Failed to unzip input target-files \"{}\":\n{}".format(
704 filename, stdoutdata))
Doug Zongker55d93282011-01-25 17:03:34 -0800705
Tao Bao1c830bf2017-12-25 10:43:47 -0800706 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800707 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
708 if m:
709 unzip_to_dir(m.group(1), tmp)
710 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
711 filename = m.group(1)
712 else:
713 unzip_to_dir(filename, tmp)
714
Tao Baodba59ee2018-01-09 13:21:02 -0800715 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700716
717
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700718def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
719 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800720 """Returns a SparseImage object suitable for passing to BlockImageDiff.
721
722 This function loads the specified sparse image from the given path, and
723 performs additional processing for OTA purpose. For example, it always adds
724 block 0 to clobbered blocks list. It also detects files that cannot be
725 reconstructed from the block list, for whom we should avoid applying imgdiff.
726
727 Args:
728 which: The partition name, which must be "system" or "vendor".
729 tmpdir: The directory that contains the prebuilt image and block map file.
730 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800731 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700732 hashtree_info_generator: If present, generates the hashtree_info for this
733 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800734 Returns:
735 A SparseImage object, with file_map info loaded.
736 """
737 assert which in ("system", "vendor")
738
739 path = os.path.join(tmpdir, "IMAGES", which + ".img")
740 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
741
742 # The image and map files must have been created prior to calling
743 # ota_from_target_files.py (since LMP).
744 assert os.path.exists(path) and os.path.exists(mappath)
745
746 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
747 # it to clobbered_blocks so that it will be written to the target
748 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
749 clobbered_blocks = "0"
750
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700751 image = sparse_img.SparseImage(
752 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
753 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800754
755 # block.map may contain less blocks, because mke2fs may skip allocating blocks
756 # if they contain all zeros. We can't reconstruct such a file from its block
757 # list. Tag such entries accordingly. (Bug: 65213616)
758 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800759 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700760 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800761 continue
762
Tom Cherryd14b8952018-08-09 14:26:00 -0700763 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
764 # filename listed in system.map may contain an additional leading slash
765 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
766 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700767 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
768
Tom Cherryd14b8952018-08-09 14:26:00 -0700769 # Special handling another case, where files not under /system
770 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700771 if which == 'system' and not arcname.startswith('SYSTEM'):
772 arcname = 'ROOT/' + arcname
773
774 assert arcname in input_zip.namelist(), \
775 "Failed to find the ZIP entry for {}".format(entry)
776
Tao Baoc765cca2018-01-31 17:32:40 -0800777 info = input_zip.getinfo(arcname)
778 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800779
780 # If a RangeSet has been tagged as using shared blocks while loading the
781 # image, its block list must be already incomplete due to that reason. Don't
782 # give it 'incomplete' tag to avoid messing up the imgdiff stats.
783 if ranges.extra.get('uses_shared_blocks'):
784 continue
785
Tao Baoc765cca2018-01-31 17:32:40 -0800786 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
787 ranges.extra['incomplete'] = True
788
789 return image
790
791
Doug Zongkereef39442009-04-02 12:14:19 -0700792def GetKeyPasswords(keylist):
793 """Given a list of keys, prompt the user to enter passwords for
794 those which require them. Return a {key: password} dict. password
795 will be None if the key has no password."""
796
Doug Zongker8ce7c252009-05-22 13:34:54 -0700797 no_passwords = []
798 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700799 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700800 devnull = open("/dev/null", "w+b")
801 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800802 # We don't need a password for things that aren't really keys.
803 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700804 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700805 continue
806
T.R. Fullhart37e10522013-03-18 10:31:26 -0700807 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700808 "-inform", "DER", "-nocrypt"],
809 stdin=devnull.fileno(),
810 stdout=devnull.fileno(),
811 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700812 p.communicate()
813 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700814 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700815 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700816 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700817 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
818 "-inform", "DER", "-passin", "pass:"],
819 stdin=devnull.fileno(),
820 stdout=devnull.fileno(),
821 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700822 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700823 if p.returncode == 0:
824 # Encrypted key with empty string as password.
825 key_passwords[k] = ''
826 elif stderr.startswith('Error decrypting key'):
827 # Definitely encrypted key.
828 # It would have said "Error reading key" if it didn't parse correctly.
829 need_passwords.append(k)
830 else:
831 # Potentially, a type of key that openssl doesn't understand.
832 # We'll let the routines in signapk.jar handle it.
833 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700834 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700835
T.R. Fullhart37e10522013-03-18 10:31:26 -0700836 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800837 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700838 return key_passwords
839
840
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800841def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700842 """Gets the minSdkVersion declared in the APK.
843
844 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
845 This can be both a decimal number (API Level) or a codename.
846
847 Args:
848 apk_name: The APK filename.
849
850 Returns:
851 The parsed SDK version string.
852
853 Raises:
854 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800855 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700856 proc = Run(
857 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
858 stderr=subprocess.PIPE)
859 stdoutdata, stderrdata = proc.communicate()
860 if proc.returncode != 0:
861 raise ExternalError(
862 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
863 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800864
Tao Baof47bf0f2018-03-21 23:28:51 -0700865 for line in stdoutdata.split("\n"):
866 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800867 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
868 if m:
869 return m.group(1)
870 raise ExternalError("No minSdkVersion returned by aapt")
871
872
873def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700874 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800875
Tao Baof47bf0f2018-03-21 23:28:51 -0700876 If minSdkVersion is set to a codename, it is translated to a number using the
877 provided map.
878
879 Args:
880 apk_name: The APK filename.
881
882 Returns:
883 The parsed SDK version number.
884
885 Raises:
886 ExternalError: On failing to get the min SDK version number.
887 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800888 version = GetMinSdkVersion(apk_name)
889 try:
890 return int(version)
891 except ValueError:
892 # Not a decimal number. Codename?
893 if version in codename_to_api_level_map:
894 return codename_to_api_level_map[version]
895 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700896 raise ExternalError(
897 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
898 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800899
900
901def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800902 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700903 """Sign the input_name zip/jar/apk, producing output_name. Use the
904 given key and password (the latter may be None if the key does not
905 have a password.
906
Doug Zongker951495f2009-08-14 12:44:19 -0700907 If whole_file is true, use the "-w" option to SignApk to embed a
908 signature that covers the whole file in the archive comment of the
909 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800910
911 min_api_level is the API Level (int) of the oldest platform this file may end
912 up on. If not specified for an APK, the API Level is obtained by interpreting
913 the minSdkVersion attribute of the APK's AndroidManifest.xml.
914
915 codename_to_api_level_map is needed to translate the codename which may be
916 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700917 """
Tao Bao76def242017-11-21 09:25:31 -0800918 if codename_to_api_level_map is None:
919 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700920
Alex Klyubin9667b182015-12-10 13:38:50 -0800921 java_library_path = os.path.join(
922 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
923
Tao Baoe95540e2016-11-08 12:08:53 -0800924 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
925 ["-Djava.library.path=" + java_library_path,
926 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
927 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700928 if whole_file:
929 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800930
931 min_sdk_version = min_api_level
932 if min_sdk_version is None:
933 if not whole_file:
934 min_sdk_version = GetMinSdkVersionInt(
935 input_name, codename_to_api_level_map)
936 if min_sdk_version is not None:
937 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
938
T.R. Fullhart37e10522013-03-18 10:31:26 -0700939 cmd.extend([key + OPTIONS.public_key_suffix,
940 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800941 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700942
Tao Bao73dd4f42018-10-04 16:25:33 -0700943 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700944 if password is not None:
945 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -0700946 stdoutdata, _ = proc.communicate(password)
947 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -0700948 raise ExternalError(
949 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -0700950 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -0700951
Doug Zongkereef39442009-04-02 12:14:19 -0700952
Doug Zongker37974732010-09-16 17:44:38 -0700953def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -0800954 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700955
Tao Bao9dd909e2017-11-14 11:27:32 -0800956 For non-AVB images, raise exception if the data is too big. Print a warning
957 if the data is nearing the maximum size.
958
959 For AVB images, the actual image size should be identical to the limit.
960
961 Args:
962 data: A string that contains all the data for the partition.
963 target: The partition name. The ".img" suffix is optional.
964 info_dict: The dict to be looked up for relevant info.
965 """
Dan Albert8b72aef2015-03-23 19:13:21 -0700966 if target.endswith(".img"):
967 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700968 mount_point = "/" + target
969
Ying Wangf8824af2014-06-03 14:07:27 -0700970 fs_type = None
971 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700972 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700973 if mount_point == "/userdata":
974 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700975 p = info_dict["fstab"][mount_point]
976 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800977 device = p.device
978 if "/" in device:
979 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -0800980 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -0700981 if not fs_type or not limit:
982 return
Doug Zongkereef39442009-04-02 12:14:19 -0700983
Andrew Boie0f9aec82012-02-14 09:32:52 -0800984 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -0800985 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
986 # path.
987 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
988 if size != limit:
989 raise ExternalError(
990 "Mismatching image size for %s: expected %d actual %d" % (
991 target, limit, size))
992 else:
993 pct = float(size) * 100.0 / limit
994 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
995 if pct >= 99.0:
996 raise ExternalError(msg)
997 elif pct >= 95.0:
998 print("\n WARNING: %s\n" % (msg,))
999 elif OPTIONS.verbose:
1000 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001001
1002
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001003def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001004 """Parses the APK certs info from a given target-files zip.
1005
1006 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1007 tuple with the following elements: (1) a dictionary that maps packages to
1008 certs (based on the "certificate" and "private_key" attributes in the file;
1009 (2) a string representing the extension of compressed APKs in the target files
1010 (e.g ".gz", ".bro").
1011
1012 Args:
1013 tf_zip: The input target_files ZipFile (already open).
1014
1015 Returns:
1016 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1017 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1018 no compressed APKs.
1019 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001020 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001021 compressed_extension = None
1022
Tao Bao0f990332017-09-08 19:02:54 -07001023 # META/apkcerts.txt contains the info for _all_ the packages known at build
1024 # time. Filter out the ones that are not installed.
1025 installed_files = set()
1026 for name in tf_zip.namelist():
1027 basename = os.path.basename(name)
1028 if basename:
1029 installed_files.add(basename)
1030
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001031 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1032 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001033 if not line:
1034 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001035 m = re.match(
1036 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1037 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1038 line)
1039 if not m:
1040 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001041
Tao Bao818ddf52018-01-05 11:17:34 -08001042 matches = m.groupdict()
1043 cert = matches["CERT"]
1044 privkey = matches["PRIVKEY"]
1045 name = matches["NAME"]
1046 this_compressed_extension = matches["COMPRESSED"]
1047
1048 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1049 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1050 if cert in SPECIAL_CERT_STRINGS and not privkey:
1051 certmap[name] = cert
1052 elif (cert.endswith(OPTIONS.public_key_suffix) and
1053 privkey.endswith(OPTIONS.private_key_suffix) and
1054 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1055 certmap[name] = cert[:-public_key_suffix_len]
1056 else:
1057 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1058
1059 if not this_compressed_extension:
1060 continue
1061
1062 # Only count the installed files.
1063 filename = name + '.' + this_compressed_extension
1064 if filename not in installed_files:
1065 continue
1066
1067 # Make sure that all the values in the compression map have the same
1068 # extension. We don't support multiple compression methods in the same
1069 # system image.
1070 if compressed_extension:
1071 if this_compressed_extension != compressed_extension:
1072 raise ValueError(
1073 "Multiple compressed extensions: {} vs {}".format(
1074 compressed_extension, this_compressed_extension))
1075 else:
1076 compressed_extension = this_compressed_extension
1077
1078 return (certmap,
1079 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001080
1081
Doug Zongkereef39442009-04-02 12:14:19 -07001082COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001083Global options
1084
1085 -p (--path) <dir>
1086 Prepend <dir>/bin to the list of places to search for binaries run by this
1087 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001088
Doug Zongker05d3dea2009-06-22 11:32:31 -07001089 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001090 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001091
Tao Bao30df8b42018-04-23 15:32:53 -07001092 -x (--extra) <key=value>
1093 Add a key/value pair to the 'extras' dict, which device-specific extension
1094 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001095
Doug Zongkereef39442009-04-02 12:14:19 -07001096 -v (--verbose)
1097 Show command lines being executed.
1098
1099 -h (--help)
1100 Display this usage message and exit.
1101"""
1102
1103def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001104 print(docstring.rstrip("\n"))
1105 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001106
1107
1108def ParseOptions(argv,
1109 docstring,
1110 extra_opts="", extra_long_opts=(),
1111 extra_option_handler=None):
1112 """Parse the options in argv and return any arguments that aren't
1113 flags. docstring is the calling module's docstring, to be displayed
1114 for errors and -h. extra_opts and extra_long_opts are for flags
1115 defined by the caller, which are processed by passing them to
1116 extra_option_handler."""
1117
1118 try:
1119 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001120 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001121 ["help", "verbose", "path=", "signapk_path=",
1122 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001123 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001124 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1125 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001126 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001127 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001128 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001129 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001130 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001131 sys.exit(2)
1132
Doug Zongkereef39442009-04-02 12:14:19 -07001133 for o, a in opts:
1134 if o in ("-h", "--help"):
1135 Usage(docstring)
1136 sys.exit()
1137 elif o in ("-v", "--verbose"):
1138 OPTIONS.verbose = True
1139 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001140 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001141 elif o in ("--signapk_path",):
1142 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001143 elif o in ("--signapk_shared_library_path",):
1144 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001145 elif o in ("--extra_signapk_args",):
1146 OPTIONS.extra_signapk_args = shlex.split(a)
1147 elif o in ("--java_path",):
1148 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001149 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001150 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001151 elif o in ("--public_key_suffix",):
1152 OPTIONS.public_key_suffix = a
1153 elif o in ("--private_key_suffix",):
1154 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001155 elif o in ("--boot_signer_path",):
1156 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001157 elif o in ("--boot_signer_args",):
1158 OPTIONS.boot_signer_args = shlex.split(a)
1159 elif o in ("--verity_signer_path",):
1160 OPTIONS.verity_signer_path = a
1161 elif o in ("--verity_signer_args",):
1162 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001163 elif o in ("-s", "--device_specific"):
1164 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001165 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001166 key, value = a.split("=", 1)
1167 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001168 else:
1169 if extra_option_handler is None or not extra_option_handler(o, a):
1170 assert False, "unknown option \"%s\"" % (o,)
1171
Doug Zongker85448772014-09-09 14:59:20 -07001172 if OPTIONS.search_path:
1173 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1174 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001175
1176 return args
1177
1178
Tao Bao4c851b12016-09-19 13:54:38 -07001179def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001180 """Make a temp file and add it to the list of things to be deleted
1181 when Cleanup() is called. Return the filename."""
1182 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1183 os.close(fd)
1184 OPTIONS.tempfiles.append(fn)
1185 return fn
1186
1187
Tao Bao1c830bf2017-12-25 10:43:47 -08001188def MakeTempDir(prefix='tmp', suffix=''):
1189 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1190
1191 Returns:
1192 The absolute pathname of the new directory.
1193 """
1194 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1195 OPTIONS.tempfiles.append(dir_name)
1196 return dir_name
1197
1198
Doug Zongkereef39442009-04-02 12:14:19 -07001199def Cleanup():
1200 for i in OPTIONS.tempfiles:
1201 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001202 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001203 else:
1204 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001205 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001206
1207
1208class PasswordManager(object):
1209 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001210 self.editor = os.getenv("EDITOR")
1211 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001212
1213 def GetPasswords(self, items):
1214 """Get passwords corresponding to each string in 'items',
1215 returning a dict. (The dict may have keys in addition to the
1216 values in 'items'.)
1217
1218 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1219 user edit that file to add more needed passwords. If no editor is
1220 available, or $ANDROID_PW_FILE isn't define, prompts the user
1221 interactively in the ordinary way.
1222 """
1223
1224 current = self.ReadFile()
1225
1226 first = True
1227 while True:
1228 missing = []
1229 for i in items:
1230 if i not in current or not current[i]:
1231 missing.append(i)
1232 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001233 if not missing:
1234 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001235
1236 for i in missing:
1237 current[i] = ""
1238
1239 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001240 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001241 answer = raw_input("try to edit again? [y]> ").strip()
1242 if answer and answer[0] not in 'yY':
1243 raise RuntimeError("key passwords unavailable")
1244 first = False
1245
1246 current = self.UpdateAndReadFile(current)
1247
Dan Albert8b72aef2015-03-23 19:13:21 -07001248 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001249 """Prompt the user to enter a value (password) for each key in
1250 'current' whose value is fales. Returns a new dict with all the
1251 values.
1252 """
1253 result = {}
1254 for k, v in sorted(current.iteritems()):
1255 if v:
1256 result[k] = v
1257 else:
1258 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001259 result[k] = getpass.getpass(
1260 "Enter password for %s key> " % k).strip()
1261 if result[k]:
1262 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001263 return result
1264
1265 def UpdateAndReadFile(self, current):
1266 if not self.editor or not self.pwfile:
1267 return self.PromptResult(current)
1268
1269 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001270 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001271 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1272 f.write("# (Additional spaces are harmless.)\n\n")
1273
1274 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001275 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1276 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001277 f.write("[[[ %s ]]] %s\n" % (v, k))
1278 if not v and first_line is None:
1279 # position cursor on first line with no password.
1280 first_line = i + 4
1281 f.close()
1282
Tao Bao73dd4f42018-10-04 16:25:33 -07001283 Run([self.editor, "+%d" % (first_line,), self.pwfile]).communicate()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001284
1285 return self.ReadFile()
1286
1287 def ReadFile(self):
1288 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001289 if self.pwfile is None:
1290 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001291 try:
1292 f = open(self.pwfile, "r")
1293 for line in f:
1294 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001295 if not line or line[0] == '#':
1296 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001297 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1298 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001299 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001300 else:
1301 result[m.group(2)] = m.group(1)
1302 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001303 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001304 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001305 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001306 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001307
1308
Dan Albert8e0178d2015-01-27 15:53:15 -08001309def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1310 compress_type=None):
1311 import datetime
1312
1313 # http://b/18015246
1314 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1315 # for files larger than 2GiB. We can work around this by adjusting their
1316 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1317 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1318 # it isn't clear to me exactly what circumstances cause this).
1319 # `zipfile.write()` must be used directly to work around this.
1320 #
1321 # This mess can be avoided if we port to python3.
1322 saved_zip64_limit = zipfile.ZIP64_LIMIT
1323 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1324
1325 if compress_type is None:
1326 compress_type = zip_file.compression
1327 if arcname is None:
1328 arcname = filename
1329
1330 saved_stat = os.stat(filename)
1331
1332 try:
1333 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1334 # file to be zipped and reset it when we're done.
1335 os.chmod(filename, perms)
1336
1337 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001338 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1339 # intentional. zip stores datetimes in local time without a time zone
1340 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1341 # in the zip archive.
1342 local_epoch = datetime.datetime.fromtimestamp(0)
1343 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001344 os.utime(filename, (timestamp, timestamp))
1345
1346 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1347 finally:
1348 os.chmod(filename, saved_stat.st_mode)
1349 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1350 zipfile.ZIP64_LIMIT = saved_zip64_limit
1351
1352
Tao Bao58c1b962015-05-20 09:32:18 -07001353def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001354 compress_type=None):
1355 """Wrap zipfile.writestr() function to work around the zip64 limit.
1356
1357 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1358 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1359 when calling crc32(bytes).
1360
1361 But it still works fine to write a shorter string into a large zip file.
1362 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1363 when we know the string won't be too long.
1364 """
1365
1366 saved_zip64_limit = zipfile.ZIP64_LIMIT
1367 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1368
1369 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1370 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001371 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001372 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001373 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001374 else:
Tao Baof3282b42015-04-01 11:21:55 -07001375 zinfo = zinfo_or_arcname
1376
1377 # If compress_type is given, it overrides the value in zinfo.
1378 if compress_type is not None:
1379 zinfo.compress_type = compress_type
1380
Tao Bao58c1b962015-05-20 09:32:18 -07001381 # If perms is given, it has a priority.
1382 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001383 # If perms doesn't set the file type, mark it as a regular file.
1384 if perms & 0o770000 == 0:
1385 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001386 zinfo.external_attr = perms << 16
1387
Tao Baof3282b42015-04-01 11:21:55 -07001388 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001389 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1390
Dan Albert8b72aef2015-03-23 19:13:21 -07001391 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001392 zipfile.ZIP64_LIMIT = saved_zip64_limit
1393
1394
Tao Bao89d7ab22017-12-14 17:05:33 -08001395def ZipDelete(zip_filename, entries):
1396 """Deletes entries from a ZIP file.
1397
1398 Since deleting entries from a ZIP file is not supported, it shells out to
1399 'zip -d'.
1400
1401 Args:
1402 zip_filename: The name of the ZIP file.
1403 entries: The name of the entry, or the list of names to be deleted.
1404
1405 Raises:
1406 AssertionError: In case of non-zero return from 'zip'.
1407 """
1408 if isinstance(entries, basestring):
1409 entries = [entries]
1410 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao73dd4f42018-10-04 16:25:33 -07001411 proc = Run(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001412 stdoutdata, _ = proc.communicate()
Tao Bao73dd4f42018-10-04 16:25:33 -07001413 assert proc.returncode == 0, \
1414 "Failed to delete {}:\n{}".format(entries, stdoutdata)
Tao Bao89d7ab22017-12-14 17:05:33 -08001415
1416
Tao Baof3282b42015-04-01 11:21:55 -07001417def ZipClose(zip_file):
1418 # http://b/18015246
1419 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1420 # central directory.
1421 saved_zip64_limit = zipfile.ZIP64_LIMIT
1422 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1423
1424 zip_file.close()
1425
1426 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001427
1428
1429class DeviceSpecificParams(object):
1430 module = None
1431 def __init__(self, **kwargs):
1432 """Keyword arguments to the constructor become attributes of this
1433 object, which is passed to all functions in the device-specific
1434 module."""
1435 for k, v in kwargs.iteritems():
1436 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001437 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001438
1439 if self.module is None:
1440 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001441 if not path:
1442 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001443 try:
1444 if os.path.isdir(path):
1445 info = imp.find_module("releasetools", [path])
1446 else:
1447 d, f = os.path.split(path)
1448 b, x = os.path.splitext(f)
1449 if x == ".py":
1450 f = b
1451 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001452 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001453 self.module = imp.load_module("device_specific", *info)
1454 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001455 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001456
1457 def _DoCall(self, function_name, *args, **kwargs):
1458 """Call the named function in the device-specific module, passing
1459 the given args and kwargs. The first argument to the call will be
1460 the DeviceSpecific object itself. If there is no module, or the
1461 module does not define the function, return the value of the
1462 'default' kwarg (which itself defaults to None)."""
1463 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001464 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001465 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1466
1467 def FullOTA_Assertions(self):
1468 """Called after emitting the block of assertions at the top of a
1469 full OTA package. Implementations can add whatever additional
1470 assertions they like."""
1471 return self._DoCall("FullOTA_Assertions")
1472
Doug Zongkere5ff5902012-01-17 10:55:37 -08001473 def FullOTA_InstallBegin(self):
1474 """Called at the start of full OTA installation."""
1475 return self._DoCall("FullOTA_InstallBegin")
1476
Doug Zongker05d3dea2009-06-22 11:32:31 -07001477 def FullOTA_InstallEnd(self):
1478 """Called at the end of full OTA installation; typically this is
1479 used to install the image for the device's baseband processor."""
1480 return self._DoCall("FullOTA_InstallEnd")
1481
1482 def IncrementalOTA_Assertions(self):
1483 """Called after emitting the block of assertions at the top of an
1484 incremental OTA package. Implementations can add whatever
1485 additional assertions they like."""
1486 return self._DoCall("IncrementalOTA_Assertions")
1487
Doug Zongkere5ff5902012-01-17 10:55:37 -08001488 def IncrementalOTA_VerifyBegin(self):
1489 """Called at the start of the verification phase of incremental
1490 OTA installation; additional checks can be placed here to abort
1491 the script before any changes are made."""
1492 return self._DoCall("IncrementalOTA_VerifyBegin")
1493
Doug Zongker05d3dea2009-06-22 11:32:31 -07001494 def IncrementalOTA_VerifyEnd(self):
1495 """Called at the end of the verification phase of incremental OTA
1496 installation; additional checks can be placed here to abort the
1497 script before any changes are made."""
1498 return self._DoCall("IncrementalOTA_VerifyEnd")
1499
Doug Zongkere5ff5902012-01-17 10:55:37 -08001500 def IncrementalOTA_InstallBegin(self):
1501 """Called at the start of incremental OTA installation (after
1502 verification is complete)."""
1503 return self._DoCall("IncrementalOTA_InstallBegin")
1504
Doug Zongker05d3dea2009-06-22 11:32:31 -07001505 def IncrementalOTA_InstallEnd(self):
1506 """Called at the end of incremental OTA installation; typically
1507 this is used to install the image for the device's baseband
1508 processor."""
1509 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001510
Tao Bao9bc6bb22015-11-09 16:58:28 -08001511 def VerifyOTA_Assertions(self):
1512 return self._DoCall("VerifyOTA_Assertions")
1513
Tao Bao76def242017-11-21 09:25:31 -08001514
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001515class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001516 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001517 self.name = name
1518 self.data = data
1519 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001520 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001521 self.sha1 = sha1(data).hexdigest()
1522
1523 @classmethod
1524 def FromLocalFile(cls, name, diskname):
1525 f = open(diskname, "rb")
1526 data = f.read()
1527 f.close()
1528 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001529
1530 def WriteToTemp(self):
1531 t = tempfile.NamedTemporaryFile()
1532 t.write(self.data)
1533 t.flush()
1534 return t
1535
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001536 def WriteToDir(self, d):
1537 with open(os.path.join(d, self.name), "wb") as fp:
1538 fp.write(self.data)
1539
Geremy Condra36bd3652014-02-06 19:45:10 -08001540 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001541 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001542
Tao Bao76def242017-11-21 09:25:31 -08001543
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001544DIFF_PROGRAM_BY_EXT = {
1545 ".gz" : "imgdiff",
1546 ".zip" : ["imgdiff", "-z"],
1547 ".jar" : ["imgdiff", "-z"],
1548 ".apk" : ["imgdiff", "-z"],
1549 ".img" : "imgdiff",
1550 }
1551
Tao Bao76def242017-11-21 09:25:31 -08001552
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001553class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001554 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001555 self.tf = tf
1556 self.sf = sf
1557 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001558 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001559
1560 def ComputePatch(self):
1561 """Compute the patch (as a string of data) needed to turn sf into
1562 tf. Returns the same tuple as GetPatch()."""
1563
1564 tf = self.tf
1565 sf = self.sf
1566
Doug Zongker24cd2802012-08-14 16:36:15 -07001567 if self.diff_program:
1568 diff_program = self.diff_program
1569 else:
1570 ext = os.path.splitext(tf.name)[1]
1571 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001572
1573 ttemp = tf.WriteToTemp()
1574 stemp = sf.WriteToTemp()
1575
1576 ext = os.path.splitext(tf.name)[1]
1577
1578 try:
1579 ptemp = tempfile.NamedTemporaryFile()
1580 if isinstance(diff_program, list):
1581 cmd = copy.copy(diff_program)
1582 else:
1583 cmd = [diff_program]
1584 cmd.append(stemp.name)
1585 cmd.append(ttemp.name)
1586 cmd.append(ptemp.name)
1587 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001588 err = []
1589 def run():
1590 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001591 if e:
1592 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001593 th = threading.Thread(target=run)
1594 th.start()
1595 th.join(timeout=300) # 5 mins
1596 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001597 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001598 p.terminate()
1599 th.join(5)
1600 if th.is_alive():
1601 p.kill()
1602 th.join()
1603
Tianjie Xua2a9f992018-01-05 15:15:54 -08001604 if p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001605 print("WARNING: failure running %s:\n%s\n" % (
1606 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001607 self.patch = None
1608 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001609 diff = ptemp.read()
1610 finally:
1611 ptemp.close()
1612 stemp.close()
1613 ttemp.close()
1614
1615 self.patch = diff
1616 return self.tf, self.sf, self.patch
1617
1618
1619 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001620 """Returns a tuple of (target_file, source_file, patch_data).
1621
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001622 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001623 computing the patch failed.
1624 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001625 return self.tf, self.sf, self.patch
1626
1627
1628def ComputeDifferences(diffs):
1629 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001630 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001631
1632 # Do the largest files first, to try and reduce the long-pole effect.
1633 by_size = [(i.tf.size, i) for i in diffs]
1634 by_size.sort(reverse=True)
1635 by_size = [i[1] for i in by_size]
1636
1637 lock = threading.Lock()
1638 diff_iter = iter(by_size) # accessed under lock
1639
1640 def worker():
1641 try:
1642 lock.acquire()
1643 for d in diff_iter:
1644 lock.release()
1645 start = time.time()
1646 d.ComputePatch()
1647 dur = time.time() - start
1648 lock.acquire()
1649
1650 tf, sf, patch = d.GetPatch()
1651 if sf.name == tf.name:
1652 name = tf.name
1653 else:
1654 name = "%s (%s)" % (tf.name, sf.name)
1655 if patch is None:
Tao Bao76def242017-11-21 09:25:31 -08001656 print(
1657 "patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001658 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001659 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1660 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001661 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001662 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001663 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001664 raise
1665
1666 # start worker threads; wait for them all to finish.
1667 threads = [threading.Thread(target=worker)
1668 for i in range(OPTIONS.worker_threads)]
1669 for th in threads:
1670 th.start()
1671 while threads:
1672 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001673
1674
Dan Albert8b72aef2015-03-23 19:13:21 -07001675class BlockDifference(object):
1676 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001677 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001678 self.tgt = tgt
1679 self.src = src
1680 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001681 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001682 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001683
Tao Baodd2a5892015-03-12 12:32:37 -07001684 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001685 version = max(
1686 int(i) for i in
1687 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001688 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001689 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001690
1691 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001692 version=self.version,
1693 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001694 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001695 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001696 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001697 self.touched_src_ranges = b.touched_src_ranges
1698 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001699
Tao Baoaac4ad52015-10-16 15:26:34 -07001700 if src is None:
1701 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1702 else:
1703 _, self.device = GetTypeAndDevice("/" + partition,
1704 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001705
Tao Baod8d14be2016-02-04 14:26:02 -08001706 @property
1707 def required_cache(self):
1708 return self._required_cache
1709
Tao Bao76def242017-11-21 09:25:31 -08001710 def WriteScript(self, script, output_zip, progress=None,
1711 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001712 if not self.src:
1713 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001714 script.Print("Patching %s image unconditionally..." % (self.partition,))
1715 else:
1716 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001717
Dan Albert8b72aef2015-03-23 19:13:21 -07001718 if progress:
1719 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001720 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001721
1722 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001723 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001724
Tao Bao9bc6bb22015-11-09 16:58:28 -08001725 def WriteStrictVerifyScript(self, script):
1726 """Verify all the blocks in the care_map, including clobbered blocks.
1727
1728 This differs from the WriteVerifyScript() function: a) it prints different
1729 error messages; b) it doesn't allow half-way updated images to pass the
1730 verification."""
1731
1732 partition = self.partition
1733 script.Print("Verifying %s..." % (partition,))
1734 ranges = self.tgt.care_map
1735 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001736 script.AppendExtra(
1737 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1738 'ui_print("\\"%s\\" has unexpected contents.");' % (
1739 self.device, ranges_str,
1740 self.tgt.TotalSha1(include_clobbered_blocks=True),
1741 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001742 script.AppendExtra("")
1743
Tao Baod522bdc2016-04-12 15:53:16 -07001744 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001745 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001746
1747 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001748 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001749 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001750
1751 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001752 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001753 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001754 ranges = self.touched_src_ranges
1755 expected_sha1 = self.touched_src_sha1
1756 else:
1757 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1758 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001759
1760 # No blocks to be checked, skipping.
1761 if not ranges:
1762 return
1763
Tao Bao5ece99d2015-05-12 11:42:31 -07001764 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001765 script.AppendExtra(
1766 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1767 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1768 '"%s.patch.dat")) then' % (
1769 self.device, ranges_str, expected_sha1,
1770 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001771 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001772 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001773
Tianjie Xufc3422a2015-12-15 11:53:59 -08001774 if self.version >= 4:
1775
1776 # Bug: 21124327
1777 # When generating incrementals for the system and vendor partitions in
1778 # version 4 or newer, explicitly check the first block (which contains
1779 # the superblock) of the partition to see if it's what we expect. If
1780 # this check fails, give an explicit log message about the partition
1781 # having been remounted R/W (the most likely explanation).
1782 if self.check_first_block:
1783 script.AppendExtra('check_first_block("%s");' % (self.device,))
1784
1785 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001786 if partition == "system":
1787 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1788 else:
1789 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001790 script.AppendExtra((
1791 'ifelse (block_image_recover("{device}", "{ranges}") && '
1792 'block_image_verify("{device}", '
1793 'package_extract_file("{partition}.transfer.list"), '
1794 '"{partition}.new.dat", "{partition}.patch.dat"), '
1795 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001796 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001797 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001798 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001799
Tao Baodd2a5892015-03-12 12:32:37 -07001800 # Abort the OTA update. Note that the incremental OTA cannot be applied
1801 # even if it may match the checksum of the target partition.
1802 # a) If version < 3, operations like move and erase will make changes
1803 # unconditionally and damage the partition.
1804 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001805 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001806 if partition == "system":
1807 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1808 else:
1809 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1810 script.AppendExtra((
1811 'abort("E%d: %s partition has unexpected contents");\n'
1812 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001813
Tao Bao5fcaaef2015-06-01 13:40:49 -07001814 def _WritePostInstallVerifyScript(self, script):
1815 partition = self.partition
1816 script.Print('Verifying the updated %s image...' % (partition,))
1817 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1818 ranges = self.tgt.care_map
1819 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001820 script.AppendExtra(
1821 'if range_sha1("%s", "%s") == "%s" then' % (
1822 self.device, ranges_str,
1823 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001824
1825 # Bug: 20881595
1826 # Verify that extended blocks are really zeroed out.
1827 if self.tgt.extended:
1828 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001829 script.AppendExtra(
1830 'if range_sha1("%s", "%s") == "%s" then' % (
1831 self.device, ranges_str,
1832 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001833 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001834 if partition == "system":
1835 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1836 else:
1837 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001838 script.AppendExtra(
1839 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001840 ' abort("E%d: %s partition has unexpected non-zero contents after '
1841 'OTA update");\n'
1842 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001843 else:
1844 script.Print('Verified the updated %s image.' % (partition,))
1845
Tianjie Xu209db462016-05-24 17:34:52 -07001846 if partition == "system":
1847 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1848 else:
1849 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1850
Tao Bao5fcaaef2015-06-01 13:40:49 -07001851 script.AppendExtra(
1852 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001853 ' abort("E%d: %s partition has unexpected contents after OTA '
1854 'update");\n'
1855 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001856
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001857 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001858 ZipWrite(output_zip,
1859 '{}.transfer.list'.format(self.path),
1860 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001861
Tao Bao76def242017-11-21 09:25:31 -08001862 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1863 # its size. Quailty 9 almost triples the compression time but doesn't
1864 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001865 # zip | brotli(quality 6) | brotli(quality 9)
1866 # compressed_size: 942M | 869M (~8% reduced) | 854M
1867 # compression_time: 75s | 265s | 719s
1868 # decompression_time: 15s | 25s | 25s
1869
1870 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001871 brotli_cmd = ['brotli', '--quality=6',
1872 '--output={}.new.dat.br'.format(self.path),
1873 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001874 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao73dd4f42018-10-04 16:25:33 -07001875 proc = Run(brotli_cmd)
1876 stdoutdata, _ = proc.communicate()
1877 assert proc.returncode == 0, \
Tao Bao80921982018-03-21 21:02:19 -07001878 'Failed to compress {}.new.dat with brotli:\n{}'.format(
1879 self.partition, stdoutdata)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001880
1881 new_data_name = '{}.new.dat.br'.format(self.partition)
1882 ZipWrite(output_zip,
1883 '{}.new.dat.br'.format(self.path),
1884 new_data_name,
1885 compress_type=zipfile.ZIP_STORED)
1886 else:
1887 new_data_name = '{}.new.dat'.format(self.partition)
1888 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1889
Dan Albert8e0178d2015-01-27 15:53:15 -08001890 ZipWrite(output_zip,
1891 '{}.patch.dat'.format(self.path),
1892 '{}.patch.dat'.format(self.partition),
1893 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001894
Tianjie Xu209db462016-05-24 17:34:52 -07001895 if self.partition == "system":
1896 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1897 else:
1898 code = ErrorCode.VENDOR_UPDATE_FAILURE
1899
Dan Albert8e0178d2015-01-27 15:53:15 -08001900 call = ('block_image_update("{device}", '
1901 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001902 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001903 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001904 device=self.device, partition=self.partition,
1905 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001906 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001907
Dan Albert8b72aef2015-03-23 19:13:21 -07001908 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001909 data = source.ReadRangeSet(ranges)
1910 ctx = sha1()
1911
1912 for p in data:
1913 ctx.update(p)
1914
1915 return ctx.hexdigest()
1916
Tao Baoe9b61912015-07-09 17:37:49 -07001917 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1918 """Return the hash value for all zero blocks."""
1919 zero_block = '\x00' * 4096
1920 ctx = sha1()
1921 for _ in range(num_blocks):
1922 ctx.update(zero_block)
1923
1924 return ctx.hexdigest()
1925
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001926
1927DataImage = blockimgdiff.DataImage
1928
Tao Bao76def242017-11-21 09:25:31 -08001929
Doug Zongker96a57e72010-09-26 14:57:41 -07001930# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001931PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001932 "ext4": "EMMC",
1933 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001934 "f2fs": "EMMC",
1935 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001936}
Doug Zongker96a57e72010-09-26 14:57:41 -07001937
Tao Bao76def242017-11-21 09:25:31 -08001938
Doug Zongker96a57e72010-09-26 14:57:41 -07001939def GetTypeAndDevice(mount_point, info):
1940 fstab = info["fstab"]
1941 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001942 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1943 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001944 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001945 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001946
1947
1948def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001949 """Parses and converts a PEM-encoded certificate into DER-encoded.
1950
1951 This gives the same result as `openssl x509 -in <filename> -outform DER`.
1952
1953 Returns:
1954 The decoded certificate string.
1955 """
1956 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001957 save = False
1958 for line in data.split("\n"):
1959 if "--END CERTIFICATE--" in line:
1960 break
1961 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08001962 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001963 if "--BEGIN CERTIFICATE--" in line:
1964 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08001965 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001966 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001967
Tao Bao04e1f012018-02-04 12:13:35 -08001968
1969def ExtractPublicKey(cert):
1970 """Extracts the public key (PEM-encoded) from the given certificate file.
1971
1972 Args:
1973 cert: The certificate filename.
1974
1975 Returns:
1976 The public key string.
1977
1978 Raises:
1979 AssertionError: On non-zero return from 'openssl'.
1980 """
1981 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
1982 # While openssl 1.1 writes the key into the given filename followed by '-out',
1983 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
1984 # stdout instead.
1985 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
1986 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1987 pubkey, stderrdata = proc.communicate()
1988 assert proc.returncode == 0, \
1989 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
1990 return pubkey
1991
1992
Doug Zongker412c02f2014-02-13 10:58:24 -08001993def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1994 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08001995 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08001996
Tao Bao6d5d6232018-03-09 17:04:42 -08001997 Most of the space in the boot and recovery images is just the kernel, which is
1998 identical for the two, so the resulting patch should be efficient. Add it to
1999 the output zip, along with a shell script that is run from init.rc on first
2000 boot to actually do the patching and install the new recovery image.
2001
2002 Args:
2003 input_dir: The top-level input directory of the target-files.zip.
2004 output_sink: The callback function that writes the result.
2005 recovery_img: File object for the recovery image.
2006 boot_img: File objects for the boot image.
2007 info_dict: A dict returned by common.LoadInfoDict() on the input
2008 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002009 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002010 if info_dict is None:
2011 info_dict = OPTIONS.info_dict
2012
Tao Bao6d5d6232018-03-09 17:04:42 -08002013 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002014
Tao Baof2cffbd2015-07-22 12:33:18 -07002015 if full_recovery_image:
2016 output_sink("etc/recovery.img", recovery_img.data)
2017
2018 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002019 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002020 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002021 # With system-root-image, boot and recovery images will have mismatching
2022 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2023 # to handle such a case.
2024 if system_root_image:
2025 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002026 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002027 assert not os.path.exists(path)
2028 else:
2029 diff_program = ["imgdiff"]
2030 if os.path.exists(path):
2031 diff_program.append("-b")
2032 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002033 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002034 else:
2035 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002036
2037 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2038 _, _, patch = d.ComputePatch()
2039 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002040
Dan Albertebb19aa2015-03-27 19:11:53 -07002041 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002042 # The following GetTypeAndDevice()s need to use the path in the target
2043 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002044 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2045 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2046 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002047 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002048
Tao Baof2cffbd2015-07-22 12:33:18 -07002049 if full_recovery_image:
2050 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002051if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2052 applypatch \\
2053 --flash /system/etc/recovery.img \\
2054 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2055 log -t recovery "Installing new recovery image: succeeded" || \\
2056 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002057else
2058 log -t recovery "Recovery image already installed"
2059fi
2060""" % {'type': recovery_type,
2061 'device': recovery_device,
2062 'sha1': recovery_img.sha1,
2063 'size': recovery_img.size}
2064 else:
2065 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002066if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2067 applypatch %(bonus_args)s \\
2068 --patch /system/recovery-from-boot.p \\
2069 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2070 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2071 log -t recovery "Installing new recovery image: succeeded" || \\
2072 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002073else
2074 log -t recovery "Recovery image already installed"
2075fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002076""" % {'boot_size': boot_img.size,
2077 'boot_sha1': boot_img.sha1,
2078 'recovery_size': recovery_img.size,
2079 'recovery_sha1': recovery_img.sha1,
2080 'boot_type': boot_type,
2081 'boot_device': boot_device,
2082 'recovery_type': recovery_type,
2083 'recovery_device': recovery_device,
2084 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002085
2086 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002087 # in the L release.
2088 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002089
Tao Bao89fbb0f2017-01-10 10:47:58 -08002090 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002091
2092 output_sink(sh_location, sh)