blob: 74192f79b81e404f5b748f8484e348ea21e2874a [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
21import re
Doug Zongkerea5d7a92010-09-12 15:26:16 -070022import sha
Doug Zongkereef39442009-04-02 12:14:19 -070023import 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
31# missing in Python 2.4 and before
32if not hasattr(os, "SEEK_SET"):
33 os.SEEK_SET = 0
34
35class Options(object): pass
36OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070037OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070038OPTIONS.verbose = False
39OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070040OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080041OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070042OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070043
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080044
45# Values for "certificate" in apkcerts that mean special things.
46SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
47
48
Doug Zongkereef39442009-04-02 12:14:19 -070049class ExternalError(RuntimeError): pass
50
51
52def Run(args, **kwargs):
53 """Create and return a subprocess.Popen object, printing the command
54 line on the terminal if -v was specified."""
55 if OPTIONS.verbose:
56 print " running: ", " ".join(args)
57 return subprocess.Popen(args, **kwargs)
58
59
Doug Zongker37974732010-09-16 17:44:38 -070060def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070061 """Read and parse the META/misc_info.txt key/value pairs from the
62 input target files and return a dict."""
63
64 d = {}
65 try:
Doug Zongker37974732010-09-16 17:44:38 -070066 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070067 line = line.strip()
68 if not line or line.startswith("#"): continue
69 k, v = line.split("=", 1)
70 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070071 except KeyError:
72 # ok if misc_info.txt doesn't exist
73 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070074
Doug Zongker37974732010-09-16 17:44:38 -070075 # backwards compatibility: These values used to be in their own
76 # files. Look for them, in case we're processing an old
77 # target_files zip.
78
79 if "mkyaffs2_extra_flags" not in d:
80 try:
81 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
82 except KeyError:
83 # ok if flags don't exist
84 pass
85
86 if "recovery_api_version" not in d:
87 try:
88 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
89 except KeyError:
90 raise ValueError("can't find recovery API version in input target-files")
91
92 if "tool_extensions" not in d:
93 try:
94 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
95 except KeyError:
96 # ok if extensions don't exist
97 pass
98
99 try:
100 data = zip.read("META/imagesizes.txt")
101 for line in data.split("\n"):
102 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700103 name, value = line.split(" ", 1)
104 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700105 if name == "blocksize":
106 d[name] = value
107 else:
108 d[name + "_size"] = value
109 except KeyError:
110 pass
111
112 def makeint(key):
113 if key in d:
114 d[key] = int(d[key], 0)
115
116 makeint("recovery_api_version")
117 makeint("blocksize")
118 makeint("system_size")
119 makeint("userdata_size")
120 makeint("recovery_size")
121 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700122
Doug Zongker258bf462010-09-20 18:04:41 -0700123 d["fstab"] = LoadRecoveryFSTab(zip)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700124 return d
125
Doug Zongker258bf462010-09-20 18:04:41 -0700126def LoadRecoveryFSTab(zip):
127 class Partition(object):
128 pass
129
130 try:
131 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
132 except KeyError:
Ying Wanga73b6562011-03-03 21:52:08 -0800133 raise ValueError("Could not find RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker258bf462010-09-20 18:04:41 -0700134
135 d = {}
136 for line in data.split("\n"):
137 line = line.strip()
138 if not line or line.startswith("#"): continue
139 pieces = line.split()
140 if not (3 <= len(pieces) <= 4):
141 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
142
143 p = Partition()
144 p.mount_point = pieces[0]
145 p.fs_type = pieces[1]
146 p.device = pieces[2]
147 if len(pieces) == 4:
148 p.device2 = pieces[3]
149 else:
150 p.device2 = None
151
152 d[p.mount_point] = p
153 return d
154
155
Doug Zongker37974732010-09-16 17:44:38 -0700156def DumpInfoDict(d):
157 for k, v in sorted(d.items()):
158 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700159
Doug Zongker37974732010-09-16 17:44:38 -0700160def BuildAndAddBootableImage(sourcedir, targetname, output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700161 """Take a kernel, cmdline, and ramdisk directory from the input (in
162 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700163 into the output zip file under the name 'targetname'. Returns
164 targetname on success or None on failure (if sourcedir does not
165 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700166
167 print "creating %s..." % (targetname,)
168
169 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700170 if img is None:
171 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700172
Doug Zongker37974732010-09-16 17:44:38 -0700173 CheckSize(img, targetname, info_dict)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700174 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700175 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700176
177def BuildBootableImage(sourcedir):
178 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700179 'sourcedir'), and turn them into a boot image. Return the image
180 data, or None if sourcedir does not appear to contains files for
181 building the requested image."""
182
183 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
184 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
185 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700186
187 ramdisk_img = tempfile.NamedTemporaryFile()
188 img = tempfile.NamedTemporaryFile()
189
190 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
191 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700192 p2 = Run(["minigzip"],
193 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700194
195 p2.wait()
196 p1.wait()
197 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700198 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700199
Doug Zongker38a649f2009-06-17 09:07:09 -0700200 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
201
Doug Zongker171f1cd2009-06-15 22:36:37 -0700202 fn = os.path.join(sourcedir, "cmdline")
203 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700204 cmd.append("--cmdline")
205 cmd.append(open(fn).read().rstrip("\n"))
206
207 fn = os.path.join(sourcedir, "base")
208 if os.access(fn, os.F_OK):
209 cmd.append("--base")
210 cmd.append(open(fn).read().rstrip("\n"))
211
Ying Wang4de6b5b2010-08-25 14:29:34 -0700212 fn = os.path.join(sourcedir, "pagesize")
213 if os.access(fn, os.F_OK):
214 cmd.append("--pagesize")
215 cmd.append(open(fn).read().rstrip("\n"))
216
Doug Zongker38a649f2009-06-17 09:07:09 -0700217 cmd.extend(["--ramdisk", ramdisk_img.name,
218 "--output", img.name])
219
220 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700221 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700222 assert p.returncode == 0, "mkbootimg of %s image failed" % (
223 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700224
225 img.seek(os.SEEK_SET, 0)
226 data = img.read()
227
228 ramdisk_img.close()
229 img.close()
230
231 return data
232
233
Doug Zongker37974732010-09-16 17:44:38 -0700234def AddRecovery(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700235 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
Doug Zongker37974732010-09-16 17:44:38 -0700236 "recovery.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700237
Doug Zongker37974732010-09-16 17:44:38 -0700238def AddBoot(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700239 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
Doug Zongker37974732010-09-16 17:44:38 -0700240 "boot.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700241
Doug Zongker75f17362009-12-08 13:46:44 -0800242def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700243 """Unzip the given archive into a temporary directory and return the name."""
244
245 tmp = tempfile.mkdtemp(prefix="targetfiles-")
246 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800247 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
248 if pattern is not None:
249 cmd.append(pattern)
250 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700251 p.communicate()
252 if p.returncode != 0:
253 raise ExternalError("failed to unzip input target-files \"%s\"" %
254 (filename,))
255 return tmp
256
257
258def GetKeyPasswords(keylist):
259 """Given a list of keys, prompt the user to enter passwords for
260 those which require them. Return a {key: password} dict. password
261 will be None if the key has no password."""
262
Doug Zongker8ce7c252009-05-22 13:34:54 -0700263 no_passwords = []
264 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700265 devnull = open("/dev/null", "w+b")
266 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800267 # We don't need a password for things that aren't really keys.
268 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700269 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700270 continue
271
Doug Zongker602a84e2009-06-18 08:35:12 -0700272 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
273 "-inform", "DER", "-nocrypt"],
274 stdin=devnull.fileno(),
275 stdout=devnull.fileno(),
276 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700277 p.communicate()
278 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700279 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700280 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700281 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700282 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700283
284 key_passwords = PasswordManager().GetPasswords(need_passwords)
285 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700286 return key_passwords
287
288
Doug Zongker951495f2009-08-14 12:44:19 -0700289def SignFile(input_name, output_name, key, password, align=None,
290 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700291 """Sign the input_name zip/jar/apk, producing output_name. Use the
292 given key and password (the latter may be None if the key does not
293 have a password.
294
295 If align is an integer > 1, zipalign is run to align stored files in
296 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700297
298 If whole_file is true, use the "-w" option to SignApk to embed a
299 signature that covers the whole file in the archive comment of the
300 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700301 """
Doug Zongker951495f2009-08-14 12:44:19 -0700302
Doug Zongkereef39442009-04-02 12:14:19 -0700303 if align == 0 or align == 1:
304 align = None
305
306 if align:
307 temp = tempfile.NamedTemporaryFile()
308 sign_name = temp.name
309 else:
310 sign_name = output_name
311
Doug Zongker09cf5602009-08-14 15:25:06 -0700312 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700313 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
314 if whole_file:
315 cmd.append("-w")
316 cmd.extend([key + ".x509.pem", key + ".pk8",
317 input_name, sign_name])
318
319 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700320 if password is not None:
321 password += "\n"
322 p.communicate(password)
323 if p.returncode != 0:
324 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
325
326 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700327 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700328 p.communicate()
329 if p.returncode != 0:
330 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
331 temp.close()
332
333
Doug Zongker37974732010-09-16 17:44:38 -0700334def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700335 """Check the data string passed against the max size limit, if
336 any, for the given target. Raise exception if the data is too big.
337 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700338
Doug Zongker1684d9c2010-09-17 07:44:38 -0700339 if target.endswith(".img"): target = target[:-4]
Doug Zongker258bf462010-09-20 18:04:41 -0700340 mount_point = "/" + target
341
342 if info_dict["fstab"]:
343 if mount_point == "/userdata": mount_point = "/data"
344 p = info_dict["fstab"][mount_point]
345 fs_type = p.fs_type
346 limit = info_dict.get(p.device + "_size", None)
Doug Zongker258bf462010-09-20 18:04:41 -0700347 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700348
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700349 if fs_type == "yaffs2":
350 # image size should be increased by 1/64th to account for the
351 # spare area (64 bytes per 2k page)
352 limit = limit / 2048 * (2048+64)
353
Doug Zongkereef39442009-04-02 12:14:19 -0700354 size = len(data)
355 pct = float(size) * 100.0 / limit
356 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
357 if pct >= 99.0:
358 raise ExternalError(msg)
359 elif pct >= 95.0:
360 print
361 print " WARNING: ", msg
362 print
363 elif OPTIONS.verbose:
364 print " ", msg
365
366
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800367def ReadApkCerts(tf_zip):
368 """Given a target_files ZipFile, parse the META/apkcerts.txt file
369 and return a {package: cert} dict."""
370 certmap = {}
371 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
372 line = line.strip()
373 if not line: continue
374 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
375 r'private_key="(.*)"$', line)
376 if m:
377 name, cert, privkey = m.groups()
378 if cert in SPECIAL_CERT_STRINGS and not privkey:
379 certmap[name] = cert
380 elif (cert.endswith(".x509.pem") and
381 privkey.endswith(".pk8") and
382 cert[:-9] == privkey[:-4]):
383 certmap[name] = cert[:-9]
384 else:
385 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
386 return certmap
387
388
Doug Zongkereef39442009-04-02 12:14:19 -0700389COMMON_DOCSTRING = """
390 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700391 Prepend <dir>/bin to the list of places to search for binaries
392 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700393
Doug Zongker05d3dea2009-06-22 11:32:31 -0700394 -s (--device_specific) <file>
395 Path to the python module containing device-specific
396 releasetools code.
397
Doug Zongker8bec09e2009-11-30 15:37:14 -0800398 -x (--extra) <key=value>
399 Add a key/value pair to the 'extras' dict, which device-specific
400 extension code may look at.
401
Doug Zongkereef39442009-04-02 12:14:19 -0700402 -v (--verbose)
403 Show command lines being executed.
404
405 -h (--help)
406 Display this usage message and exit.
407"""
408
409def Usage(docstring):
410 print docstring.rstrip("\n")
411 print COMMON_DOCSTRING
412
413
414def ParseOptions(argv,
415 docstring,
416 extra_opts="", extra_long_opts=(),
417 extra_option_handler=None):
418 """Parse the options in argv and return any arguments that aren't
419 flags. docstring is the calling module's docstring, to be displayed
420 for errors and -h. extra_opts and extra_long_opts are for flags
421 defined by the caller, which are processed by passing them to
422 extra_option_handler."""
423
424 try:
425 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800426 argv, "hvp:s:x:" + extra_opts,
427 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700428 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700429 except getopt.GetoptError, err:
430 Usage(docstring)
431 print "**", str(err), "**"
432 sys.exit(2)
433
434 path_specified = False
435
436 for o, a in opts:
437 if o in ("-h", "--help"):
438 Usage(docstring)
439 sys.exit()
440 elif o in ("-v", "--verbose"):
441 OPTIONS.verbose = True
442 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700443 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700444 elif o in ("-s", "--device_specific"):
445 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800446 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800447 key, value = a.split("=", 1)
448 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700449 else:
450 if extra_option_handler is None or not extra_option_handler(o, a):
451 assert False, "unknown option \"%s\"" % (o,)
452
Doug Zongker602a84e2009-06-18 08:35:12 -0700453 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
454 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700455
456 return args
457
458
459def Cleanup():
460 for i in OPTIONS.tempfiles:
461 if os.path.isdir(i):
462 shutil.rmtree(i)
463 else:
464 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700465
466
467class PasswordManager(object):
468 def __init__(self):
469 self.editor = os.getenv("EDITOR", None)
470 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
471
472 def GetPasswords(self, items):
473 """Get passwords corresponding to each string in 'items',
474 returning a dict. (The dict may have keys in addition to the
475 values in 'items'.)
476
477 Uses the passwords in $ANDROID_PW_FILE if available, letting the
478 user edit that file to add more needed passwords. If no editor is
479 available, or $ANDROID_PW_FILE isn't define, prompts the user
480 interactively in the ordinary way.
481 """
482
483 current = self.ReadFile()
484
485 first = True
486 while True:
487 missing = []
488 for i in items:
489 if i not in current or not current[i]:
490 missing.append(i)
491 # Are all the passwords already in the file?
492 if not missing: return current
493
494 for i in missing:
495 current[i] = ""
496
497 if not first:
498 print "key file %s still missing some passwords." % (self.pwfile,)
499 answer = raw_input("try to edit again? [y]> ").strip()
500 if answer and answer[0] not in 'yY':
501 raise RuntimeError("key passwords unavailable")
502 first = False
503
504 current = self.UpdateAndReadFile(current)
505
506 def PromptResult(self, current):
507 """Prompt the user to enter a value (password) for each key in
508 'current' whose value is fales. Returns a new dict with all the
509 values.
510 """
511 result = {}
512 for k, v in sorted(current.iteritems()):
513 if v:
514 result[k] = v
515 else:
516 while True:
517 result[k] = getpass.getpass("Enter password for %s key> "
518 % (k,)).strip()
519 if result[k]: break
520 return result
521
522 def UpdateAndReadFile(self, current):
523 if not self.editor or not self.pwfile:
524 return self.PromptResult(current)
525
526 f = open(self.pwfile, "w")
527 os.chmod(self.pwfile, 0600)
528 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
529 f.write("# (Additional spaces are harmless.)\n\n")
530
531 first_line = None
532 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
533 sorted.sort()
534 for i, (_, k, v) in enumerate(sorted):
535 f.write("[[[ %s ]]] %s\n" % (v, k))
536 if not v and first_line is None:
537 # position cursor on first line with no password.
538 first_line = i + 4
539 f.close()
540
541 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
542 _, _ = p.communicate()
543
544 return self.ReadFile()
545
546 def ReadFile(self):
547 result = {}
548 if self.pwfile is None: return result
549 try:
550 f = open(self.pwfile, "r")
551 for line in f:
552 line = line.strip()
553 if not line or line[0] == '#': continue
554 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
555 if not m:
556 print "failed to parse password file: ", line
557 else:
558 result[m.group(2)] = m.group(1)
559 f.close()
560 except IOError, e:
561 if e.errno != errno.ENOENT:
562 print "error reading password file: ", str(e)
563 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700564
565
566def ZipWriteStr(zip, filename, data, perms=0644):
567 # use a fixed timestamp so the output is repeatable.
568 zinfo = zipfile.ZipInfo(filename=filename,
569 date_time=(2009, 1, 1, 0, 0, 0))
570 zinfo.compress_type = zip.compression
571 zinfo.external_attr = perms << 16
572 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700573
574
575class DeviceSpecificParams(object):
576 module = None
577 def __init__(self, **kwargs):
578 """Keyword arguments to the constructor become attributes of this
579 object, which is passed to all functions in the device-specific
580 module."""
581 for k, v in kwargs.iteritems():
582 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800583 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700584
585 if self.module is None:
586 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700587 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700588 try:
589 if os.path.isdir(path):
590 info = imp.find_module("releasetools", [path])
591 else:
592 d, f = os.path.split(path)
593 b, x = os.path.splitext(f)
594 if x == ".py":
595 f = b
596 info = imp.find_module(f, [d])
597 self.module = imp.load_module("device_specific", *info)
598 except ImportError:
599 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700600
601 def _DoCall(self, function_name, *args, **kwargs):
602 """Call the named function in the device-specific module, passing
603 the given args and kwargs. The first argument to the call will be
604 the DeviceSpecific object itself. If there is no module, or the
605 module does not define the function, return the value of the
606 'default' kwarg (which itself defaults to None)."""
607 if self.module is None or not hasattr(self.module, function_name):
608 return kwargs.get("default", None)
609 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
610
611 def FullOTA_Assertions(self):
612 """Called after emitting the block of assertions at the top of a
613 full OTA package. Implementations can add whatever additional
614 assertions they like."""
615 return self._DoCall("FullOTA_Assertions")
616
617 def FullOTA_InstallEnd(self):
618 """Called at the end of full OTA installation; typically this is
619 used to install the image for the device's baseband processor."""
620 return self._DoCall("FullOTA_InstallEnd")
621
622 def IncrementalOTA_Assertions(self):
623 """Called after emitting the block of assertions at the top of an
624 incremental OTA package. Implementations can add whatever
625 additional assertions they like."""
626 return self._DoCall("IncrementalOTA_Assertions")
627
628 def IncrementalOTA_VerifyEnd(self):
629 """Called at the end of the verification phase of incremental OTA
630 installation; additional checks can be placed here to abort the
631 script before any changes are made."""
632 return self._DoCall("IncrementalOTA_VerifyEnd")
633
634 def IncrementalOTA_InstallEnd(self):
635 """Called at the end of incremental OTA installation; typically
636 this is used to install the image for the device's baseband
637 processor."""
638 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700639
640class File(object):
641 def __init__(self, name, data):
642 self.name = name
643 self.data = data
644 self.size = len(data)
645 self.sha1 = sha.sha(data).hexdigest()
646
647 def WriteToTemp(self):
648 t = tempfile.NamedTemporaryFile()
649 t.write(self.data)
650 t.flush()
651 return t
652
653 def AddToZip(self, z):
654 ZipWriteStr(z, self.name, self.data)
655
656DIFF_PROGRAM_BY_EXT = {
657 ".gz" : "imgdiff",
658 ".zip" : ["imgdiff", "-z"],
659 ".jar" : ["imgdiff", "-z"],
660 ".apk" : ["imgdiff", "-z"],
661 ".img" : "imgdiff",
662 }
663
664class Difference(object):
665 def __init__(self, tf, sf):
666 self.tf = tf
667 self.sf = sf
668 self.patch = None
669
670 def ComputePatch(self):
671 """Compute the patch (as a string of data) needed to turn sf into
672 tf. Returns the same tuple as GetPatch()."""
673
674 tf = self.tf
675 sf = self.sf
676
677 ext = os.path.splitext(tf.name)[1]
678 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
679
680 ttemp = tf.WriteToTemp()
681 stemp = sf.WriteToTemp()
682
683 ext = os.path.splitext(tf.name)[1]
684
685 try:
686 ptemp = tempfile.NamedTemporaryFile()
687 if isinstance(diff_program, list):
688 cmd = copy.copy(diff_program)
689 else:
690 cmd = [diff_program]
691 cmd.append(stemp.name)
692 cmd.append(ttemp.name)
693 cmd.append(ptemp.name)
694 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
695 _, err = p.communicate()
696 if err or p.returncode != 0:
697 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
698 return None
699 diff = ptemp.read()
700 finally:
701 ptemp.close()
702 stemp.close()
703 ttemp.close()
704
705 self.patch = diff
706 return self.tf, self.sf, self.patch
707
708
709 def GetPatch(self):
710 """Return a tuple (target_file, source_file, patch_data).
711 patch_data may be None if ComputePatch hasn't been called, or if
712 computing the patch failed."""
713 return self.tf, self.sf, self.patch
714
715
716def ComputeDifferences(diffs):
717 """Call ComputePatch on all the Difference objects in 'diffs'."""
718 print len(diffs), "diffs to compute"
719
720 # Do the largest files first, to try and reduce the long-pole effect.
721 by_size = [(i.tf.size, i) for i in diffs]
722 by_size.sort(reverse=True)
723 by_size = [i[1] for i in by_size]
724
725 lock = threading.Lock()
726 diff_iter = iter(by_size) # accessed under lock
727
728 def worker():
729 try:
730 lock.acquire()
731 for d in diff_iter:
732 lock.release()
733 start = time.time()
734 d.ComputePatch()
735 dur = time.time() - start
736 lock.acquire()
737
738 tf, sf, patch = d.GetPatch()
739 if sf.name == tf.name:
740 name = tf.name
741 else:
742 name = "%s (%s)" % (tf.name, sf.name)
743 if patch is None:
744 print "patching failed! %s" % (name,)
745 else:
746 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
747 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
748 lock.release()
749 except Exception, e:
750 print e
751 raise
752
753 # start worker threads; wait for them all to finish.
754 threads = [threading.Thread(target=worker)
755 for i in range(OPTIONS.worker_threads)]
756 for th in threads:
757 th.start()
758 while threads:
759 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700760
761
762# map recovery.fstab's fs_types to mount/format "partition types"
763PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
764 "ext4": "EMMC", "emmc": "EMMC" }
765
766def GetTypeAndDevice(mount_point, info):
767 fstab = info["fstab"]
768 if fstab:
769 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
770 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800771 return None