blob: 8196b3c3643c7ea528ae669b0e502ba5177e7ba8 [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
23import shutil
24import subprocess
25import sys
26import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070027import threading
28import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070029import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070030
Doug Zongker55d93282011-01-25 17:03:34 -080031try:
davidcad0bb92011-03-15 14:21:38 +000032 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080033except ImportError:
davidcad0bb92011-03-15 14:21:38 +000034 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080035
Doug Zongkereef39442009-04-02 12:14:19 -070036# missing in Python 2.4 and before
37if not hasattr(os, "SEEK_SET"):
38 os.SEEK_SET = 0
39
40class Options(object): pass
41OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070042OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070043OPTIONS.verbose = False
44OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070045OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080046OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070047OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070048
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080049
50# Values for "certificate" in apkcerts that mean special things.
51SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
52
53
Doug Zongkereef39442009-04-02 12:14:19 -070054class ExternalError(RuntimeError): pass
55
56
57def Run(args, **kwargs):
58 """Create and return a subprocess.Popen object, printing the command
59 line on the terminal if -v was specified."""
60 if OPTIONS.verbose:
61 print " running: ", " ".join(args)
62 return subprocess.Popen(args, **kwargs)
63
64
Ying Wang7e6d4e42010-12-13 16:25:36 -080065def CloseInheritedPipes():
66 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
67 before doing other work."""
68 if platform.system() != "Darwin":
69 return
70 for d in range(3, 1025):
71 try:
72 stat = os.fstat(d)
73 if stat is not None:
74 pipebit = stat[0] & 0x1000
75 if pipebit != 0:
76 os.close(d)
77 except OSError:
78 pass
79
80
Doug Zongker37974732010-09-16 17:44:38 -070081def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070082 """Read and parse the META/misc_info.txt key/value pairs from the
83 input target files and return a dict."""
84
85 d = {}
86 try:
Doug Zongker37974732010-09-16 17:44:38 -070087 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070088 line = line.strip()
89 if not line or line.startswith("#"): continue
90 k, v = line.split("=", 1)
91 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070092 except KeyError:
93 # ok if misc_info.txt doesn't exist
94 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070095
Doug Zongker37974732010-09-16 17:44:38 -070096 # backwards compatibility: These values used to be in their own
97 # files. Look for them, in case we're processing an old
98 # target_files zip.
99
100 if "mkyaffs2_extra_flags" not in d:
101 try:
102 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
103 except KeyError:
104 # ok if flags don't exist
105 pass
106
107 if "recovery_api_version" not in d:
108 try:
109 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
110 except KeyError:
111 raise ValueError("can't find recovery API version in input target-files")
112
113 if "tool_extensions" not in d:
114 try:
115 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
116 except KeyError:
117 # ok if extensions don't exist
118 pass
119
120 try:
121 data = zip.read("META/imagesizes.txt")
122 for line in data.split("\n"):
123 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700124 name, value = line.split(" ", 1)
125 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700126 if name == "blocksize":
127 d[name] = value
128 else:
129 d[name + "_size"] = value
130 except KeyError:
131 pass
132
133 def makeint(key):
134 if key in d:
135 d[key] = int(d[key], 0)
136
137 makeint("recovery_api_version")
138 makeint("blocksize")
139 makeint("system_size")
140 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700141 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700142 makeint("recovery_size")
143 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700144
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700145 d["fstab"] = LoadRecoveryFSTab(zip)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700146 return d
147
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700148def LoadRecoveryFSTab(zip):
149 class Partition(object):
150 pass
151
152 try:
153 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
154 except KeyError:
Jeff Davidson033fbe22011-10-26 18:08:09 -0700155 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip
156 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700157
158 d = {}
159 for line in data.split("\n"):
160 line = line.strip()
161 if not line or line.startswith("#"): continue
162 pieces = line.split()
163 if not (3 <= len(pieces) <= 4):
164 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
165
166 p = Partition()
167 p.mount_point = pieces[0]
168 p.fs_type = pieces[1]
169 p.device = pieces[2]
Doug Zongker086cbb02011-02-17 15:54:20 -0800170 p.length = 0
171 options = None
172 if len(pieces) >= 4:
173 if pieces[3].startswith("/"):
174 p.device2 = pieces[3]
175 if len(pieces) >= 5:
176 options = pieces[4]
177 else:
178 p.device2 = None
179 options = pieces[3]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700180 else:
181 p.device2 = None
182
Doug Zongker086cbb02011-02-17 15:54:20 -0800183 if options:
184 options = options.split(",")
185 for i in options:
186 if i.startswith("length="):
187 p.length = int(i[7:])
188 else:
189 print "%s: unknown option \"%s\"" % (p.mount_point, i)
190
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700191 d[p.mount_point] = p
192 return d
193
194
Doug Zongker37974732010-09-16 17:44:38 -0700195def DumpInfoDict(d):
196 for k, v in sorted(d.items()):
197 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700198
Doug Zongkereef39442009-04-02 12:14:19 -0700199def BuildBootableImage(sourcedir):
200 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700201 'sourcedir'), and turn them into a boot image. Return the image
202 data, or None if sourcedir does not appear to contains files for
203 building the requested image."""
204
205 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
206 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
207 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700208
209 ramdisk_img = tempfile.NamedTemporaryFile()
210 img = tempfile.NamedTemporaryFile()
211
212 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
213 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700214 p2 = Run(["minigzip"],
215 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700216
217 p2.wait()
218 p1.wait()
219 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700220 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700221
Doug Zongker38a649f2009-06-17 09:07:09 -0700222 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
223
Doug Zongker171f1cd2009-06-15 22:36:37 -0700224 fn = os.path.join(sourcedir, "cmdline")
225 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700226 cmd.append("--cmdline")
227 cmd.append(open(fn).read().rstrip("\n"))
228
229 fn = os.path.join(sourcedir, "base")
230 if os.access(fn, os.F_OK):
231 cmd.append("--base")
232 cmd.append(open(fn).read().rstrip("\n"))
233
Ying Wang4de6b5b2010-08-25 14:29:34 -0700234 fn = os.path.join(sourcedir, "pagesize")
235 if os.access(fn, os.F_OK):
236 cmd.append("--pagesize")
237 cmd.append(open(fn).read().rstrip("\n"))
238
Doug Zongker38a649f2009-06-17 09:07:09 -0700239 cmd.extend(["--ramdisk", ramdisk_img.name,
240 "--output", img.name])
241
242 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700243 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700244 assert p.returncode == 0, "mkbootimg of %s image failed" % (
245 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700246
247 img.seek(os.SEEK_SET, 0)
248 data = img.read()
249
250 ramdisk_img.close()
251 img.close()
252
253 return data
254
255
Doug Zongker55d93282011-01-25 17:03:34 -0800256def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir):
257 """Return a File object (with name 'name') with the desired bootable
258 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
259 'prebuilt_name', otherwise construct it from the source files in
260 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700261
Doug Zongker55d93282011-01-25 17:03:34 -0800262 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
263 if os.path.exists(prebuilt_path):
264 print "using prebuilt %s..." % (prebuilt_name,)
265 return File.FromLocalFile(name, prebuilt_path)
266 else:
267 print "building image from target_files %s..." % (tree_subdir,)
268 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir)))
269
Doug Zongkereef39442009-04-02 12:14:19 -0700270
Doug Zongker75f17362009-12-08 13:46:44 -0800271def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800272 """Unzip the given archive into a temporary directory and return the name.
273
274 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
275 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
276
277 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
278 main file), open for reading.
279 """
Doug Zongkereef39442009-04-02 12:14:19 -0700280
281 tmp = tempfile.mkdtemp(prefix="targetfiles-")
282 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800283
284 def unzip_to_dir(filename, dirname):
285 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
286 if pattern is not None:
287 cmd.append(pattern)
288 p = Run(cmd, stdout=subprocess.PIPE)
289 p.communicate()
290 if p.returncode != 0:
291 raise ExternalError("failed to unzip input target-files \"%s\"" %
292 (filename,))
293
294 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
295 if m:
296 unzip_to_dir(m.group(1), tmp)
297 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
298 filename = m.group(1)
299 else:
300 unzip_to_dir(filename, tmp)
301
302 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700303
304
305def GetKeyPasswords(keylist):
306 """Given a list of keys, prompt the user to enter passwords for
307 those which require them. Return a {key: password} dict. password
308 will be None if the key has no password."""
309
Doug Zongker8ce7c252009-05-22 13:34:54 -0700310 no_passwords = []
311 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700312 devnull = open("/dev/null", "w+b")
313 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800314 # We don't need a password for things that aren't really keys.
315 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700316 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700317 continue
318
Doug Zongker602a84e2009-06-18 08:35:12 -0700319 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
320 "-inform", "DER", "-nocrypt"],
321 stdin=devnull.fileno(),
322 stdout=devnull.fileno(),
323 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700324 p.communicate()
325 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700326 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700327 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700328 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700329 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700330
331 key_passwords = PasswordManager().GetPasswords(need_passwords)
332 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700333 return key_passwords
334
335
Doug Zongker951495f2009-08-14 12:44:19 -0700336def SignFile(input_name, output_name, key, password, align=None,
337 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700338 """Sign the input_name zip/jar/apk, producing output_name. Use the
339 given key and password (the latter may be None if the key does not
340 have a password.
341
342 If align is an integer > 1, zipalign is run to align stored files in
343 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700344
345 If whole_file is true, use the "-w" option to SignApk to embed a
346 signature that covers the whole file in the archive comment of the
347 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700348 """
Doug Zongker951495f2009-08-14 12:44:19 -0700349
Doug Zongkereef39442009-04-02 12:14:19 -0700350 if align == 0 or align == 1:
351 align = None
352
353 if align:
354 temp = tempfile.NamedTemporaryFile()
355 sign_name = temp.name
356 else:
357 sign_name = output_name
358
Doug Zongkerca855e92011-02-23 09:25:01 -0800359 cmd = ["java", "-Xmx2048m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700360 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
361 if whole_file:
362 cmd.append("-w")
363 cmd.extend([key + ".x509.pem", key + ".pk8",
364 input_name, sign_name])
365
366 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700367 if password is not None:
368 password += "\n"
369 p.communicate(password)
370 if p.returncode != 0:
371 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
372
373 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700374 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700375 p.communicate()
376 if p.returncode != 0:
377 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
378 temp.close()
379
380
Doug Zongker37974732010-09-16 17:44:38 -0700381def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700382 """Check the data string passed against the max size limit, if
383 any, for the given target. Raise exception if the data is too big.
384 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700385
Doug Zongker1684d9c2010-09-17 07:44:38 -0700386 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700387 mount_point = "/" + target
388
389 if info_dict["fstab"]:
390 if mount_point == "/userdata": mount_point = "/data"
391 p = info_dict["fstab"][mount_point]
392 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800393 device = p.device
394 if "/" in device:
395 device = device[device.rfind("/")+1:]
396 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700397 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700398
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700399 if fs_type == "yaffs2":
400 # image size should be increased by 1/64th to account for the
401 # spare area (64 bytes per 2k page)
402 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800403 size = len(data)
404 pct = float(size) * 100.0 / limit
405 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
406 if pct >= 99.0:
407 raise ExternalError(msg)
408 elif pct >= 95.0:
409 print
410 print " WARNING: ", msg
411 print
412 elif OPTIONS.verbose:
413 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700414
415
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800416def ReadApkCerts(tf_zip):
417 """Given a target_files ZipFile, parse the META/apkcerts.txt file
418 and return a {package: cert} dict."""
419 certmap = {}
420 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
421 line = line.strip()
422 if not line: continue
423 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
424 r'private_key="(.*)"$', line)
425 if m:
426 name, cert, privkey = m.groups()
427 if cert in SPECIAL_CERT_STRINGS and not privkey:
428 certmap[name] = cert
429 elif (cert.endswith(".x509.pem") and
430 privkey.endswith(".pk8") and
431 cert[:-9] == privkey[:-4]):
432 certmap[name] = cert[:-9]
433 else:
434 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
435 return certmap
436
437
Doug Zongkereef39442009-04-02 12:14:19 -0700438COMMON_DOCSTRING = """
439 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700440 Prepend <dir>/bin to the list of places to search for binaries
441 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700442
Doug Zongker05d3dea2009-06-22 11:32:31 -0700443 -s (--device_specific) <file>
444 Path to the python module containing device-specific
445 releasetools code.
446
Doug Zongker8bec09e2009-11-30 15:37:14 -0800447 -x (--extra) <key=value>
448 Add a key/value pair to the 'extras' dict, which device-specific
449 extension code may look at.
450
Doug Zongkereef39442009-04-02 12:14:19 -0700451 -v (--verbose)
452 Show command lines being executed.
453
454 -h (--help)
455 Display this usage message and exit.
456"""
457
458def Usage(docstring):
459 print docstring.rstrip("\n")
460 print COMMON_DOCSTRING
461
462
463def ParseOptions(argv,
464 docstring,
465 extra_opts="", extra_long_opts=(),
466 extra_option_handler=None):
467 """Parse the options in argv and return any arguments that aren't
468 flags. docstring is the calling module's docstring, to be displayed
469 for errors and -h. extra_opts and extra_long_opts are for flags
470 defined by the caller, which are processed by passing them to
471 extra_option_handler."""
472
473 try:
474 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800475 argv, "hvp:s:x:" + extra_opts,
476 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700477 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700478 except getopt.GetoptError, err:
479 Usage(docstring)
480 print "**", str(err), "**"
481 sys.exit(2)
482
483 path_specified = False
484
485 for o, a in opts:
486 if o in ("-h", "--help"):
487 Usage(docstring)
488 sys.exit()
489 elif o in ("-v", "--verbose"):
490 OPTIONS.verbose = True
491 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700492 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700493 elif o in ("-s", "--device_specific"):
494 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800495 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800496 key, value = a.split("=", 1)
497 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700498 else:
499 if extra_option_handler is None or not extra_option_handler(o, a):
500 assert False, "unknown option \"%s\"" % (o,)
501
Doug Zongker602a84e2009-06-18 08:35:12 -0700502 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
503 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700504
505 return args
506
507
508def Cleanup():
509 for i in OPTIONS.tempfiles:
510 if os.path.isdir(i):
511 shutil.rmtree(i)
512 else:
513 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700514
515
516class PasswordManager(object):
517 def __init__(self):
518 self.editor = os.getenv("EDITOR", None)
519 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
520
521 def GetPasswords(self, items):
522 """Get passwords corresponding to each string in 'items',
523 returning a dict. (The dict may have keys in addition to the
524 values in 'items'.)
525
526 Uses the passwords in $ANDROID_PW_FILE if available, letting the
527 user edit that file to add more needed passwords. If no editor is
528 available, or $ANDROID_PW_FILE isn't define, prompts the user
529 interactively in the ordinary way.
530 """
531
532 current = self.ReadFile()
533
534 first = True
535 while True:
536 missing = []
537 for i in items:
538 if i not in current or not current[i]:
539 missing.append(i)
540 # Are all the passwords already in the file?
541 if not missing: return current
542
543 for i in missing:
544 current[i] = ""
545
546 if not first:
547 print "key file %s still missing some passwords." % (self.pwfile,)
548 answer = raw_input("try to edit again? [y]> ").strip()
549 if answer and answer[0] not in 'yY':
550 raise RuntimeError("key passwords unavailable")
551 first = False
552
553 current = self.UpdateAndReadFile(current)
554
555 def PromptResult(self, current):
556 """Prompt the user to enter a value (password) for each key in
557 'current' whose value is fales. Returns a new dict with all the
558 values.
559 """
560 result = {}
561 for k, v in sorted(current.iteritems()):
562 if v:
563 result[k] = v
564 else:
565 while True:
566 result[k] = getpass.getpass("Enter password for %s key> "
567 % (k,)).strip()
568 if result[k]: break
569 return result
570
571 def UpdateAndReadFile(self, current):
572 if not self.editor or not self.pwfile:
573 return self.PromptResult(current)
574
575 f = open(self.pwfile, "w")
576 os.chmod(self.pwfile, 0600)
577 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
578 f.write("# (Additional spaces are harmless.)\n\n")
579
580 first_line = None
581 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
582 sorted.sort()
583 for i, (_, k, v) in enumerate(sorted):
584 f.write("[[[ %s ]]] %s\n" % (v, k))
585 if not v and first_line is None:
586 # position cursor on first line with no password.
587 first_line = i + 4
588 f.close()
589
590 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
591 _, _ = p.communicate()
592
593 return self.ReadFile()
594
595 def ReadFile(self):
596 result = {}
597 if self.pwfile is None: return result
598 try:
599 f = open(self.pwfile, "r")
600 for line in f:
601 line = line.strip()
602 if not line or line[0] == '#': continue
603 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
604 if not m:
605 print "failed to parse password file: ", line
606 else:
607 result[m.group(2)] = m.group(1)
608 f.close()
609 except IOError, e:
610 if e.errno != errno.ENOENT:
611 print "error reading password file: ", str(e)
612 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700613
614
615def ZipWriteStr(zip, filename, data, perms=0644):
616 # use a fixed timestamp so the output is repeatable.
617 zinfo = zipfile.ZipInfo(filename=filename,
618 date_time=(2009, 1, 1, 0, 0, 0))
619 zinfo.compress_type = zip.compression
620 zinfo.external_attr = perms << 16
621 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700622
623
624class DeviceSpecificParams(object):
625 module = None
626 def __init__(self, **kwargs):
627 """Keyword arguments to the constructor become attributes of this
628 object, which is passed to all functions in the device-specific
629 module."""
630 for k, v in kwargs.iteritems():
631 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800632 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700633
634 if self.module is None:
635 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700636 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700637 try:
638 if os.path.isdir(path):
639 info = imp.find_module("releasetools", [path])
640 else:
641 d, f = os.path.split(path)
642 b, x = os.path.splitext(f)
643 if x == ".py":
644 f = b
645 info = imp.find_module(f, [d])
646 self.module = imp.load_module("device_specific", *info)
647 except ImportError:
648 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700649
650 def _DoCall(self, function_name, *args, **kwargs):
651 """Call the named function in the device-specific module, passing
652 the given args and kwargs. The first argument to the call will be
653 the DeviceSpecific object itself. If there is no module, or the
654 module does not define the function, return the value of the
655 'default' kwarg (which itself defaults to None)."""
656 if self.module is None or not hasattr(self.module, function_name):
657 return kwargs.get("default", None)
658 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
659
660 def FullOTA_Assertions(self):
661 """Called after emitting the block of assertions at the top of a
662 full OTA package. Implementations can add whatever additional
663 assertions they like."""
664 return self._DoCall("FullOTA_Assertions")
665
Doug Zongkere5ff5902012-01-17 10:55:37 -0800666 def FullOTA_InstallBegin(self):
667 """Called at the start of full OTA installation."""
668 return self._DoCall("FullOTA_InstallBegin")
669
Doug Zongker05d3dea2009-06-22 11:32:31 -0700670 def FullOTA_InstallEnd(self):
671 """Called at the end of full OTA installation; typically this is
672 used to install the image for the device's baseband processor."""
673 return self._DoCall("FullOTA_InstallEnd")
674
675 def IncrementalOTA_Assertions(self):
676 """Called after emitting the block of assertions at the top of an
677 incremental OTA package. Implementations can add whatever
678 additional assertions they like."""
679 return self._DoCall("IncrementalOTA_Assertions")
680
Doug Zongkere5ff5902012-01-17 10:55:37 -0800681 def IncrementalOTA_VerifyBegin(self):
682 """Called at the start of the verification phase of incremental
683 OTA installation; additional checks can be placed here to abort
684 the script before any changes are made."""
685 return self._DoCall("IncrementalOTA_VerifyBegin")
686
Doug Zongker05d3dea2009-06-22 11:32:31 -0700687 def IncrementalOTA_VerifyEnd(self):
688 """Called at the end of the verification phase of incremental OTA
689 installation; additional checks can be placed here to abort the
690 script before any changes are made."""
691 return self._DoCall("IncrementalOTA_VerifyEnd")
692
Doug Zongkere5ff5902012-01-17 10:55:37 -0800693 def IncrementalOTA_InstallBegin(self):
694 """Called at the start of incremental OTA installation (after
695 verification is complete)."""
696 return self._DoCall("IncrementalOTA_InstallBegin")
697
Doug Zongker05d3dea2009-06-22 11:32:31 -0700698 def IncrementalOTA_InstallEnd(self):
699 """Called at the end of incremental OTA installation; typically
700 this is used to install the image for the device's baseband
701 processor."""
702 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700703
704class File(object):
705 def __init__(self, name, data):
706 self.name = name
707 self.data = data
708 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800709 self.sha1 = sha1(data).hexdigest()
710
711 @classmethod
712 def FromLocalFile(cls, name, diskname):
713 f = open(diskname, "rb")
714 data = f.read()
715 f.close()
716 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700717
718 def WriteToTemp(self):
719 t = tempfile.NamedTemporaryFile()
720 t.write(self.data)
721 t.flush()
722 return t
723
724 def AddToZip(self, z):
725 ZipWriteStr(z, self.name, self.data)
726
727DIFF_PROGRAM_BY_EXT = {
728 ".gz" : "imgdiff",
729 ".zip" : ["imgdiff", "-z"],
730 ".jar" : ["imgdiff", "-z"],
731 ".apk" : ["imgdiff", "-z"],
732 ".img" : "imgdiff",
733 }
734
735class Difference(object):
736 def __init__(self, tf, sf):
737 self.tf = tf
738 self.sf = sf
739 self.patch = None
740
741 def ComputePatch(self):
742 """Compute the patch (as a string of data) needed to turn sf into
743 tf. Returns the same tuple as GetPatch()."""
744
745 tf = self.tf
746 sf = self.sf
747
748 ext = os.path.splitext(tf.name)[1]
749 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
750
751 ttemp = tf.WriteToTemp()
752 stemp = sf.WriteToTemp()
753
754 ext = os.path.splitext(tf.name)[1]
755
756 try:
757 ptemp = tempfile.NamedTemporaryFile()
758 if isinstance(diff_program, list):
759 cmd = copy.copy(diff_program)
760 else:
761 cmd = [diff_program]
762 cmd.append(stemp.name)
763 cmd.append(ttemp.name)
764 cmd.append(ptemp.name)
765 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
766 _, err = p.communicate()
767 if err or p.returncode != 0:
768 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
769 return None
770 diff = ptemp.read()
771 finally:
772 ptemp.close()
773 stemp.close()
774 ttemp.close()
775
776 self.patch = diff
777 return self.tf, self.sf, self.patch
778
779
780 def GetPatch(self):
781 """Return a tuple (target_file, source_file, patch_data).
782 patch_data may be None if ComputePatch hasn't been called, or if
783 computing the patch failed."""
784 return self.tf, self.sf, self.patch
785
786
787def ComputeDifferences(diffs):
788 """Call ComputePatch on all the Difference objects in 'diffs'."""
789 print len(diffs), "diffs to compute"
790
791 # Do the largest files first, to try and reduce the long-pole effect.
792 by_size = [(i.tf.size, i) for i in diffs]
793 by_size.sort(reverse=True)
794 by_size = [i[1] for i in by_size]
795
796 lock = threading.Lock()
797 diff_iter = iter(by_size) # accessed under lock
798
799 def worker():
800 try:
801 lock.acquire()
802 for d in diff_iter:
803 lock.release()
804 start = time.time()
805 d.ComputePatch()
806 dur = time.time() - start
807 lock.acquire()
808
809 tf, sf, patch = d.GetPatch()
810 if sf.name == tf.name:
811 name = tf.name
812 else:
813 name = "%s (%s)" % (tf.name, sf.name)
814 if patch is None:
815 print "patching failed! %s" % (name,)
816 else:
817 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
818 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
819 lock.release()
820 except Exception, e:
821 print e
822 raise
823
824 # start worker threads; wait for them all to finish.
825 threads = [threading.Thread(target=worker)
826 for i in range(OPTIONS.worker_threads)]
827 for th in threads:
828 th.start()
829 while threads:
830 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700831
832
833# map recovery.fstab's fs_types to mount/format "partition types"
834PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
835 "ext4": "EMMC", "emmc": "EMMC" }
836
837def GetTypeAndDevice(mount_point, info):
838 fstab = info["fstab"]
839 if fstab:
840 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
841 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800842 return None