blob: b27e4c1535d65707b571709639f7075380ec528f [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 Zongker55d93282011-01-25 17:03:34 -080032try:
davidcad0bb92011-03-15 14:21:38 +000033 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080034except ImportError:
davidcad0bb92011-03-15 14:21:38 +000035 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037# missing in Python 2.4 and before
38if not hasattr(os, "SEEK_SET"):
39 os.SEEK_SET = 0
40
41class Options(object): pass
42OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070043OPTIONS.search_path = "out/host/linux-x86"
T.R. Fullhart37e10522013-03-18 10:31:26 -070044OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
45OPTIONS.extra_signapk_args = []
46OPTIONS.java_path = "java" # Use the one on the path by default.
47OPTIONS.public_key_suffix = ".x509.pem"
48OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070049OPTIONS.verbose = False
50OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070051OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080052OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070053OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070054
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080055
56# Values for "certificate" in apkcerts that mean special things.
57SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
58
59
Doug Zongkereef39442009-04-02 12:14:19 -070060class ExternalError(RuntimeError): pass
61
62
63def Run(args, **kwargs):
64 """Create and return a subprocess.Popen object, printing the command
65 line on the terminal if -v was specified."""
66 if OPTIONS.verbose:
67 print " running: ", " ".join(args)
68 return subprocess.Popen(args, **kwargs)
69
70
Ying Wang7e6d4e42010-12-13 16:25:36 -080071def CloseInheritedPipes():
72 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
73 before doing other work."""
74 if platform.system() != "Darwin":
75 return
76 for d in range(3, 1025):
77 try:
78 stat = os.fstat(d)
79 if stat is not None:
80 pipebit = stat[0] & 0x1000
81 if pipebit != 0:
82 os.close(d)
83 except OSError:
84 pass
85
86
Doug Zongkerc9253822014-02-04 12:17:58 -080087def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070088 """Read and parse the META/misc_info.txt key/value pairs from the
89 input target files and return a dict."""
90
Doug Zongkerc9253822014-02-04 12:17:58 -080091 def read_helper(fn):
92 if isinstance(input, zipfile.ZipFile):
93 return input.read(fn)
94 else:
95 path = os.path.join(input, *fn.split("/"))
96 try:
97 with open(path) as f:
98 return f.read()
99 except IOError, e:
100 if e.errno == errno.ENOENT:
101 raise KeyError(fn)
102
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700103 d = {}
104 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800105 for line in read_helper("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700106 line = line.strip()
107 if not line or line.startswith("#"): continue
108 k, v = line.split("=", 1)
109 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -0700110 except KeyError:
111 # ok if misc_info.txt doesn't exist
112 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700113
Doug Zongker37974732010-09-16 17:44:38 -0700114 # backwards compatibility: These values used to be in their own
115 # files. Look for them, in case we're processing an old
116 # target_files zip.
117
118 if "mkyaffs2_extra_flags" not in d:
119 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800120 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700121 except KeyError:
122 # ok if flags don't exist
123 pass
124
125 if "recovery_api_version" not in d:
126 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800127 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700128 except KeyError:
129 raise ValueError("can't find recovery API version in input target-files")
130
131 if "tool_extensions" not in d:
132 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800133 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700134 except KeyError:
135 # ok if extensions don't exist
136 pass
137
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800138 if "fstab_version" not in d:
139 d["fstab_version"] = "1"
140
Doug Zongker37974732010-09-16 17:44:38 -0700141 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800142 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700143 for line in data.split("\n"):
144 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700145 name, value = line.split(" ", 1)
146 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700147 if name == "blocksize":
148 d[name] = value
149 else:
150 d[name + "_size"] = value
151 except KeyError:
152 pass
153
154 def makeint(key):
155 if key in d:
156 d[key] = int(d[key], 0)
157
158 makeint("recovery_api_version")
159 makeint("blocksize")
160 makeint("system_size")
161 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700162 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700163 makeint("recovery_size")
164 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800165 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700166
Doug Zongkerc9253822014-02-04 12:17:58 -0800167 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
168 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700169 return d
170
Doug Zongkerc9253822014-02-04 12:17:58 -0800171def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700172 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800173 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700174 except KeyError:
175 print "Warning: could not find SYSTEM/build.prop in %s" % zip
176 data = ""
177
178 d = {}
179 for line in data.split("\n"):
180 line = line.strip()
181 if not line or line.startswith("#"): continue
182 name, value = line.split("=", 1)
183 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700184 return d
185
Doug Zongkerc9253822014-02-04 12:17:58 -0800186def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700187 class Partition(object):
188 pass
189
190 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800191 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700192 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800193 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700194 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700195
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800196 if fstab_version == 1:
197 d = {}
198 for line in data.split("\n"):
199 line = line.strip()
200 if not line or line.startswith("#"): continue
201 pieces = line.split()
202 if not (3 <= len(pieces) <= 4):
203 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800205 p = Partition()
206 p.mount_point = pieces[0]
207 p.fs_type = pieces[1]
208 p.device = pieces[2]
209 p.length = 0
210 options = None
211 if len(pieces) >= 4:
212 if pieces[3].startswith("/"):
213 p.device2 = pieces[3]
214 if len(pieces) >= 5:
215 options = pieces[4]
216 else:
217 p.device2 = None
218 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800219 else:
220 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700221
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800222 if options:
223 options = options.split(",")
224 for i in options:
225 if i.startswith("length="):
226 p.length = int(i[7:])
227 else:
228 print "%s: unknown option \"%s\"" % (p.mount_point, i)
229
230 d[p.mount_point] = p
231
232 elif fstab_version == 2:
233 d = {}
234 for line in data.split("\n"):
235 line = line.strip()
236 if not line or line.startswith("#"): continue
237 pieces = line.split()
238 if len(pieces) != 5:
239 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
240
241 # Ignore entries that are managed by vold
242 options = pieces[4]
243 if "voldmanaged=" in options: continue
244
245 # It's a good line, parse it
246 p = Partition()
247 p.device = pieces[0]
248 p.mount_point = pieces[1]
249 p.fs_type = pieces[2]
250 p.device2 = None
251 p.length = 0
252
Doug Zongker086cbb02011-02-17 15:54:20 -0800253 options = options.split(",")
254 for i in options:
255 if i.startswith("length="):
256 p.length = int(i[7:])
257 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800258 # Ignore all unknown options in the unified fstab
259 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800260
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 d[p.mount_point] = p
262
263 else:
264 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
265
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700266 return d
267
268
Doug Zongker37974732010-09-16 17:44:38 -0700269def DumpInfoDict(d):
270 for k, v in sorted(d.items()):
271 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700272
Doug Zongkerd5131602012-08-02 14:46:42 -0700273def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700274 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700275 'sourcedir'), and turn them into a boot image. Return the image
276 data, or None if sourcedir does not appear to contains files for
277 building the requested image."""
278
279 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
280 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
281 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700282
Doug Zongkerd5131602012-08-02 14:46:42 -0700283 if info_dict is None:
284 info_dict = OPTIONS.info_dict
285
Doug Zongkereef39442009-04-02 12:14:19 -0700286 ramdisk_img = tempfile.NamedTemporaryFile()
287 img = tempfile.NamedTemporaryFile()
288
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700289 if os.access(fs_config_file, os.F_OK):
290 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
291 else:
292 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
293 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700294 p2 = Run(["minigzip"],
295 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700296
297 p2.wait()
298 p1.wait()
299 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700300 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700301
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800302 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
303 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
304
305 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700306
Doug Zongker171f1cd2009-06-15 22:36:37 -0700307 fn = os.path.join(sourcedir, "cmdline")
308 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700309 cmd.append("--cmdline")
310 cmd.append(open(fn).read().rstrip("\n"))
311
312 fn = os.path.join(sourcedir, "base")
313 if os.access(fn, os.F_OK):
314 cmd.append("--base")
315 cmd.append(open(fn).read().rstrip("\n"))
316
Ying Wang4de6b5b2010-08-25 14:29:34 -0700317 fn = os.path.join(sourcedir, "pagesize")
318 if os.access(fn, os.F_OK):
319 cmd.append("--pagesize")
320 cmd.append(open(fn).read().rstrip("\n"))
321
Doug Zongkerd5131602012-08-02 14:46:42 -0700322 args = info_dict.get("mkbootimg_args", None)
323 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700324 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700325
Doug Zongker38a649f2009-06-17 09:07:09 -0700326 cmd.extend(["--ramdisk", ramdisk_img.name,
327 "--output", img.name])
328
329 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700330 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700331 assert p.returncode == 0, "mkbootimg of %s image failed" % (
332 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700333
334 img.seek(os.SEEK_SET, 0)
335 data = img.read()
336
337 ramdisk_img.close()
338 img.close()
339
340 return data
341
342
Doug Zongkerd5131602012-08-02 14:46:42 -0700343def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
344 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800345 """Return a File object (with name 'name') with the desired bootable
346 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
347 'prebuilt_name', otherwise construct it from the source files in
348 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700349
Doug Zongker55d93282011-01-25 17:03:34 -0800350 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
351 if os.path.exists(prebuilt_path):
352 print "using prebuilt %s..." % (prebuilt_name,)
353 return File.FromLocalFile(name, prebuilt_path)
354 else:
355 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700356 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
357 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
Doug Zongkerd5131602012-08-02 14:46:42 -0700358 os.path.join(unpack_dir, fs_config),
359 info_dict))
Doug Zongker55d93282011-01-25 17:03:34 -0800360
Doug Zongkereef39442009-04-02 12:14:19 -0700361
Doug Zongker75f17362009-12-08 13:46:44 -0800362def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800363 """Unzip the given archive into a temporary directory and return the name.
364
365 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
366 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
367
368 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
369 main file), open for reading.
370 """
Doug Zongkereef39442009-04-02 12:14:19 -0700371
372 tmp = tempfile.mkdtemp(prefix="targetfiles-")
373 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800374
375 def unzip_to_dir(filename, dirname):
376 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
377 if pattern is not None:
378 cmd.append(pattern)
379 p = Run(cmd, stdout=subprocess.PIPE)
380 p.communicate()
381 if p.returncode != 0:
382 raise ExternalError("failed to unzip input target-files \"%s\"" %
383 (filename,))
384
385 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
386 if m:
387 unzip_to_dir(m.group(1), tmp)
388 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
389 filename = m.group(1)
390 else:
391 unzip_to_dir(filename, tmp)
392
393 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700394
395
396def GetKeyPasswords(keylist):
397 """Given a list of keys, prompt the user to enter passwords for
398 those which require them. Return a {key: password} dict. password
399 will be None if the key has no password."""
400
Doug Zongker8ce7c252009-05-22 13:34:54 -0700401 no_passwords = []
402 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700403 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700404 devnull = open("/dev/null", "w+b")
405 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800406 # We don't need a password for things that aren't really keys.
407 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700408 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700409 continue
410
T.R. Fullhart37e10522013-03-18 10:31:26 -0700411 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700412 "-inform", "DER", "-nocrypt"],
413 stdin=devnull.fileno(),
414 stdout=devnull.fileno(),
415 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700416 p.communicate()
417 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700418 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700419 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700420 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700421 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
422 "-inform", "DER", "-passin", "pass:"],
423 stdin=devnull.fileno(),
424 stdout=devnull.fileno(),
425 stderr=subprocess.PIPE)
426 stdout, stderr = p.communicate()
427 if p.returncode == 0:
428 # Encrypted key with empty string as password.
429 key_passwords[k] = ''
430 elif stderr.startswith('Error decrypting key'):
431 # Definitely encrypted key.
432 # It would have said "Error reading key" if it didn't parse correctly.
433 need_passwords.append(k)
434 else:
435 # Potentially, a type of key that openssl doesn't understand.
436 # We'll let the routines in signapk.jar handle it.
437 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700438 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700439
T.R. Fullhart37e10522013-03-18 10:31:26 -0700440 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700441 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700442 return key_passwords
443
444
Doug Zongker951495f2009-08-14 12:44:19 -0700445def SignFile(input_name, output_name, key, password, align=None,
446 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700447 """Sign the input_name zip/jar/apk, producing output_name. Use the
448 given key and password (the latter may be None if the key does not
449 have a password.
450
451 If align is an integer > 1, zipalign is run to align stored files in
452 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700453
454 If whole_file is true, use the "-w" option to SignApk to embed a
455 signature that covers the whole file in the archive comment of the
456 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700457 """
Doug Zongker951495f2009-08-14 12:44:19 -0700458
Doug Zongkereef39442009-04-02 12:14:19 -0700459 if align == 0 or align == 1:
460 align = None
461
462 if align:
463 temp = tempfile.NamedTemporaryFile()
464 sign_name = temp.name
465 else:
466 sign_name = output_name
467
T.R. Fullhart37e10522013-03-18 10:31:26 -0700468 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
469 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
470 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700471 if whole_file:
472 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700473 cmd.extend([key + OPTIONS.public_key_suffix,
474 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700475 input_name, sign_name])
476
477 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700478 if password is not None:
479 password += "\n"
480 p.communicate(password)
481 if p.returncode != 0:
482 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
483
484 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700485 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700486 p.communicate()
487 if p.returncode != 0:
488 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
489 temp.close()
490
491
Doug Zongker37974732010-09-16 17:44:38 -0700492def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700493 """Check the data string passed against the max size limit, if
494 any, for the given target. Raise exception if the data is too big.
495 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700496
Doug Zongker1684d9c2010-09-17 07:44:38 -0700497 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700498 mount_point = "/" + target
499
500 if info_dict["fstab"]:
501 if mount_point == "/userdata": mount_point = "/data"
502 p = info_dict["fstab"][mount_point]
503 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800504 device = p.device
505 if "/" in device:
506 device = device[device.rfind("/")+1:]
507 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700508 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700509
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700510 if fs_type == "yaffs2":
511 # image size should be increased by 1/64th to account for the
512 # spare area (64 bytes per 2k page)
513 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800514 size = len(data)
515 pct = float(size) * 100.0 / limit
516 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
517 if pct >= 99.0:
518 raise ExternalError(msg)
519 elif pct >= 95.0:
520 print
521 print " WARNING: ", msg
522 print
523 elif OPTIONS.verbose:
524 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700525
526
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800527def ReadApkCerts(tf_zip):
528 """Given a target_files ZipFile, parse the META/apkcerts.txt file
529 and return a {package: cert} dict."""
530 certmap = {}
531 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
532 line = line.strip()
533 if not line: continue
534 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
535 r'private_key="(.*)"$', line)
536 if m:
537 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700538 public_key_suffix_len = len(OPTIONS.public_key_suffix)
539 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800540 if cert in SPECIAL_CERT_STRINGS and not privkey:
541 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700542 elif (cert.endswith(OPTIONS.public_key_suffix) and
543 privkey.endswith(OPTIONS.private_key_suffix) and
544 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
545 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800546 else:
547 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
548 return certmap
549
550
Doug Zongkereef39442009-04-02 12:14:19 -0700551COMMON_DOCSTRING = """
552 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700553 Prepend <dir>/bin to the list of places to search for binaries
554 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700555
Doug Zongker05d3dea2009-06-22 11:32:31 -0700556 -s (--device_specific) <file>
557 Path to the python module containing device-specific
558 releasetools code.
559
Doug Zongker8bec09e2009-11-30 15:37:14 -0800560 -x (--extra) <key=value>
561 Add a key/value pair to the 'extras' dict, which device-specific
562 extension code may look at.
563
Doug Zongkereef39442009-04-02 12:14:19 -0700564 -v (--verbose)
565 Show command lines being executed.
566
567 -h (--help)
568 Display this usage message and exit.
569"""
570
571def Usage(docstring):
572 print docstring.rstrip("\n")
573 print COMMON_DOCSTRING
574
575
576def ParseOptions(argv,
577 docstring,
578 extra_opts="", extra_long_opts=(),
579 extra_option_handler=None):
580 """Parse the options in argv and return any arguments that aren't
581 flags. docstring is the calling module's docstring, to be displayed
582 for errors and -h. extra_opts and extra_long_opts are for flags
583 defined by the caller, which are processed by passing them to
584 extra_option_handler."""
585
586 try:
587 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800588 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700589 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
590 "java_path=", "public_key_suffix=", "private_key_suffix=",
591 "device_specific=", "extra="] +
592 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700593 except getopt.GetoptError, err:
594 Usage(docstring)
595 print "**", str(err), "**"
596 sys.exit(2)
597
598 path_specified = False
599
600 for o, a in opts:
601 if o in ("-h", "--help"):
602 Usage(docstring)
603 sys.exit()
604 elif o in ("-v", "--verbose"):
605 OPTIONS.verbose = True
606 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700607 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700608 elif o in ("--signapk_path",):
609 OPTIONS.signapk_path = a
610 elif o in ("--extra_signapk_args",):
611 OPTIONS.extra_signapk_args = shlex.split(a)
612 elif o in ("--java_path",):
613 OPTIONS.java_path = a
614 elif o in ("--public_key_suffix",):
615 OPTIONS.public_key_suffix = a
616 elif o in ("--private_key_suffix",):
617 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700618 elif o in ("-s", "--device_specific"):
619 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800620 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800621 key, value = a.split("=", 1)
622 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700623 else:
624 if extra_option_handler is None or not extra_option_handler(o, a):
625 assert False, "unknown option \"%s\"" % (o,)
626
Doug Zongker602a84e2009-06-18 08:35:12 -0700627 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
628 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700629
630 return args
631
632
633def Cleanup():
634 for i in OPTIONS.tempfiles:
635 if os.path.isdir(i):
636 shutil.rmtree(i)
637 else:
638 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700639
640
641class PasswordManager(object):
642 def __init__(self):
643 self.editor = os.getenv("EDITOR", None)
644 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
645
646 def GetPasswords(self, items):
647 """Get passwords corresponding to each string in 'items',
648 returning a dict. (The dict may have keys in addition to the
649 values in 'items'.)
650
651 Uses the passwords in $ANDROID_PW_FILE if available, letting the
652 user edit that file to add more needed passwords. If no editor is
653 available, or $ANDROID_PW_FILE isn't define, prompts the user
654 interactively in the ordinary way.
655 """
656
657 current = self.ReadFile()
658
659 first = True
660 while True:
661 missing = []
662 for i in items:
663 if i not in current or not current[i]:
664 missing.append(i)
665 # Are all the passwords already in the file?
666 if not missing: return current
667
668 for i in missing:
669 current[i] = ""
670
671 if not first:
672 print "key file %s still missing some passwords." % (self.pwfile,)
673 answer = raw_input("try to edit again? [y]> ").strip()
674 if answer and answer[0] not in 'yY':
675 raise RuntimeError("key passwords unavailable")
676 first = False
677
678 current = self.UpdateAndReadFile(current)
679
680 def PromptResult(self, current):
681 """Prompt the user to enter a value (password) for each key in
682 'current' whose value is fales. Returns a new dict with all the
683 values.
684 """
685 result = {}
686 for k, v in sorted(current.iteritems()):
687 if v:
688 result[k] = v
689 else:
690 while True:
691 result[k] = getpass.getpass("Enter password for %s key> "
692 % (k,)).strip()
693 if result[k]: break
694 return result
695
696 def UpdateAndReadFile(self, current):
697 if not self.editor or not self.pwfile:
698 return self.PromptResult(current)
699
700 f = open(self.pwfile, "w")
701 os.chmod(self.pwfile, 0600)
702 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
703 f.write("# (Additional spaces are harmless.)\n\n")
704
705 first_line = None
706 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
707 sorted.sort()
708 for i, (_, k, v) in enumerate(sorted):
709 f.write("[[[ %s ]]] %s\n" % (v, k))
710 if not v and first_line is None:
711 # position cursor on first line with no password.
712 first_line = i + 4
713 f.close()
714
715 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
716 _, _ = p.communicate()
717
718 return self.ReadFile()
719
720 def ReadFile(self):
721 result = {}
722 if self.pwfile is None: return result
723 try:
724 f = open(self.pwfile, "r")
725 for line in f:
726 line = line.strip()
727 if not line or line[0] == '#': continue
728 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
729 if not m:
730 print "failed to parse password file: ", line
731 else:
732 result[m.group(2)] = m.group(1)
733 f.close()
734 except IOError, e:
735 if e.errno != errno.ENOENT:
736 print "error reading password file: ", str(e)
737 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700738
739
740def ZipWriteStr(zip, filename, data, perms=0644):
741 # use a fixed timestamp so the output is repeatable.
742 zinfo = zipfile.ZipInfo(filename=filename,
743 date_time=(2009, 1, 1, 0, 0, 0))
744 zinfo.compress_type = zip.compression
745 zinfo.external_attr = perms << 16
746 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700747
748
749class DeviceSpecificParams(object):
750 module = None
751 def __init__(self, **kwargs):
752 """Keyword arguments to the constructor become attributes of this
753 object, which is passed to all functions in the device-specific
754 module."""
755 for k, v in kwargs.iteritems():
756 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800757 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700758
759 if self.module is None:
760 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700761 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700762 try:
763 if os.path.isdir(path):
764 info = imp.find_module("releasetools", [path])
765 else:
766 d, f = os.path.split(path)
767 b, x = os.path.splitext(f)
768 if x == ".py":
769 f = b
770 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800771 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700772 self.module = imp.load_module("device_specific", *info)
773 except ImportError:
774 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700775
776 def _DoCall(self, function_name, *args, **kwargs):
777 """Call the named function in the device-specific module, passing
778 the given args and kwargs. The first argument to the call will be
779 the DeviceSpecific object itself. If there is no module, or the
780 module does not define the function, return the value of the
781 'default' kwarg (which itself defaults to None)."""
782 if self.module is None or not hasattr(self.module, function_name):
783 return kwargs.get("default", None)
784 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
785
786 def FullOTA_Assertions(self):
787 """Called after emitting the block of assertions at the top of a
788 full OTA package. Implementations can add whatever additional
789 assertions they like."""
790 return self._DoCall("FullOTA_Assertions")
791
Doug Zongkere5ff5902012-01-17 10:55:37 -0800792 def FullOTA_InstallBegin(self):
793 """Called at the start of full OTA installation."""
794 return self._DoCall("FullOTA_InstallBegin")
795
Doug Zongker05d3dea2009-06-22 11:32:31 -0700796 def FullOTA_InstallEnd(self):
797 """Called at the end of full OTA installation; typically this is
798 used to install the image for the device's baseband processor."""
799 return self._DoCall("FullOTA_InstallEnd")
800
801 def IncrementalOTA_Assertions(self):
802 """Called after emitting the block of assertions at the top of an
803 incremental OTA package. Implementations can add whatever
804 additional assertions they like."""
805 return self._DoCall("IncrementalOTA_Assertions")
806
Doug Zongkere5ff5902012-01-17 10:55:37 -0800807 def IncrementalOTA_VerifyBegin(self):
808 """Called at the start of the verification phase of incremental
809 OTA installation; additional checks can be placed here to abort
810 the script before any changes are made."""
811 return self._DoCall("IncrementalOTA_VerifyBegin")
812
Doug Zongker05d3dea2009-06-22 11:32:31 -0700813 def IncrementalOTA_VerifyEnd(self):
814 """Called at the end of the verification phase of incremental OTA
815 installation; additional checks can be placed here to abort the
816 script before any changes are made."""
817 return self._DoCall("IncrementalOTA_VerifyEnd")
818
Doug Zongkere5ff5902012-01-17 10:55:37 -0800819 def IncrementalOTA_InstallBegin(self):
820 """Called at the start of incremental OTA installation (after
821 verification is complete)."""
822 return self._DoCall("IncrementalOTA_InstallBegin")
823
Doug Zongker05d3dea2009-06-22 11:32:31 -0700824 def IncrementalOTA_InstallEnd(self):
825 """Called at the end of incremental OTA installation; typically
826 this is used to install the image for the device's baseband
827 processor."""
828 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700829
830class File(object):
831 def __init__(self, name, data):
832 self.name = name
833 self.data = data
834 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800835 self.sha1 = sha1(data).hexdigest()
836
837 @classmethod
838 def FromLocalFile(cls, name, diskname):
839 f = open(diskname, "rb")
840 data = f.read()
841 f.close()
842 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700843
844 def WriteToTemp(self):
845 t = tempfile.NamedTemporaryFile()
846 t.write(self.data)
847 t.flush()
848 return t
849
850 def AddToZip(self, z):
851 ZipWriteStr(z, self.name, self.data)
852
853DIFF_PROGRAM_BY_EXT = {
854 ".gz" : "imgdiff",
855 ".zip" : ["imgdiff", "-z"],
856 ".jar" : ["imgdiff", "-z"],
857 ".apk" : ["imgdiff", "-z"],
858 ".img" : "imgdiff",
859 }
860
861class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700862 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700863 self.tf = tf
864 self.sf = sf
865 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700866 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700867
868 def ComputePatch(self):
869 """Compute the patch (as a string of data) needed to turn sf into
870 tf. Returns the same tuple as GetPatch()."""
871
872 tf = self.tf
873 sf = self.sf
874
Doug Zongker24cd2802012-08-14 16:36:15 -0700875 if self.diff_program:
876 diff_program = self.diff_program
877 else:
878 ext = os.path.splitext(tf.name)[1]
879 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700880
881 ttemp = tf.WriteToTemp()
882 stemp = sf.WriteToTemp()
883
884 ext = os.path.splitext(tf.name)[1]
885
886 try:
887 ptemp = tempfile.NamedTemporaryFile()
888 if isinstance(diff_program, list):
889 cmd = copy.copy(diff_program)
890 else:
891 cmd = [diff_program]
892 cmd.append(stemp.name)
893 cmd.append(ttemp.name)
894 cmd.append(ptemp.name)
895 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
896 _, err = p.communicate()
897 if err or p.returncode != 0:
898 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
899 return None
900 diff = ptemp.read()
901 finally:
902 ptemp.close()
903 stemp.close()
904 ttemp.close()
905
906 self.patch = diff
907 return self.tf, self.sf, self.patch
908
909
910 def GetPatch(self):
911 """Return a tuple (target_file, source_file, patch_data).
912 patch_data may be None if ComputePatch hasn't been called, or if
913 computing the patch failed."""
914 return self.tf, self.sf, self.patch
915
916
917def ComputeDifferences(diffs):
918 """Call ComputePatch on all the Difference objects in 'diffs'."""
919 print len(diffs), "diffs to compute"
920
921 # Do the largest files first, to try and reduce the long-pole effect.
922 by_size = [(i.tf.size, i) for i in diffs]
923 by_size.sort(reverse=True)
924 by_size = [i[1] for i in by_size]
925
926 lock = threading.Lock()
927 diff_iter = iter(by_size) # accessed under lock
928
929 def worker():
930 try:
931 lock.acquire()
932 for d in diff_iter:
933 lock.release()
934 start = time.time()
935 d.ComputePatch()
936 dur = time.time() - start
937 lock.acquire()
938
939 tf, sf, patch = d.GetPatch()
940 if sf.name == tf.name:
941 name = tf.name
942 else:
943 name = "%s (%s)" % (tf.name, sf.name)
944 if patch is None:
945 print "patching failed! %s" % (name,)
946 else:
947 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
948 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
949 lock.release()
950 except Exception, e:
951 print e
952 raise
953
954 # start worker threads; wait for them all to finish.
955 threads = [threading.Thread(target=worker)
956 for i in range(OPTIONS.worker_threads)]
957 for th in threads:
958 th.start()
959 while threads:
960 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700961
962
963# map recovery.fstab's fs_types to mount/format "partition types"
964PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
965 "ext4": "EMMC", "emmc": "EMMC" }
966
967def GetTypeAndDevice(mount_point, info):
968 fstab = info["fstab"]
969 if fstab:
970 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
971 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800972 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +0000973
974
975def ParseCertificate(data):
976 """Parse a PEM-format certificate."""
977 cert = []
978 save = False
979 for line in data.split("\n"):
980 if "--END CERTIFICATE--" in line:
981 break
982 if save:
983 cert.append(line)
984 if "--BEGIN CERTIFICATE--" in line:
985 save = True
986 cert = "".join(cert).decode('base64')
987 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -0800988
989
990def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img):
991 """Generate a binary patch that creates the recovery image starting
992 with the boot image. (Most of the space in these images is just the
993 kernel, which is identical for the two, so the resulting patch
994 should be efficient.) Add it to the output zip, along with a shell
995 script that is run from init.rc on first boot to actually do the
996 patching and install the new recovery image.
997
998 recovery_img and boot_img should be File objects for the
999 corresponding images. info should be the dictionary returned by
1000 common.LoadInfoDict() on the input target_files.
1001 """
1002
1003 diff_program = ["imgdiff"]
1004 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1005 if os.path.exists(path):
1006 diff_program.append("-b")
1007 diff_program.append(path)
1008 bonus_args = "-b /system/etc/recovery-resource.dat"
1009 else:
1010 bonus_args = ""
1011
1012 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1013 _, _, patch = d.ComputePatch()
1014 output_sink("recovery-from-boot.p", patch)
1015
1016 boot_type, boot_device = GetTypeAndDevice("/boot", OPTIONS.info_dict)
1017 recovery_type, recovery_device = GetTypeAndDevice("/recovery", OPTIONS.info_dict)
1018
1019 sh = """#!/system/bin/sh
1020if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1021 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"
1022else
1023 log -t recovery "Recovery image already installed"
1024fi
1025""" % { 'boot_size': boot_img.size,
1026 'boot_sha1': boot_img.sha1,
1027 'recovery_size': recovery_img.size,
1028 'recovery_sha1': recovery_img.sha1,
1029 'boot_type': boot_type,
1030 'boot_device': boot_device,
1031 'recovery_type': recovery_type,
1032 'recovery_device': recovery_device,
1033 'bonus_args': bonus_args,
1034 }
1035
1036 # The install script location moved from /system/etc to /system/bin
1037 # in the L release. Parse the init.rc file to find out where the
1038 # target-files expects it to be, and put it there.
1039 sh_location = "etc/install-recovery.sh"
1040 try:
1041 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1042 for line in f:
1043 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1044 if m:
1045 sh_location = m.group(1)
1046 print "putting script in", sh_location
1047 break
1048 except (OSError, IOError), e:
1049 print "failed to read init.rc: %s" % (e,)
1050
1051 output_sink(sh_location, sh)