blob: e035302eb6a1027a3e611ae96fe73bf037f80ffd [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
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
33
Tao Baof3282b42015-04-01 11:21:55 -070034from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080035
Doug Zongkereef39442009-04-02 12:14:19 -070036
Dan Albert8b72aef2015-03-23 19:13:21 -070037class Options(object):
38 def __init__(self):
39 platform_search_path = {
40 "linux2": "out/host/linux-x86",
41 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070042 }
Doug Zongker85448772014-09-09 14:59:20 -070043
Dan Albert8b72aef2015-03-23 19:13:21 -070044 self.search_path = platform_search_path.get(sys.platform, None)
45 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080046 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070047 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070065 # Stash size cannot exceed cache_size * threshold.
66 self.cache_size = None
67 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070068
69
70OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070071
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080072
73# Values for "certificate" in apkcerts that mean special things.
74SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
75
Tianjie Xu209db462016-05-24 17:34:52 -070076class ErrorCode(object):
77 """Define error_codes for failures that happen during the actual
78 update package installation.
79
80 Error codes 0-999 are reserved for failures before the package
81 installation (i.e. low battery, package verification failure).
82 Detailed code in 'bootable/recovery/error_code.h' """
83
84 SYSTEM_VERIFICATION_FAILURE = 1000
85 SYSTEM_UPDATE_FAILURE = 1001
86 SYSTEM_UNEXPECTED_CONTENTS = 1002
87 SYSTEM_NONZERO_CONTENTS = 1003
88 SYSTEM_RECOVER_FAILURE = 1004
89 VENDOR_VERIFICATION_FAILURE = 2000
90 VENDOR_UPDATE_FAILURE = 2001
91 VENDOR_UNEXPECTED_CONTENTS = 2002
92 VENDOR_NONZERO_CONTENTS = 2003
93 VENDOR_RECOVER_FAILURE = 2004
94 OEM_PROP_MISMATCH = 3000
95 FINGERPRINT_MISMATCH = 3001
96 THUMBPRINT_MISMATCH = 3002
97 OLDER_BUILD = 3003
98 DEVICE_MISMATCH = 3004
99 BAD_PATCH_FILE = 3005
100 INSUFFICIENT_CACHE_SPACE = 3006
101 TUNE_PARTITION_FAILURE = 3007
102 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800103
Dan Albert8b72aef2015-03-23 19:13:21 -0700104class ExternalError(RuntimeError):
105 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700106
107
108def Run(args, **kwargs):
109 """Create and return a subprocess.Popen object, printing the command
110 line on the terminal if -v was specified."""
111 if OPTIONS.verbose:
112 print " running: ", " ".join(args)
113 return subprocess.Popen(args, **kwargs)
114
115
Ying Wang7e6d4e42010-12-13 16:25:36 -0800116def CloseInheritedPipes():
117 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
118 before doing other work."""
119 if platform.system() != "Darwin":
120 return
121 for d in range(3, 1025):
122 try:
123 stat = os.fstat(d)
124 if stat is not None:
125 pipebit = stat[0] & 0x1000
126 if pipebit != 0:
127 os.close(d)
128 except OSError:
129 pass
130
131
Tao Bao2c15d9e2015-07-09 11:51:16 -0700132def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700133 """Read and parse the META/misc_info.txt key/value pairs from the
134 input target files and return a dict."""
135
Doug Zongkerc9253822014-02-04 12:17:58 -0800136 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700137 if isinstance(input_file, zipfile.ZipFile):
138 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800139 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700140 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800141 try:
142 with open(path) as f:
143 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700144 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800145 if e.errno == errno.ENOENT:
146 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700147 d = {}
148 try:
Michael Runge6e836112014-04-15 17:40:21 -0700149 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700150 except KeyError:
151 # ok if misc_info.txt doesn't exist
152 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700153
Doug Zongker37974732010-09-16 17:44:38 -0700154 # backwards compatibility: These values used to be in their own
155 # files. Look for them, in case we're processing an old
156 # target_files zip.
157
Doug Zongker37974732010-09-16 17:44:38 -0700158 if "recovery_api_version" not in d:
159 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 d["recovery_api_version"] = read_helper(
161 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700162 except KeyError:
163 raise ValueError("can't find recovery API version in input target-files")
164
165 if "tool_extensions" not in d:
166 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800167 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700168 except KeyError:
169 # ok if extensions don't exist
170 pass
171
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800172 if "fstab_version" not in d:
173 d["fstab_version"] = "1"
174
Tao Bao84e75682015-07-19 02:38:53 -0700175 # A few properties are stored as links to the files in the out/ directory.
176 # It works fine with the build system. However, they are no longer available
177 # when (re)generating from target_files zip. If input_dir is not None, we
178 # are doing repacking. Redirect those properties to the actual files in the
179 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400181 # We carry a copy of file_contexts.bin under META/. If not available,
182 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700183 # to build images than the one running on device, such as when enabling
184 # system_root_image. In that case, we must have the one for image
185 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700186 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
187 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700188 if d.get("system_root_image") == "true":
189 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700190 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700191 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700192 if not os.path.exists(fc_config):
193 fc_config = None
194
195 if fc_config:
196 d["selinux_fc"] = fc_config
197
Tao Bao84e75682015-07-19 02:38:53 -0700198 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
199 if d.get("system_root_image") == "true":
200 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
201 d["ramdisk_fs_config"] = os.path.join(
202 input_dir, "META", "root_filesystem_config.txt")
203
Tao Baof54216f2016-03-29 15:12:37 -0700204 # Redirect {system,vendor}_base_fs_file.
205 if "system_base_fs_file" in d:
206 basename = os.path.basename(d["system_base_fs_file"])
207 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700208 if os.path.exists(system_base_fs_file):
209 d["system_base_fs_file"] = system_base_fs_file
210 else:
211 print "Warning: failed to find system base fs file: %s" % (
212 system_base_fs_file,)
213 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700214
215 if "vendor_base_fs_file" in d:
216 basename = os.path.basename(d["vendor_base_fs_file"])
217 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700218 if os.path.exists(vendor_base_fs_file):
219 d["vendor_base_fs_file"] = vendor_base_fs_file
220 else:
221 print "Warning: failed to find vendor base fs file: %s" % (
222 vendor_base_fs_file,)
223 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700224
Doug Zongker37974732010-09-16 17:44:38 -0700225 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800226 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700227 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not line:
229 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700230 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 if not value:
232 continue
Doug Zongker37974732010-09-16 17:44:38 -0700233 if name == "blocksize":
234 d[name] = value
235 else:
236 d[name + "_size"] = value
237 except KeyError:
238 pass
239
240 def makeint(key):
241 if key in d:
242 d[key] = int(d[key], 0)
243
244 makeint("recovery_api_version")
245 makeint("blocksize")
246 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700247 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700248 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700249 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700250 makeint("recovery_size")
251 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700253
Tianjie Xucfa86222016-03-07 16:31:19 -0800254 system_root_image = d.get("system_root_image", None) == "true"
255 if d.get("no_recovery", None) != "true":
256 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800257 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800258 recovery_fstab_path, system_root_image)
259 elif d.get("recovery_as_boot", None) == "true":
260 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
261 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
262 recovery_fstab_path, system_root_image)
263 else:
264 d["fstab"] = None
265
Doug Zongkerc9253822014-02-04 12:17:58 -0800266 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700267 return d
268
Doug Zongkerc9253822014-02-04 12:17:58 -0800269def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700270 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800271 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700272 except KeyError:
273 print "Warning: could not find SYSTEM/build.prop in %s" % zip
274 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700275 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700276
Michael Runge6e836112014-04-15 17:40:21 -0700277def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700278 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700279 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700280 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 if not line or line.startswith("#"):
282 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700283 if "=" in line:
284 name, value = line.split("=", 1)
285 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700286 return d
287
Tianjie Xucfa86222016-03-07 16:31:19 -0800288def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
289 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700290 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700291 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700292 self.mount_point = mount_point
293 self.fs_type = fs_type
294 self.device = device
295 self.length = length
296 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700297 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700298
299 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800300 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700301 except KeyError:
Tianjie Xucfa86222016-03-07 16:31:19 -0800302 print "Warning: could not find {}".format(recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700303 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700304
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800305 if fstab_version == 1:
306 d = {}
307 for line in data.split("\n"):
308 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 if not line or line.startswith("#"):
310 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800311 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700312 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800313 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800314 options = None
315 if len(pieces) >= 4:
316 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700317 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800318 if len(pieces) >= 5:
319 options = pieces[4]
320 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700321 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800322 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800323 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700325
Dan Albert8b72aef2015-03-23 19:13:21 -0700326 mount_point = pieces[0]
327 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800328 if options:
329 options = options.split(",")
330 for i in options:
331 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700332 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700334 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800335
Dan Albert8b72aef2015-03-23 19:13:21 -0700336 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
337 device=pieces[2], length=length,
338 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800339
340 elif fstab_version == 2:
341 d = {}
342 for line in data.split("\n"):
343 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700344 if not line or line.startswith("#"):
345 continue
Tao Bao548eb762015-06-10 12:32:41 -0700346 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800347 pieces = line.split()
348 if len(pieces) != 5:
349 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
350
351 # Ignore entries that are managed by vold
352 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700353 if "voldmanaged=" in options:
354 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800355
356 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700357 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800358 options = options.split(",")
359 for i in options:
360 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700361 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800362 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800363 # Ignore all unknown options in the unified fstab
364 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800365
Tao Bao548eb762015-06-10 12:32:41 -0700366 mount_flags = pieces[3]
367 # Honor the SELinux context if present.
368 context = None
369 for i in mount_flags.split(","):
370 if i.startswith("context="):
371 context = i
372
Dan Albert8b72aef2015-03-23 19:13:21 -0700373 mount_point = pieces[1]
374 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700375 device=pieces[0], length=length,
376 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800377
378 else:
379 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
380
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700381 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700382 # system. Other areas assume system is always at "/system" so point /system
383 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700384 if system_root_image:
385 assert not d.has_key("/system") and d.has_key("/")
386 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700387 return d
388
389
Doug Zongker37974732010-09-16 17:44:38 -0700390def DumpInfoDict(d):
391 for k, v in sorted(d.items()):
392 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700393
Dan Albert8b72aef2015-03-23 19:13:21 -0700394
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700395def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
396 has_ramdisk=False):
397 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700398
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700399 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
400 'sourcedir'), and turn them into a boot image. Return the image data, or
401 None if sourcedir does not appear to contains files for building the
402 requested image."""
403
404 def make_ramdisk():
405 ramdisk_img = tempfile.NamedTemporaryFile()
406
407 if os.access(fs_config_file, os.F_OK):
408 cmd = ["mkbootfs", "-f", fs_config_file,
409 os.path.join(sourcedir, "RAMDISK")]
410 else:
411 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
412 p1 = Run(cmd, stdout=subprocess.PIPE)
413 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
414
415 p2.wait()
416 p1.wait()
417 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
418 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
419
420 return ramdisk_img
421
422 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
423 return None
424
425 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700426 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700427
Doug Zongkerd5131602012-08-02 14:46:42 -0700428 if info_dict is None:
429 info_dict = OPTIONS.info_dict
430
Doug Zongkereef39442009-04-02 12:14:19 -0700431 img = tempfile.NamedTemporaryFile()
432
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700433 if has_ramdisk:
434 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700435
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800436 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
437 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
438
439 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700440
Benoit Fradina45a8682014-07-14 21:00:43 +0200441 fn = os.path.join(sourcedir, "second")
442 if os.access(fn, os.F_OK):
443 cmd.append("--second")
444 cmd.append(fn)
445
Doug Zongker171f1cd2009-06-15 22:36:37 -0700446 fn = os.path.join(sourcedir, "cmdline")
447 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700448 cmd.append("--cmdline")
449 cmd.append(open(fn).read().rstrip("\n"))
450
451 fn = os.path.join(sourcedir, "base")
452 if os.access(fn, os.F_OK):
453 cmd.append("--base")
454 cmd.append(open(fn).read().rstrip("\n"))
455
Ying Wang4de6b5b2010-08-25 14:29:34 -0700456 fn = os.path.join(sourcedir, "pagesize")
457 if os.access(fn, os.F_OK):
458 cmd.append("--pagesize")
459 cmd.append(open(fn).read().rstrip("\n"))
460
Doug Zongkerd5131602012-08-02 14:46:42 -0700461 args = info_dict.get("mkbootimg_args", None)
462 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700463 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700464
Sami Tolvanen3303d902016-03-15 16:49:30 +0000465 args = info_dict.get("mkbootimg_version_args", None)
466 if args and args.strip():
467 cmd.extend(shlex.split(args))
468
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700469 if has_ramdisk:
470 cmd.extend(["--ramdisk", ramdisk_img.name])
471
Tao Baod95e9fd2015-03-29 23:07:41 -0700472 img_unsigned = None
473 if info_dict.get("vboot", None):
474 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700475 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700476 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700477 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700478
479 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700480 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700481 assert p.returncode == 0, "mkbootimg of %s image failed" % (
482 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700483
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100484 if (info_dict.get("boot_signer", None) == "true" and
485 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700486 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700487 cmd = [OPTIONS.boot_signer_path]
488 cmd.extend(OPTIONS.boot_signer_args)
489 cmd.extend([path, img.name,
490 info_dict["verity_key"] + ".pk8",
491 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700492 p = Run(cmd, stdout=subprocess.PIPE)
493 p.communicate()
494 assert p.returncode == 0, "boot_signer of %s image failed" % path
495
Tao Baod95e9fd2015-03-29 23:07:41 -0700496 # Sign the image if vboot is non-empty.
497 elif info_dict.get("vboot", None):
498 path = "/" + os.path.basename(sourcedir).lower()
499 img_keyblock = tempfile.NamedTemporaryFile()
500 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
501 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700502 info_dict["vboot_key"] + ".vbprivk",
503 info_dict["vboot_subkey"] + ".vbprivk",
504 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700505 img.name]
506 p = Run(cmd, stdout=subprocess.PIPE)
507 p.communicate()
508 assert p.returncode == 0, "vboot_signer of %s image failed" % path
509
Tao Baof3282b42015-04-01 11:21:55 -0700510 # Clean up the temp files.
511 img_unsigned.close()
512 img_keyblock.close()
513
Doug Zongkereef39442009-04-02 12:14:19 -0700514 img.seek(os.SEEK_SET, 0)
515 data = img.read()
516
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700517 if has_ramdisk:
518 ramdisk_img.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700519 img.close()
520
521 return data
522
523
David Zeuthend995f4b2016-01-29 16:59:17 -0500524def _BuildBvbBootableImage(sourcedir, fs_config_file, system_img_path,
525 info_dict=None, has_ramdisk=False):
526 """Build a bootable image compatible with Brillo Verified Boot from the
527 specified sourcedir.
528
529 Take a kernel, cmdline, system image path, and optionally a ramdisk
530 directory from the input (in 'sourcedir'), and turn them into a boot
531 image. Return the image data, or None if sourcedir does not appear
532 to contains files for building the requested image.
533 """
534
535 def make_ramdisk():
536 ramdisk_img = tempfile.NamedTemporaryFile()
537
538 if os.access(fs_config_file, os.F_OK):
539 cmd = ["mkbootfs", "-f", fs_config_file,
540 os.path.join(sourcedir, "RAMDISK")]
541 else:
542 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
543 p1 = Run(cmd, stdout=subprocess.PIPE)
544 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
545
546 p2.wait()
547 p1.wait()
548 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
549 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
550
551 return ramdisk_img
552
553 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
554 return None
555
556 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
557 return None
558
559 if info_dict is None:
560 info_dict = OPTIONS.info_dict
561
562 img = tempfile.NamedTemporaryFile()
563
564 if has_ramdisk:
565 ramdisk_img = make_ramdisk()
566
567 # use BVBTOOL from environ, or "bvbtool" if empty or not set
568 bvbtool = os.getenv('BVBTOOL') or "bvbtool"
569
570 # First, create boot.img.
571 cmd = [bvbtool, "make_boot_image"]
572
573 fn = os.path.join(sourcedir, "cmdline")
574 if os.access(fn, os.F_OK):
575 cmd.append("--kernel_cmdline")
576 cmd.append(open(fn).read().rstrip("\n"))
577
578 cmd.extend(["--kernel", os.path.join(sourcedir, "kernel")])
579
580 if has_ramdisk:
581 cmd.extend(["--initrd", ramdisk_img.name])
582
583 cmd.extend(["--rootfs_with_hashes", system_img_path])
584
585 args = info_dict.get("board_bvb_make_boot_image_args", None)
586 if args and args.strip():
587 cmd.extend(shlex.split(args))
588
589 rollback_index = info_dict.get("board_bvb_rollback_index", None)
590 if rollback_index and rollback_index.strip():
591 cmd.extend(["--rollback_index", rollback_index.strip()])
592
593 cmd.extend(["--output", img.name])
594
595 p = Run(cmd, stdout=subprocess.PIPE)
596 p.communicate()
597 assert p.returncode == 0, "bvbtool make_boot_image of %s image failed" % (
598 os.path.basename(sourcedir),)
599
600 # Then, sign boot.img.
601 cmd = [bvbtool, "sign_boot_image", "--image", img.name]
602
603 algorithm = info_dict.get("board_bvb_algorithm", None)
604 key_path = info_dict.get("board_bvb_key_path", None)
605 if algorithm and algorithm.strip() and key_path and key_path.strip():
606 cmd.extend(["--algorithm", algorithm, "--key", key_path])
607 else:
608 cmd.extend(["--algorithm", "SHA256_RSA4096"])
Ethan Xia37b4a982016-06-27 17:19:01 +0800609 cmd.extend(["--key", "external/bvb/test/testkey_rsa4096.pem"])
David Zeuthend995f4b2016-01-29 16:59:17 -0500610
611 args = info_dict.get("board_bvb_sign_boot_image_args", None)
612 if args and args.strip():
613 cmd.extend(shlex.split(args))
614
615 p = Run(cmd, stdout=subprocess.PIPE)
616 p.communicate()
617 assert p.returncode == 0, "bvbtool sign_boot_image of %s image failed" % (
618 os.path.basename(sourcedir),)
619
620 img.seek(os.SEEK_SET, 0)
621 data = img.read()
622
623 if has_ramdisk:
624 ramdisk_img.close()
625 img.close()
626
627 return data
628
629
Doug Zongkerd5131602012-08-02 14:46:42 -0700630def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
David Zeuthend995f4b2016-01-29 16:59:17 -0500631 info_dict=None, system_img_path=None):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700632 """Return a File object with the desired bootable image.
633
634 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
635 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
636 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700637
Doug Zongker55d93282011-01-25 17:03:34 -0800638 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
639 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700640 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800641 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700642
643 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
644 if os.path.exists(prebuilt_path):
645 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
646 return File.FromLocalFile(name, prebuilt_path)
647
648 print "building image from target_files %s..." % (tree_subdir,)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700649
650 if info_dict is None:
651 info_dict = OPTIONS.info_dict
652
653 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800654 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
655 # for recovery.
656 has_ramdisk = (info_dict.get("system_root_image") != "true" or
657 prebuilt_name != "boot.img" or
658 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700659
Doug Zongker6f1d0312014-08-22 08:07:12 -0700660 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthend995f4b2016-01-29 16:59:17 -0500661 if info_dict.get("board_bvb_enable", None) == "true":
662 data = _BuildBvbBootableImage(os.path.join(unpack_dir, tree_subdir),
663 os.path.join(unpack_dir, fs_config),
664 system_img_path, info_dict, has_ramdisk)
665 else:
666 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
667 os.path.join(unpack_dir, fs_config),
668 info_dict, has_ramdisk)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700669 if data:
670 return File(name, data)
671 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800672
Doug Zongkereef39442009-04-02 12:14:19 -0700673
Doug Zongker75f17362009-12-08 13:46:44 -0800674def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800675 """Unzip the given archive into a temporary directory and return the name.
676
677 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
678 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
679
680 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
681 main file), open for reading.
682 """
Doug Zongkereef39442009-04-02 12:14:19 -0700683
684 tmp = tempfile.mkdtemp(prefix="targetfiles-")
685 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800686
687 def unzip_to_dir(filename, dirname):
688 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
689 if pattern is not None:
690 cmd.append(pattern)
691 p = Run(cmd, stdout=subprocess.PIPE)
692 p.communicate()
693 if p.returncode != 0:
694 raise ExternalError("failed to unzip input target-files \"%s\"" %
695 (filename,))
696
697 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
698 if m:
699 unzip_to_dir(m.group(1), tmp)
700 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
701 filename = m.group(1)
702 else:
703 unzip_to_dir(filename, tmp)
704
705 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700706
707
708def GetKeyPasswords(keylist):
709 """Given a list of keys, prompt the user to enter passwords for
710 those which require them. Return a {key: password} dict. password
711 will be None if the key has no password."""
712
Doug Zongker8ce7c252009-05-22 13:34:54 -0700713 no_passwords = []
714 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700715 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700716 devnull = open("/dev/null", "w+b")
717 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800718 # We don't need a password for things that aren't really keys.
719 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700720 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700721 continue
722
T.R. Fullhart37e10522013-03-18 10:31:26 -0700723 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700724 "-inform", "DER", "-nocrypt"],
725 stdin=devnull.fileno(),
726 stdout=devnull.fileno(),
727 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700728 p.communicate()
729 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700730 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700731 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700732 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700733 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
734 "-inform", "DER", "-passin", "pass:"],
735 stdin=devnull.fileno(),
736 stdout=devnull.fileno(),
737 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700738 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700739 if p.returncode == 0:
740 # Encrypted key with empty string as password.
741 key_passwords[k] = ''
742 elif stderr.startswith('Error decrypting key'):
743 # Definitely encrypted key.
744 # It would have said "Error reading key" if it didn't parse correctly.
745 need_passwords.append(k)
746 else:
747 # Potentially, a type of key that openssl doesn't understand.
748 # We'll let the routines in signapk.jar handle it.
749 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700750 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700751
T.R. Fullhart37e10522013-03-18 10:31:26 -0700752 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700753 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700754 return key_passwords
755
756
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800757def GetMinSdkVersion(apk_name):
758 """Get the minSdkVersion delared in the APK. This can be both a decimal number
759 (API Level) or a codename.
760 """
761
762 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
763 output, err = p.communicate()
764 if err:
765 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
766 % (p.returncode,))
767
768 for line in output.split("\n"):
769 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
770 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
771 if m:
772 return m.group(1)
773 raise ExternalError("No minSdkVersion returned by aapt")
774
775
776def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
777 """Get the minSdkVersion declared in the APK as a number (API Level). If
778 minSdkVersion is set to a codename, it is translated to a number using the
779 provided map.
780 """
781
782 version = GetMinSdkVersion(apk_name)
783 try:
784 return int(version)
785 except ValueError:
786 # Not a decimal number. Codename?
787 if version in codename_to_api_level_map:
788 return codename_to_api_level_map[version]
789 else:
790 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
791 % (version, codename_to_api_level_map))
792
793
794def SignFile(input_name, output_name, key, password, min_api_level=None,
795 codename_to_api_level_map=dict(),
796 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700797 """Sign the input_name zip/jar/apk, producing output_name. Use the
798 given key and password (the latter may be None if the key does not
799 have a password.
800
Doug Zongker951495f2009-08-14 12:44:19 -0700801 If whole_file is true, use the "-w" option to SignApk to embed a
802 signature that covers the whole file in the archive comment of the
803 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800804
805 min_api_level is the API Level (int) of the oldest platform this file may end
806 up on. If not specified for an APK, the API Level is obtained by interpreting
807 the minSdkVersion attribute of the APK's AndroidManifest.xml.
808
809 codename_to_api_level_map is needed to translate the codename which may be
810 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700811 """
Doug Zongker951495f2009-08-14 12:44:19 -0700812
Alex Klyubin9667b182015-12-10 13:38:50 -0800813 java_library_path = os.path.join(
814 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
815
816 cmd = [OPTIONS.java_path, OPTIONS.java_args,
817 "-Djava.library.path=" + java_library_path,
818 "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700819 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
820 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700821 if whole_file:
822 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800823
824 min_sdk_version = min_api_level
825 if min_sdk_version is None:
826 if not whole_file:
827 min_sdk_version = GetMinSdkVersionInt(
828 input_name, codename_to_api_level_map)
829 if min_sdk_version is not None:
830 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
831
T.R. Fullhart37e10522013-03-18 10:31:26 -0700832 cmd.extend([key + OPTIONS.public_key_suffix,
833 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800834 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700835
836 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700837 if password is not None:
838 password += "\n"
839 p.communicate(password)
840 if p.returncode != 0:
841 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
842
Doug Zongkereef39442009-04-02 12:14:19 -0700843
Doug Zongker37974732010-09-16 17:44:38 -0700844def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700845 """Check the data string passed against the max size limit, if
846 any, for the given target. Raise exception if the data is too big.
847 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700848
Dan Albert8b72aef2015-03-23 19:13:21 -0700849 if target.endswith(".img"):
850 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700851 mount_point = "/" + target
852
Ying Wangf8824af2014-06-03 14:07:27 -0700853 fs_type = None
854 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700855 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700856 if mount_point == "/userdata":
857 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700858 p = info_dict["fstab"][mount_point]
859 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800860 device = p.device
861 if "/" in device:
862 device = device[device.rfind("/")+1:]
863 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700864 if not fs_type or not limit:
865 return
Doug Zongkereef39442009-04-02 12:14:19 -0700866
Andrew Boie0f9aec82012-02-14 09:32:52 -0800867 size = len(data)
868 pct = float(size) * 100.0 / limit
869 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
870 if pct >= 99.0:
871 raise ExternalError(msg)
872 elif pct >= 95.0:
873 print
874 print " WARNING: ", msg
875 print
876 elif OPTIONS.verbose:
877 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700878
879
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800880def ReadApkCerts(tf_zip):
881 """Given a target_files ZipFile, parse the META/apkcerts.txt file
882 and return a {package: cert} dict."""
883 certmap = {}
884 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
885 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700886 if not line:
887 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800888 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
889 r'private_key="(.*)"$', line)
890 if m:
891 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700892 public_key_suffix_len = len(OPTIONS.public_key_suffix)
893 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800894 if cert in SPECIAL_CERT_STRINGS and not privkey:
895 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700896 elif (cert.endswith(OPTIONS.public_key_suffix) and
897 privkey.endswith(OPTIONS.private_key_suffix) and
898 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
899 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800900 else:
901 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
902 return certmap
903
904
Doug Zongkereef39442009-04-02 12:14:19 -0700905COMMON_DOCSTRING = """
906 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700907 Prepend <dir>/bin to the list of places to search for binaries
908 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700909
Doug Zongker05d3dea2009-06-22 11:32:31 -0700910 -s (--device_specific) <file>
911 Path to the python module containing device-specific
912 releasetools code.
913
Doug Zongker8bec09e2009-11-30 15:37:14 -0800914 -x (--extra) <key=value>
915 Add a key/value pair to the 'extras' dict, which device-specific
916 extension code may look at.
917
Doug Zongkereef39442009-04-02 12:14:19 -0700918 -v (--verbose)
919 Show command lines being executed.
920
921 -h (--help)
922 Display this usage message and exit.
923"""
924
925def Usage(docstring):
926 print docstring.rstrip("\n")
927 print COMMON_DOCSTRING
928
929
930def ParseOptions(argv,
931 docstring,
932 extra_opts="", extra_long_opts=(),
933 extra_option_handler=None):
934 """Parse the options in argv and return any arguments that aren't
935 flags. docstring is the calling module's docstring, to be displayed
936 for errors and -h. extra_opts and extra_long_opts are for flags
937 defined by the caller, which are processed by passing them to
938 extra_option_handler."""
939
940 try:
941 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800942 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800943 ["help", "verbose", "path=", "signapk_path=",
944 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700945 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700946 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
947 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800948 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700949 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700950 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700951 Usage(docstring)
952 print "**", str(err), "**"
953 sys.exit(2)
954
Doug Zongkereef39442009-04-02 12:14:19 -0700955 for o, a in opts:
956 if o in ("-h", "--help"):
957 Usage(docstring)
958 sys.exit()
959 elif o in ("-v", "--verbose"):
960 OPTIONS.verbose = True
961 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700962 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700963 elif o in ("--signapk_path",):
964 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800965 elif o in ("--signapk_shared_library_path",):
966 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700967 elif o in ("--extra_signapk_args",):
968 OPTIONS.extra_signapk_args = shlex.split(a)
969 elif o in ("--java_path",):
970 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700971 elif o in ("--java_args",):
972 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700973 elif o in ("--public_key_suffix",):
974 OPTIONS.public_key_suffix = a
975 elif o in ("--private_key_suffix",):
976 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800977 elif o in ("--boot_signer_path",):
978 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700979 elif o in ("--boot_signer_args",):
980 OPTIONS.boot_signer_args = shlex.split(a)
981 elif o in ("--verity_signer_path",):
982 OPTIONS.verity_signer_path = a
983 elif o in ("--verity_signer_args",):
984 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700985 elif o in ("-s", "--device_specific"):
986 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800987 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800988 key, value = a.split("=", 1)
989 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700990 else:
991 if extra_option_handler is None or not extra_option_handler(o, a):
992 assert False, "unknown option \"%s\"" % (o,)
993
Doug Zongker85448772014-09-09 14:59:20 -0700994 if OPTIONS.search_path:
995 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
996 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700997
998 return args
999
1000
Doug Zongkerfc44a512014-08-26 13:10:25 -07001001def MakeTempFile(prefix=None, suffix=None):
1002 """Make a temp file and add it to the list of things to be deleted
1003 when Cleanup() is called. Return the filename."""
1004 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1005 os.close(fd)
1006 OPTIONS.tempfiles.append(fn)
1007 return fn
1008
1009
Doug Zongkereef39442009-04-02 12:14:19 -07001010def Cleanup():
1011 for i in OPTIONS.tempfiles:
1012 if os.path.isdir(i):
1013 shutil.rmtree(i)
1014 else:
1015 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001016
1017
1018class PasswordManager(object):
1019 def __init__(self):
1020 self.editor = os.getenv("EDITOR", None)
1021 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
1022
1023 def GetPasswords(self, items):
1024 """Get passwords corresponding to each string in 'items',
1025 returning a dict. (The dict may have keys in addition to the
1026 values in 'items'.)
1027
1028 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1029 user edit that file to add more needed passwords. If no editor is
1030 available, or $ANDROID_PW_FILE isn't define, prompts the user
1031 interactively in the ordinary way.
1032 """
1033
1034 current = self.ReadFile()
1035
1036 first = True
1037 while True:
1038 missing = []
1039 for i in items:
1040 if i not in current or not current[i]:
1041 missing.append(i)
1042 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001043 if not missing:
1044 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001045
1046 for i in missing:
1047 current[i] = ""
1048
1049 if not first:
1050 print "key file %s still missing some passwords." % (self.pwfile,)
1051 answer = raw_input("try to edit again? [y]> ").strip()
1052 if answer and answer[0] not in 'yY':
1053 raise RuntimeError("key passwords unavailable")
1054 first = False
1055
1056 current = self.UpdateAndReadFile(current)
1057
Dan Albert8b72aef2015-03-23 19:13:21 -07001058 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001059 """Prompt the user to enter a value (password) for each key in
1060 'current' whose value is fales. Returns a new dict with all the
1061 values.
1062 """
1063 result = {}
1064 for k, v in sorted(current.iteritems()):
1065 if v:
1066 result[k] = v
1067 else:
1068 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001069 result[k] = getpass.getpass(
1070 "Enter password for %s key> " % k).strip()
1071 if result[k]:
1072 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001073 return result
1074
1075 def UpdateAndReadFile(self, current):
1076 if not self.editor or not self.pwfile:
1077 return self.PromptResult(current)
1078
1079 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001080 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001081 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1082 f.write("# (Additional spaces are harmless.)\n\n")
1083
1084 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001085 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1086 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001087 f.write("[[[ %s ]]] %s\n" % (v, k))
1088 if not v and first_line is None:
1089 # position cursor on first line with no password.
1090 first_line = i + 4
1091 f.close()
1092
1093 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
1094 _, _ = p.communicate()
1095
1096 return self.ReadFile()
1097
1098 def ReadFile(self):
1099 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001100 if self.pwfile is None:
1101 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001102 try:
1103 f = open(self.pwfile, "r")
1104 for line in f:
1105 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001106 if not line or line[0] == '#':
1107 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001108 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1109 if not m:
1110 print "failed to parse password file: ", line
1111 else:
1112 result[m.group(2)] = m.group(1)
1113 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001114 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001115 if e.errno != errno.ENOENT:
1116 print "error reading password file: ", str(e)
1117 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001118
1119
Dan Albert8e0178d2015-01-27 15:53:15 -08001120def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1121 compress_type=None):
1122 import datetime
1123
1124 # http://b/18015246
1125 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1126 # for files larger than 2GiB. We can work around this by adjusting their
1127 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1128 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1129 # it isn't clear to me exactly what circumstances cause this).
1130 # `zipfile.write()` must be used directly to work around this.
1131 #
1132 # This mess can be avoided if we port to python3.
1133 saved_zip64_limit = zipfile.ZIP64_LIMIT
1134 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1135
1136 if compress_type is None:
1137 compress_type = zip_file.compression
1138 if arcname is None:
1139 arcname = filename
1140
1141 saved_stat = os.stat(filename)
1142
1143 try:
1144 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1145 # file to be zipped and reset it when we're done.
1146 os.chmod(filename, perms)
1147
1148 # Use a fixed timestamp so the output is repeatable.
1149 epoch = datetime.datetime.fromtimestamp(0)
1150 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1151 os.utime(filename, (timestamp, timestamp))
1152
1153 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1154 finally:
1155 os.chmod(filename, saved_stat.st_mode)
1156 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1157 zipfile.ZIP64_LIMIT = saved_zip64_limit
1158
1159
Tao Bao58c1b962015-05-20 09:32:18 -07001160def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001161 compress_type=None):
1162 """Wrap zipfile.writestr() function to work around the zip64 limit.
1163
1164 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1165 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1166 when calling crc32(bytes).
1167
1168 But it still works fine to write a shorter string into a large zip file.
1169 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1170 when we know the string won't be too long.
1171 """
1172
1173 saved_zip64_limit = zipfile.ZIP64_LIMIT
1174 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1175
1176 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1177 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001178 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001179 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001180 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001181 else:
Tao Baof3282b42015-04-01 11:21:55 -07001182 zinfo = zinfo_or_arcname
1183
1184 # If compress_type is given, it overrides the value in zinfo.
1185 if compress_type is not None:
1186 zinfo.compress_type = compress_type
1187
Tao Bao58c1b962015-05-20 09:32:18 -07001188 # If perms is given, it has a priority.
1189 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001190 # If perms doesn't set the file type, mark it as a regular file.
1191 if perms & 0o770000 == 0:
1192 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001193 zinfo.external_attr = perms << 16
1194
Tao Baof3282b42015-04-01 11:21:55 -07001195 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001196 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1197
Dan Albert8b72aef2015-03-23 19:13:21 -07001198 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001199 zipfile.ZIP64_LIMIT = saved_zip64_limit
1200
1201
1202def ZipClose(zip_file):
1203 # http://b/18015246
1204 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1205 # central directory.
1206 saved_zip64_limit = zipfile.ZIP64_LIMIT
1207 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1208
1209 zip_file.close()
1210
1211 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001212
1213
1214class DeviceSpecificParams(object):
1215 module = None
1216 def __init__(self, **kwargs):
1217 """Keyword arguments to the constructor become attributes of this
1218 object, which is passed to all functions in the device-specific
1219 module."""
1220 for k, v in kwargs.iteritems():
1221 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001222 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001223
1224 if self.module is None:
1225 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001226 if not path:
1227 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001228 try:
1229 if os.path.isdir(path):
1230 info = imp.find_module("releasetools", [path])
1231 else:
1232 d, f = os.path.split(path)
1233 b, x = os.path.splitext(f)
1234 if x == ".py":
1235 f = b
1236 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001237 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001238 self.module = imp.load_module("device_specific", *info)
1239 except ImportError:
1240 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001241
1242 def _DoCall(self, function_name, *args, **kwargs):
1243 """Call the named function in the device-specific module, passing
1244 the given args and kwargs. The first argument to the call will be
1245 the DeviceSpecific object itself. If there is no module, or the
1246 module does not define the function, return the value of the
1247 'default' kwarg (which itself defaults to None)."""
1248 if self.module is None or not hasattr(self.module, function_name):
1249 return kwargs.get("default", None)
1250 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1251
1252 def FullOTA_Assertions(self):
1253 """Called after emitting the block of assertions at the top of a
1254 full OTA package. Implementations can add whatever additional
1255 assertions they like."""
1256 return self._DoCall("FullOTA_Assertions")
1257
Doug Zongkere5ff5902012-01-17 10:55:37 -08001258 def FullOTA_InstallBegin(self):
1259 """Called at the start of full OTA installation."""
1260 return self._DoCall("FullOTA_InstallBegin")
1261
Doug Zongker05d3dea2009-06-22 11:32:31 -07001262 def FullOTA_InstallEnd(self):
1263 """Called at the end of full OTA installation; typically this is
1264 used to install the image for the device's baseband processor."""
1265 return self._DoCall("FullOTA_InstallEnd")
1266
1267 def IncrementalOTA_Assertions(self):
1268 """Called after emitting the block of assertions at the top of an
1269 incremental OTA package. Implementations can add whatever
1270 additional assertions they like."""
1271 return self._DoCall("IncrementalOTA_Assertions")
1272
Doug Zongkere5ff5902012-01-17 10:55:37 -08001273 def IncrementalOTA_VerifyBegin(self):
1274 """Called at the start of the verification phase of incremental
1275 OTA installation; additional checks can be placed here to abort
1276 the script before any changes are made."""
1277 return self._DoCall("IncrementalOTA_VerifyBegin")
1278
Doug Zongker05d3dea2009-06-22 11:32:31 -07001279 def IncrementalOTA_VerifyEnd(self):
1280 """Called at the end of the verification phase of incremental OTA
1281 installation; additional checks can be placed here to abort the
1282 script before any changes are made."""
1283 return self._DoCall("IncrementalOTA_VerifyEnd")
1284
Doug Zongkere5ff5902012-01-17 10:55:37 -08001285 def IncrementalOTA_InstallBegin(self):
1286 """Called at the start of incremental OTA installation (after
1287 verification is complete)."""
1288 return self._DoCall("IncrementalOTA_InstallBegin")
1289
Doug Zongker05d3dea2009-06-22 11:32:31 -07001290 def IncrementalOTA_InstallEnd(self):
1291 """Called at the end of incremental OTA installation; typically
1292 this is used to install the image for the device's baseband
1293 processor."""
1294 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001295
Tao Bao9bc6bb22015-11-09 16:58:28 -08001296 def VerifyOTA_Assertions(self):
1297 return self._DoCall("VerifyOTA_Assertions")
1298
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001299class File(object):
1300 def __init__(self, name, data):
1301 self.name = name
1302 self.data = data
1303 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001304 self.sha1 = sha1(data).hexdigest()
1305
1306 @classmethod
1307 def FromLocalFile(cls, name, diskname):
1308 f = open(diskname, "rb")
1309 data = f.read()
1310 f.close()
1311 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001312
1313 def WriteToTemp(self):
1314 t = tempfile.NamedTemporaryFile()
1315 t.write(self.data)
1316 t.flush()
1317 return t
1318
Geremy Condra36bd3652014-02-06 19:45:10 -08001319 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001320 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001321
1322DIFF_PROGRAM_BY_EXT = {
1323 ".gz" : "imgdiff",
1324 ".zip" : ["imgdiff", "-z"],
1325 ".jar" : ["imgdiff", "-z"],
1326 ".apk" : ["imgdiff", "-z"],
1327 ".img" : "imgdiff",
1328 }
1329
1330class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001331 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001332 self.tf = tf
1333 self.sf = sf
1334 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001335 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001336
1337 def ComputePatch(self):
1338 """Compute the patch (as a string of data) needed to turn sf into
1339 tf. Returns the same tuple as GetPatch()."""
1340
1341 tf = self.tf
1342 sf = self.sf
1343
Doug Zongker24cd2802012-08-14 16:36:15 -07001344 if self.diff_program:
1345 diff_program = self.diff_program
1346 else:
1347 ext = os.path.splitext(tf.name)[1]
1348 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001349
1350 ttemp = tf.WriteToTemp()
1351 stemp = sf.WriteToTemp()
1352
1353 ext = os.path.splitext(tf.name)[1]
1354
1355 try:
1356 ptemp = tempfile.NamedTemporaryFile()
1357 if isinstance(diff_program, list):
1358 cmd = copy.copy(diff_program)
1359 else:
1360 cmd = [diff_program]
1361 cmd.append(stemp.name)
1362 cmd.append(ttemp.name)
1363 cmd.append(ptemp.name)
1364 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001365 err = []
1366 def run():
1367 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001368 if e:
1369 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001370 th = threading.Thread(target=run)
1371 th.start()
1372 th.join(timeout=300) # 5 mins
1373 if th.is_alive():
1374 print "WARNING: diff command timed out"
1375 p.terminate()
1376 th.join(5)
1377 if th.is_alive():
1378 p.kill()
1379 th.join()
1380
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001381 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001382 print "WARNING: failure running %s:\n%s\n" % (
1383 diff_program, "".join(err))
1384 self.patch = None
1385 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001386 diff = ptemp.read()
1387 finally:
1388 ptemp.close()
1389 stemp.close()
1390 ttemp.close()
1391
1392 self.patch = diff
1393 return self.tf, self.sf, self.patch
1394
1395
1396 def GetPatch(self):
1397 """Return a tuple (target_file, source_file, patch_data).
1398 patch_data may be None if ComputePatch hasn't been called, or if
1399 computing the patch failed."""
1400 return self.tf, self.sf, self.patch
1401
1402
1403def ComputeDifferences(diffs):
1404 """Call ComputePatch on all the Difference objects in 'diffs'."""
1405 print len(diffs), "diffs to compute"
1406
1407 # Do the largest files first, to try and reduce the long-pole effect.
1408 by_size = [(i.tf.size, i) for i in diffs]
1409 by_size.sort(reverse=True)
1410 by_size = [i[1] for i in by_size]
1411
1412 lock = threading.Lock()
1413 diff_iter = iter(by_size) # accessed under lock
1414
1415 def worker():
1416 try:
1417 lock.acquire()
1418 for d in diff_iter:
1419 lock.release()
1420 start = time.time()
1421 d.ComputePatch()
1422 dur = time.time() - start
1423 lock.acquire()
1424
1425 tf, sf, patch = d.GetPatch()
1426 if sf.name == tf.name:
1427 name = tf.name
1428 else:
1429 name = "%s (%s)" % (tf.name, sf.name)
1430 if patch is None:
1431 print "patching failed! %s" % (name,)
1432 else:
1433 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1434 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1435 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001436 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001437 print e
1438 raise
1439
1440 # start worker threads; wait for them all to finish.
1441 threads = [threading.Thread(target=worker)
1442 for i in range(OPTIONS.worker_threads)]
1443 for th in threads:
1444 th.start()
1445 while threads:
1446 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001447
1448
Dan Albert8b72aef2015-03-23 19:13:21 -07001449class BlockDifference(object):
1450 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001451 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001452 self.tgt = tgt
1453 self.src = src
1454 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001455 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001456 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001457
Tao Baodd2a5892015-03-12 12:32:37 -07001458 if version is None:
1459 version = 1
1460 if OPTIONS.info_dict:
1461 version = max(
1462 int(i) for i in
1463 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1464 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001465
1466 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001467 version=self.version,
1468 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001469 tmpdir = tempfile.mkdtemp()
1470 OPTIONS.tempfiles.append(tmpdir)
1471 self.path = os.path.join(tmpdir, partition)
1472 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001473 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001474 self.touched_src_ranges = b.touched_src_ranges
1475 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001476
Tao Baoaac4ad52015-10-16 15:26:34 -07001477 if src is None:
1478 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1479 else:
1480 _, self.device = GetTypeAndDevice("/" + partition,
1481 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001482
Tao Baod8d14be2016-02-04 14:26:02 -08001483 @property
1484 def required_cache(self):
1485 return self._required_cache
1486
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001487 def WriteScript(self, script, output_zip, progress=None):
1488 if not self.src:
1489 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001490 script.Print("Patching %s image unconditionally..." % (self.partition,))
1491 else:
1492 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001493
Dan Albert8b72aef2015-03-23 19:13:21 -07001494 if progress:
1495 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001496 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001497 if OPTIONS.verify:
1498 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001499
Tao Bao9bc6bb22015-11-09 16:58:28 -08001500 def WriteStrictVerifyScript(self, script):
1501 """Verify all the blocks in the care_map, including clobbered blocks.
1502
1503 This differs from the WriteVerifyScript() function: a) it prints different
1504 error messages; b) it doesn't allow half-way updated images to pass the
1505 verification."""
1506
1507 partition = self.partition
1508 script.Print("Verifying %s..." % (partition,))
1509 ranges = self.tgt.care_map
1510 ranges_str = ranges.to_string_raw()
1511 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1512 'ui_print(" Verified.") || '
1513 'ui_print("\\"%s\\" has unexpected contents.");' % (
1514 self.device, ranges_str,
1515 self.tgt.TotalSha1(include_clobbered_blocks=True),
1516 self.device))
1517 script.AppendExtra("")
1518
Tao Baod522bdc2016-04-12 15:53:16 -07001519 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001520 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001521
1522 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001523 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001524 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001525
1526 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001527 else:
Tao Baod522bdc2016-04-12 15:53:16 -07001528 if touched_blocks_only and self.version >= 3:
1529 ranges = self.touched_src_ranges
1530 expected_sha1 = self.touched_src_sha1
1531 else:
1532 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1533 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001534
1535 # No blocks to be checked, skipping.
1536 if not ranges:
1537 return
1538
Tao Bao5ece99d2015-05-12 11:42:31 -07001539 ranges_str = ranges.to_string_raw()
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001540 if self.version >= 4:
1541 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1542 'block_image_verify("%s", '
1543 'package_extract_file("%s.transfer.list"), '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001544 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001545 self.device, ranges_str, expected_sha1,
Sami Tolvanenf0a7c762015-06-25 11:48:29 +01001546 self.device, partition, partition, partition))
1547 elif self.version == 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001548 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1549 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001550 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001551 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Baod522bdc2016-04-12 15:53:16 -07001552 self.device, ranges_str, expected_sha1,
Sami Tolvanene09d0962015-04-24 11:54:01 +01001553 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001554 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001555 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001556 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001557 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001558 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001559
Tianjie Xufc3422a2015-12-15 11:53:59 -08001560 if self.version >= 4:
1561
1562 # Bug: 21124327
1563 # When generating incrementals for the system and vendor partitions in
1564 # version 4 or newer, explicitly check the first block (which contains
1565 # the superblock) of the partition to see if it's what we expect. If
1566 # this check fails, give an explicit log message about the partition
1567 # having been remounted R/W (the most likely explanation).
1568 if self.check_first_block:
1569 script.AppendExtra('check_first_block("%s");' % (self.device,))
1570
1571 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001572 if partition == "system":
1573 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1574 else:
1575 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001576 script.AppendExtra((
1577 'ifelse (block_image_recover("{device}", "{ranges}") && '
1578 'block_image_verify("{device}", '
1579 'package_extract_file("{partition}.transfer.list"), '
1580 '"{partition}.new.dat", "{partition}.patch.dat"), '
1581 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001582 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001583 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001584 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001585
Tao Baodd2a5892015-03-12 12:32:37 -07001586 # Abort the OTA update. Note that the incremental OTA cannot be applied
1587 # even if it may match the checksum of the target partition.
1588 # a) If version < 3, operations like move and erase will make changes
1589 # unconditionally and damage the partition.
1590 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001591 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001592 if partition == "system":
1593 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1594 else:
1595 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1596 script.AppendExtra((
1597 'abort("E%d: %s partition has unexpected contents");\n'
1598 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001599
Tao Bao5fcaaef2015-06-01 13:40:49 -07001600 def _WritePostInstallVerifyScript(self, script):
1601 partition = self.partition
1602 script.Print('Verifying the updated %s image...' % (partition,))
1603 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1604 ranges = self.tgt.care_map
1605 ranges_str = ranges.to_string_raw()
1606 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1607 self.device, ranges_str,
1608 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001609
1610 # Bug: 20881595
1611 # Verify that extended blocks are really zeroed out.
1612 if self.tgt.extended:
1613 ranges_str = self.tgt.extended.to_string_raw()
1614 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1615 self.device, ranges_str,
1616 self._HashZeroBlocks(self.tgt.extended.size())))
1617 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001618 if partition == "system":
1619 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1620 else:
1621 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001622 script.AppendExtra(
1623 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001624 ' abort("E%d: %s partition has unexpected non-zero contents after '
1625 'OTA update");\n'
1626 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001627 else:
1628 script.Print('Verified the updated %s image.' % (partition,))
1629
Tianjie Xu209db462016-05-24 17:34:52 -07001630 if partition == "system":
1631 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1632 else:
1633 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1634
Tao Bao5fcaaef2015-06-01 13:40:49 -07001635 script.AppendExtra(
1636 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001637 ' abort("E%d: %s partition has unexpected contents after OTA '
1638 'update");\n'
1639 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001640
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001641 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001642 ZipWrite(output_zip,
1643 '{}.transfer.list'.format(self.path),
1644 '{}.transfer.list'.format(self.partition))
1645 ZipWrite(output_zip,
1646 '{}.new.dat'.format(self.path),
1647 '{}.new.dat'.format(self.partition))
1648 ZipWrite(output_zip,
1649 '{}.patch.dat'.format(self.path),
1650 '{}.patch.dat'.format(self.partition),
1651 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001652
Tianjie Xu209db462016-05-24 17:34:52 -07001653 if self.partition == "system":
1654 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1655 else:
1656 code = ErrorCode.VENDOR_UPDATE_FAILURE
1657
Dan Albert8e0178d2015-01-27 15:53:15 -08001658 call = ('block_image_update("{device}", '
1659 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001660 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001661 ' abort("E{code}: Failed to update {partition} image.");'.format(
1662 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001663 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001664
Dan Albert8b72aef2015-03-23 19:13:21 -07001665 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001666 data = source.ReadRangeSet(ranges)
1667 ctx = sha1()
1668
1669 for p in data:
1670 ctx.update(p)
1671
1672 return ctx.hexdigest()
1673
Tao Baoe9b61912015-07-09 17:37:49 -07001674 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1675 """Return the hash value for all zero blocks."""
1676 zero_block = '\x00' * 4096
1677 ctx = sha1()
1678 for _ in range(num_blocks):
1679 ctx.update(zero_block)
1680
1681 return ctx.hexdigest()
1682
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001683
1684DataImage = blockimgdiff.DataImage
1685
Doug Zongker96a57e72010-09-26 14:57:41 -07001686# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001687PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001688 "ext4": "EMMC",
1689 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001690 "f2fs": "EMMC",
1691 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001692}
Doug Zongker96a57e72010-09-26 14:57:41 -07001693
1694def GetTypeAndDevice(mount_point, info):
1695 fstab = info["fstab"]
1696 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001697 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1698 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001699 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001700 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001701
1702
1703def ParseCertificate(data):
1704 """Parse a PEM-format certificate."""
1705 cert = []
1706 save = False
1707 for line in data.split("\n"):
1708 if "--END CERTIFICATE--" in line:
1709 break
1710 if save:
1711 cert.append(line)
1712 if "--BEGIN CERTIFICATE--" in line:
1713 save = True
1714 cert = "".join(cert).decode('base64')
1715 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001716
Doug Zongker412c02f2014-02-13 10:58:24 -08001717def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1718 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001719 """Generate a binary patch that creates the recovery image starting
1720 with the boot image. (Most of the space in these images is just the
1721 kernel, which is identical for the two, so the resulting patch
1722 should be efficient.) Add it to the output zip, along with a shell
1723 script that is run from init.rc on first boot to actually do the
1724 patching and install the new recovery image.
1725
1726 recovery_img and boot_img should be File objects for the
1727 corresponding images. info should be the dictionary returned by
1728 common.LoadInfoDict() on the input target_files.
1729 """
1730
Doug Zongker412c02f2014-02-13 10:58:24 -08001731 if info_dict is None:
1732 info_dict = OPTIONS.info_dict
1733
Tao Baof2cffbd2015-07-22 12:33:18 -07001734 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001735 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001736
Tao Baof2cffbd2015-07-22 12:33:18 -07001737 if full_recovery_image:
1738 output_sink("etc/recovery.img", recovery_img.data)
1739
1740 else:
1741 diff_program = ["imgdiff"]
1742 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1743 if os.path.exists(path):
1744 diff_program.append("-b")
1745 diff_program.append(path)
1746 bonus_args = "-b /system/etc/recovery-resource.dat"
1747 else:
1748 bonus_args = ""
1749
1750 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1751 _, _, patch = d.ComputePatch()
1752 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001753
Dan Albertebb19aa2015-03-27 19:11:53 -07001754 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001755 # The following GetTypeAndDevice()s need to use the path in the target
1756 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001757 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1758 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1759 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001760 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001761
Tao Baof2cffbd2015-07-22 12:33:18 -07001762 if full_recovery_image:
1763 sh = """#!/system/bin/sh
1764if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1765 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1766else
1767 log -t recovery "Recovery image already installed"
1768fi
1769""" % {'type': recovery_type,
1770 'device': recovery_device,
1771 'sha1': recovery_img.sha1,
1772 'size': recovery_img.size}
1773 else:
1774 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001775if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1776 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1777else
1778 log -t recovery "Recovery image already installed"
1779fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001780""" % {'boot_size': boot_img.size,
1781 'boot_sha1': boot_img.sha1,
1782 'recovery_size': recovery_img.size,
1783 'recovery_sha1': recovery_img.sha1,
1784 'boot_type': boot_type,
1785 'boot_device': boot_device,
1786 'recovery_type': recovery_type,
1787 'recovery_device': recovery_device,
1788 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001789
1790 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001791 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001792 # target-files expects it to be, and put it there.
1793 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001794 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001795 if system_root_image:
1796 init_rc_dir = os.path.join(input_dir, "ROOT")
1797 else:
1798 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001799 init_rc_files = os.listdir(init_rc_dir)
1800 for init_rc_file in init_rc_files:
1801 if (not init_rc_file.startswith('init.') or
1802 not init_rc_file.endswith('.rc')):
1803 continue
1804
1805 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001806 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001807 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001808 if m:
1809 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001810 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001811 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001812
1813 if found:
1814 break
1815
1816 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001817
1818 output_sink(sh_location, sh)