blob: adbd32d771c8ebe8db518c12e1fe9d9d8ca366a5 [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"
Ying Wangd89ffa82014-02-05 11:28:51 -0800357 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
358 os.path.join(unpack_dir, fs_config),
Ying Wang64a55ba2014-02-05 12:15:06 -0800359 info_dict)
Ying Wangd89ffa82014-02-05 11:28:51 -0800360 if data:
361 return File(name, data)
362 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800363
Doug Zongkereef39442009-04-02 12:14:19 -0700364
Doug Zongker75f17362009-12-08 13:46:44 -0800365def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800366 """Unzip the given archive into a temporary directory and return the name.
367
368 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
369 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
370
371 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
372 main file), open for reading.
373 """
Doug Zongkereef39442009-04-02 12:14:19 -0700374
375 tmp = tempfile.mkdtemp(prefix="targetfiles-")
376 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800377
378 def unzip_to_dir(filename, dirname):
379 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
380 if pattern is not None:
381 cmd.append(pattern)
382 p = Run(cmd, stdout=subprocess.PIPE)
383 p.communicate()
384 if p.returncode != 0:
385 raise ExternalError("failed to unzip input target-files \"%s\"" %
386 (filename,))
387
388 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
389 if m:
390 unzip_to_dir(m.group(1), tmp)
391 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
392 filename = m.group(1)
393 else:
394 unzip_to_dir(filename, tmp)
395
396 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700397
398
399def GetKeyPasswords(keylist):
400 """Given a list of keys, prompt the user to enter passwords for
401 those which require them. Return a {key: password} dict. password
402 will be None if the key has no password."""
403
Doug Zongker8ce7c252009-05-22 13:34:54 -0700404 no_passwords = []
405 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700406 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700407 devnull = open("/dev/null", "w+b")
408 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800409 # We don't need a password for things that aren't really keys.
410 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700411 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700412 continue
413
T.R. Fullhart37e10522013-03-18 10:31:26 -0700414 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700415 "-inform", "DER", "-nocrypt"],
416 stdin=devnull.fileno(),
417 stdout=devnull.fileno(),
418 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700419 p.communicate()
420 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700421 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700422 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700423 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700424 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
425 "-inform", "DER", "-passin", "pass:"],
426 stdin=devnull.fileno(),
427 stdout=devnull.fileno(),
428 stderr=subprocess.PIPE)
429 stdout, stderr = p.communicate()
430 if p.returncode == 0:
431 # Encrypted key with empty string as password.
432 key_passwords[k] = ''
433 elif stderr.startswith('Error decrypting key'):
434 # Definitely encrypted key.
435 # It would have said "Error reading key" if it didn't parse correctly.
436 need_passwords.append(k)
437 else:
438 # Potentially, a type of key that openssl doesn't understand.
439 # We'll let the routines in signapk.jar handle it.
440 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700441 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700442
T.R. Fullhart37e10522013-03-18 10:31:26 -0700443 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700444 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700445 return key_passwords
446
447
Doug Zongker951495f2009-08-14 12:44:19 -0700448def SignFile(input_name, output_name, key, password, align=None,
449 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700450 """Sign the input_name zip/jar/apk, producing output_name. Use the
451 given key and password (the latter may be None if the key does not
452 have a password.
453
454 If align is an integer > 1, zipalign is run to align stored files in
455 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700456
457 If whole_file is true, use the "-w" option to SignApk to embed a
458 signature that covers the whole file in the archive comment of the
459 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700460 """
Doug Zongker951495f2009-08-14 12:44:19 -0700461
Doug Zongkereef39442009-04-02 12:14:19 -0700462 if align == 0 or align == 1:
463 align = None
464
465 if align:
466 temp = tempfile.NamedTemporaryFile()
467 sign_name = temp.name
468 else:
469 sign_name = output_name
470
T.R. Fullhart37e10522013-03-18 10:31:26 -0700471 cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
472 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
473 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700474 if whole_file:
475 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700476 cmd.extend([key + OPTIONS.public_key_suffix,
477 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700478 input_name, sign_name])
479
480 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700481 if password is not None:
482 password += "\n"
483 p.communicate(password)
484 if p.returncode != 0:
485 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
486
487 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700488 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700489 p.communicate()
490 if p.returncode != 0:
491 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
492 temp.close()
493
494
Doug Zongker37974732010-09-16 17:44:38 -0700495def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700496 """Check the data string passed against the max size limit, if
497 any, for the given target. Raise exception if the data is too big.
498 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700499
Doug Zongker1684d9c2010-09-17 07:44:38 -0700500 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700501 mount_point = "/" + target
502
503 if info_dict["fstab"]:
504 if mount_point == "/userdata": mount_point = "/data"
505 p = info_dict["fstab"][mount_point]
506 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800507 device = p.device
508 if "/" in device:
509 device = device[device.rfind("/")+1:]
510 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700511 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700512
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700513 if fs_type == "yaffs2":
514 # image size should be increased by 1/64th to account for the
515 # spare area (64 bytes per 2k page)
516 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800517 size = len(data)
518 pct = float(size) * 100.0 / limit
519 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
520 if pct >= 99.0:
521 raise ExternalError(msg)
522 elif pct >= 95.0:
523 print
524 print " WARNING: ", msg
525 print
526 elif OPTIONS.verbose:
527 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700528
529
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800530def ReadApkCerts(tf_zip):
531 """Given a target_files ZipFile, parse the META/apkcerts.txt file
532 and return a {package: cert} dict."""
533 certmap = {}
534 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
535 line = line.strip()
536 if not line: continue
537 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
538 r'private_key="(.*)"$', line)
539 if m:
540 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700541 public_key_suffix_len = len(OPTIONS.public_key_suffix)
542 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800543 if cert in SPECIAL_CERT_STRINGS and not privkey:
544 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700545 elif (cert.endswith(OPTIONS.public_key_suffix) and
546 privkey.endswith(OPTIONS.private_key_suffix) and
547 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
548 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800549 else:
550 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
551 return certmap
552
553
Doug Zongkereef39442009-04-02 12:14:19 -0700554COMMON_DOCSTRING = """
555 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700556 Prepend <dir>/bin to the list of places to search for binaries
557 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700558
Doug Zongker05d3dea2009-06-22 11:32:31 -0700559 -s (--device_specific) <file>
560 Path to the python module containing device-specific
561 releasetools code.
562
Doug Zongker8bec09e2009-11-30 15:37:14 -0800563 -x (--extra) <key=value>
564 Add a key/value pair to the 'extras' dict, which device-specific
565 extension code may look at.
566
Doug Zongkereef39442009-04-02 12:14:19 -0700567 -v (--verbose)
568 Show command lines being executed.
569
570 -h (--help)
571 Display this usage message and exit.
572"""
573
574def Usage(docstring):
575 print docstring.rstrip("\n")
576 print COMMON_DOCSTRING
577
578
579def ParseOptions(argv,
580 docstring,
581 extra_opts="", extra_long_opts=(),
582 extra_option_handler=None):
583 """Parse the options in argv and return any arguments that aren't
584 flags. docstring is the calling module's docstring, to be displayed
585 for errors and -h. extra_opts and extra_long_opts are for flags
586 defined by the caller, which are processed by passing them to
587 extra_option_handler."""
588
589 try:
590 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800591 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700592 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
593 "java_path=", "public_key_suffix=", "private_key_suffix=",
594 "device_specific=", "extra="] +
595 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700596 except getopt.GetoptError, err:
597 Usage(docstring)
598 print "**", str(err), "**"
599 sys.exit(2)
600
601 path_specified = False
602
603 for o, a in opts:
604 if o in ("-h", "--help"):
605 Usage(docstring)
606 sys.exit()
607 elif o in ("-v", "--verbose"):
608 OPTIONS.verbose = True
609 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700610 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700611 elif o in ("--signapk_path",):
612 OPTIONS.signapk_path = a
613 elif o in ("--extra_signapk_args",):
614 OPTIONS.extra_signapk_args = shlex.split(a)
615 elif o in ("--java_path",):
616 OPTIONS.java_path = a
617 elif o in ("--public_key_suffix",):
618 OPTIONS.public_key_suffix = a
619 elif o in ("--private_key_suffix",):
620 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700621 elif o in ("-s", "--device_specific"):
622 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800623 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800624 key, value = a.split("=", 1)
625 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700626 else:
627 if extra_option_handler is None or not extra_option_handler(o, a):
628 assert False, "unknown option \"%s\"" % (o,)
629
Doug Zongker602a84e2009-06-18 08:35:12 -0700630 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
631 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700632
633 return args
634
635
636def Cleanup():
637 for i in OPTIONS.tempfiles:
638 if os.path.isdir(i):
639 shutil.rmtree(i)
640 else:
641 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700642
643
644class PasswordManager(object):
645 def __init__(self):
646 self.editor = os.getenv("EDITOR", None)
647 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
648
649 def GetPasswords(self, items):
650 """Get passwords corresponding to each string in 'items',
651 returning a dict. (The dict may have keys in addition to the
652 values in 'items'.)
653
654 Uses the passwords in $ANDROID_PW_FILE if available, letting the
655 user edit that file to add more needed passwords. If no editor is
656 available, or $ANDROID_PW_FILE isn't define, prompts the user
657 interactively in the ordinary way.
658 """
659
660 current = self.ReadFile()
661
662 first = True
663 while True:
664 missing = []
665 for i in items:
666 if i not in current or not current[i]:
667 missing.append(i)
668 # Are all the passwords already in the file?
669 if not missing: return current
670
671 for i in missing:
672 current[i] = ""
673
674 if not first:
675 print "key file %s still missing some passwords." % (self.pwfile,)
676 answer = raw_input("try to edit again? [y]> ").strip()
677 if answer and answer[0] not in 'yY':
678 raise RuntimeError("key passwords unavailable")
679 first = False
680
681 current = self.UpdateAndReadFile(current)
682
683 def PromptResult(self, current):
684 """Prompt the user to enter a value (password) for each key in
685 'current' whose value is fales. Returns a new dict with all the
686 values.
687 """
688 result = {}
689 for k, v in sorted(current.iteritems()):
690 if v:
691 result[k] = v
692 else:
693 while True:
694 result[k] = getpass.getpass("Enter password for %s key> "
695 % (k,)).strip()
696 if result[k]: break
697 return result
698
699 def UpdateAndReadFile(self, current):
700 if not self.editor or not self.pwfile:
701 return self.PromptResult(current)
702
703 f = open(self.pwfile, "w")
704 os.chmod(self.pwfile, 0600)
705 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
706 f.write("# (Additional spaces are harmless.)\n\n")
707
708 first_line = None
709 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
710 sorted.sort()
711 for i, (_, k, v) in enumerate(sorted):
712 f.write("[[[ %s ]]] %s\n" % (v, k))
713 if not v and first_line is None:
714 # position cursor on first line with no password.
715 first_line = i + 4
716 f.close()
717
718 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
719 _, _ = p.communicate()
720
721 return self.ReadFile()
722
723 def ReadFile(self):
724 result = {}
725 if self.pwfile is None: return result
726 try:
727 f = open(self.pwfile, "r")
728 for line in f:
729 line = line.strip()
730 if not line or line[0] == '#': continue
731 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
732 if not m:
733 print "failed to parse password file: ", line
734 else:
735 result[m.group(2)] = m.group(1)
736 f.close()
737 except IOError, e:
738 if e.errno != errno.ENOENT:
739 print "error reading password file: ", str(e)
740 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700741
742
743def ZipWriteStr(zip, filename, data, perms=0644):
744 # use a fixed timestamp so the output is repeatable.
745 zinfo = zipfile.ZipInfo(filename=filename,
746 date_time=(2009, 1, 1, 0, 0, 0))
747 zinfo.compress_type = zip.compression
748 zinfo.external_attr = perms << 16
749 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700750
751
752class DeviceSpecificParams(object):
753 module = None
754 def __init__(self, **kwargs):
755 """Keyword arguments to the constructor become attributes of this
756 object, which is passed to all functions in the device-specific
757 module."""
758 for k, v in kwargs.iteritems():
759 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800760 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700761
762 if self.module is None:
763 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700764 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700765 try:
766 if os.path.isdir(path):
767 info = imp.find_module("releasetools", [path])
768 else:
769 d, f = os.path.split(path)
770 b, x = os.path.splitext(f)
771 if x == ".py":
772 f = b
773 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800774 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700775 self.module = imp.load_module("device_specific", *info)
776 except ImportError:
777 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700778
779 def _DoCall(self, function_name, *args, **kwargs):
780 """Call the named function in the device-specific module, passing
781 the given args and kwargs. The first argument to the call will be
782 the DeviceSpecific object itself. If there is no module, or the
783 module does not define the function, return the value of the
784 'default' kwarg (which itself defaults to None)."""
785 if self.module is None or not hasattr(self.module, function_name):
786 return kwargs.get("default", None)
787 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
788
789 def FullOTA_Assertions(self):
790 """Called after emitting the block of assertions at the top of a
791 full OTA package. Implementations can add whatever additional
792 assertions they like."""
793 return self._DoCall("FullOTA_Assertions")
794
Doug Zongkere5ff5902012-01-17 10:55:37 -0800795 def FullOTA_InstallBegin(self):
796 """Called at the start of full OTA installation."""
797 return self._DoCall("FullOTA_InstallBegin")
798
Doug Zongker05d3dea2009-06-22 11:32:31 -0700799 def FullOTA_InstallEnd(self):
800 """Called at the end of full OTA installation; typically this is
801 used to install the image for the device's baseband processor."""
802 return self._DoCall("FullOTA_InstallEnd")
803
804 def IncrementalOTA_Assertions(self):
805 """Called after emitting the block of assertions at the top of an
806 incremental OTA package. Implementations can add whatever
807 additional assertions they like."""
808 return self._DoCall("IncrementalOTA_Assertions")
809
Doug Zongkere5ff5902012-01-17 10:55:37 -0800810 def IncrementalOTA_VerifyBegin(self):
811 """Called at the start of the verification phase of incremental
812 OTA installation; additional checks can be placed here to abort
813 the script before any changes are made."""
814 return self._DoCall("IncrementalOTA_VerifyBegin")
815
Doug Zongker05d3dea2009-06-22 11:32:31 -0700816 def IncrementalOTA_VerifyEnd(self):
817 """Called at the end of the verification phase of incremental OTA
818 installation; additional checks can be placed here to abort the
819 script before any changes are made."""
820 return self._DoCall("IncrementalOTA_VerifyEnd")
821
Doug Zongkere5ff5902012-01-17 10:55:37 -0800822 def IncrementalOTA_InstallBegin(self):
823 """Called at the start of incremental OTA installation (after
824 verification is complete)."""
825 return self._DoCall("IncrementalOTA_InstallBegin")
826
Doug Zongker05d3dea2009-06-22 11:32:31 -0700827 def IncrementalOTA_InstallEnd(self):
828 """Called at the end of incremental OTA installation; typically
829 this is used to install the image for the device's baseband
830 processor."""
831 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700832
833class File(object):
834 def __init__(self, name, data):
835 self.name = name
836 self.data = data
837 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800838 self.sha1 = sha1(data).hexdigest()
839
840 @classmethod
841 def FromLocalFile(cls, name, diskname):
842 f = open(diskname, "rb")
843 data = f.read()
844 f.close()
845 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700846
847 def WriteToTemp(self):
848 t = tempfile.NamedTemporaryFile()
849 t.write(self.data)
850 t.flush()
851 return t
852
853 def AddToZip(self, z):
854 ZipWriteStr(z, self.name, self.data)
855
856DIFF_PROGRAM_BY_EXT = {
857 ".gz" : "imgdiff",
858 ".zip" : ["imgdiff", "-z"],
859 ".jar" : ["imgdiff", "-z"],
860 ".apk" : ["imgdiff", "-z"],
861 ".img" : "imgdiff",
862 }
863
864class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700865 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700866 self.tf = tf
867 self.sf = sf
868 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700869 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700870
871 def ComputePatch(self):
872 """Compute the patch (as a string of data) needed to turn sf into
873 tf. Returns the same tuple as GetPatch()."""
874
875 tf = self.tf
876 sf = self.sf
877
Doug Zongker24cd2802012-08-14 16:36:15 -0700878 if self.diff_program:
879 diff_program = self.diff_program
880 else:
881 ext = os.path.splitext(tf.name)[1]
882 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700883
884 ttemp = tf.WriteToTemp()
885 stemp = sf.WriteToTemp()
886
887 ext = os.path.splitext(tf.name)[1]
888
889 try:
890 ptemp = tempfile.NamedTemporaryFile()
891 if isinstance(diff_program, list):
892 cmd = copy.copy(diff_program)
893 else:
894 cmd = [diff_program]
895 cmd.append(stemp.name)
896 cmd.append(ttemp.name)
897 cmd.append(ptemp.name)
898 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
899 _, err = p.communicate()
900 if err or p.returncode != 0:
901 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
902 return None
903 diff = ptemp.read()
904 finally:
905 ptemp.close()
906 stemp.close()
907 ttemp.close()
908
909 self.patch = diff
910 return self.tf, self.sf, self.patch
911
912
913 def GetPatch(self):
914 """Return a tuple (target_file, source_file, patch_data).
915 patch_data may be None if ComputePatch hasn't been called, or if
916 computing the patch failed."""
917 return self.tf, self.sf, self.patch
918
919
920def ComputeDifferences(diffs):
921 """Call ComputePatch on all the Difference objects in 'diffs'."""
922 print len(diffs), "diffs to compute"
923
924 # Do the largest files first, to try and reduce the long-pole effect.
925 by_size = [(i.tf.size, i) for i in diffs]
926 by_size.sort(reverse=True)
927 by_size = [i[1] for i in by_size]
928
929 lock = threading.Lock()
930 diff_iter = iter(by_size) # accessed under lock
931
932 def worker():
933 try:
934 lock.acquire()
935 for d in diff_iter:
936 lock.release()
937 start = time.time()
938 d.ComputePatch()
939 dur = time.time() - start
940 lock.acquire()
941
942 tf, sf, patch = d.GetPatch()
943 if sf.name == tf.name:
944 name = tf.name
945 else:
946 name = "%s (%s)" % (tf.name, sf.name)
947 if patch is None:
948 print "patching failed! %s" % (name,)
949 else:
950 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
951 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
952 lock.release()
953 except Exception, e:
954 print e
955 raise
956
957 # start worker threads; wait for them all to finish.
958 threads = [threading.Thread(target=worker)
959 for i in range(OPTIONS.worker_threads)]
960 for th in threads:
961 th.start()
962 while threads:
963 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700964
965
966# map recovery.fstab's fs_types to mount/format "partition types"
967PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
968 "ext4": "EMMC", "emmc": "EMMC" }
969
970def GetTypeAndDevice(mount_point, info):
971 fstab = info["fstab"]
972 if fstab:
973 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
974 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800975 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +0000976
977
978def ParseCertificate(data):
979 """Parse a PEM-format certificate."""
980 cert = []
981 save = False
982 for line in data.split("\n"):
983 if "--END CERTIFICATE--" in line:
984 break
985 if save:
986 cert.append(line)
987 if "--BEGIN CERTIFICATE--" in line:
988 save = True
989 cert = "".join(cert).decode('base64')
990 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -0800991
992
Doug Zongker412c02f2014-02-13 10:58:24 -0800993def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
994 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -0800995 """Generate a binary patch that creates the recovery image starting
996 with the boot image. (Most of the space in these images is just the
997 kernel, which is identical for the two, so the resulting patch
998 should be efficient.) Add it to the output zip, along with a shell
999 script that is run from init.rc on first boot to actually do the
1000 patching and install the new recovery image.
1001
1002 recovery_img and boot_img should be File objects for the
1003 corresponding images. info should be the dictionary returned by
1004 common.LoadInfoDict() on the input target_files.
1005 """
1006
Doug Zongker412c02f2014-02-13 10:58:24 -08001007 if info_dict is None:
1008 info_dict = OPTIONS.info_dict
1009
Doug Zongkerc9253822014-02-04 12:17:58 -08001010 diff_program = ["imgdiff"]
1011 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1012 if os.path.exists(path):
1013 diff_program.append("-b")
1014 diff_program.append(path)
1015 bonus_args = "-b /system/etc/recovery-resource.dat"
1016 else:
1017 bonus_args = ""
1018
1019 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1020 _, _, patch = d.ComputePatch()
1021 output_sink("recovery-from-boot.p", patch)
1022
Doug Zongker412c02f2014-02-13 10:58:24 -08001023 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1024 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
Doug Zongkerc9253822014-02-04 12:17:58 -08001025
1026 sh = """#!/system/bin/sh
1027if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1028 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"
1029else
1030 log -t recovery "Recovery image already installed"
1031fi
1032""" % { 'boot_size': boot_img.size,
1033 'boot_sha1': boot_img.sha1,
1034 'recovery_size': recovery_img.size,
1035 'recovery_sha1': recovery_img.sha1,
1036 'boot_type': boot_type,
1037 'boot_device': boot_device,
1038 'recovery_type': recovery_type,
1039 'recovery_device': recovery_device,
1040 'bonus_args': bonus_args,
1041 }
1042
1043 # The install script location moved from /system/etc to /system/bin
1044 # in the L release. Parse the init.rc file to find out where the
1045 # target-files expects it to be, and put it there.
1046 sh_location = "etc/install-recovery.sh"
1047 try:
1048 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1049 for line in f:
1050 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1051 if m:
1052 sh_location = m.group(1)
1053 print "putting script in", sh_location
1054 break
1055 except (OSError, IOError), e:
1056 print "failed to read init.rc: %s" % (e,)
1057
1058 output_sink(sh_location, sh)