blob: fd098ea358ceeefa42690eeebd9f9f0b55421504 [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 Wangf9bbfb52010-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
davidcad0bb92011-03-15 14:21:38 +000031try:
32 from hashlib import sha1 as sha1
33except ImportError:
34 from sha import sha as sha1
35
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 Wangf9bbfb52010-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")
141 makeint("recovery_size")
142 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700143
Doug Zongker258bf462010-09-20 18:04:41 -0700144 d["fstab"] = LoadRecoveryFSTab(zip)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700145 return d
146
Doug Zongker258bf462010-09-20 18:04:41 -0700147def LoadRecoveryFSTab(zip):
148 class Partition(object):
149 pass
150
151 try:
152 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
153 except KeyError:
Ying Wanga73b6562011-03-03 21:52:08 -0800154 raise ValueError("Could not find RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker258bf462010-09-20 18:04:41 -0700155
156 d = {}
157 for line in data.split("\n"):
158 line = line.strip()
159 if not line or line.startswith("#"): continue
160 pieces = line.split()
161 if not (3 <= len(pieces) <= 4):
162 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
163
164 p = Partition()
165 p.mount_point = pieces[0]
166 p.fs_type = pieces[1]
167 p.device = pieces[2]
168 if len(pieces) == 4:
169 p.device2 = pieces[3]
170 else:
171 p.device2 = None
172
173 d[p.mount_point] = p
174 return d
175
176
Doug Zongker37974732010-09-16 17:44:38 -0700177def DumpInfoDict(d):
178 for k, v in sorted(d.items()):
179 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700180
Doug Zongker37974732010-09-16 17:44:38 -0700181def BuildAndAddBootableImage(sourcedir, targetname, output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700182 """Take a kernel, cmdline, and ramdisk directory from the input (in
183 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700184 into the output zip file under the name 'targetname'. Returns
185 targetname on success or None on failure (if sourcedir does not
186 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700187
188 print "creating %s..." % (targetname,)
189
190 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700191 if img is None:
192 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700193
Doug Zongker37974732010-09-16 17:44:38 -0700194 CheckSize(img, targetname, info_dict)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700195 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700196 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700197
198def BuildBootableImage(sourcedir):
199 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700200 'sourcedir'), and turn them into a boot image. Return the image
201 data, or None if sourcedir does not appear to contains files for
202 building the requested image."""
203
204 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
205 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
206 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700207
208 ramdisk_img = tempfile.NamedTemporaryFile()
209 img = tempfile.NamedTemporaryFile()
210
211 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
212 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700213 p2 = Run(["minigzip"],
214 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700215
216 p2.wait()
217 p1.wait()
218 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700219 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700220
Doug Zongker38a649f2009-06-17 09:07:09 -0700221 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
222
Doug Zongker171f1cd2009-06-15 22:36:37 -0700223 fn = os.path.join(sourcedir, "cmdline")
224 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700225 cmd.append("--cmdline")
226 cmd.append(open(fn).read().rstrip("\n"))
227
228 fn = os.path.join(sourcedir, "base")
229 if os.access(fn, os.F_OK):
230 cmd.append("--base")
231 cmd.append(open(fn).read().rstrip("\n"))
232
Ying Wang4de6b5b2010-08-25 14:29:34 -0700233 fn = os.path.join(sourcedir, "pagesize")
234 if os.access(fn, os.F_OK):
235 cmd.append("--pagesize")
236 cmd.append(open(fn).read().rstrip("\n"))
237
Doug Zongker38a649f2009-06-17 09:07:09 -0700238 cmd.extend(["--ramdisk", ramdisk_img.name,
239 "--output", img.name])
240
241 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700242 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700243 assert p.returncode == 0, "mkbootimg of %s image failed" % (
244 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700245
246 img.seek(os.SEEK_SET, 0)
247 data = img.read()
248
249 ramdisk_img.close()
250 img.close()
251
252 return data
253
254
Doug Zongker37974732010-09-16 17:44:38 -0700255def AddRecovery(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700256 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
Doug Zongker37974732010-09-16 17:44:38 -0700257 "recovery.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700258
Doug Zongker37974732010-09-16 17:44:38 -0700259def AddBoot(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700260 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
Doug Zongker37974732010-09-16 17:44:38 -0700261 "boot.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700262
Doug Zongker75f17362009-12-08 13:46:44 -0800263def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700264 """Unzip the given archive into a temporary directory and return the name."""
265
266 tmp = tempfile.mkdtemp(prefix="targetfiles-")
267 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800268 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
269 if pattern is not None:
270 cmd.append(pattern)
271 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700272 p.communicate()
273 if p.returncode != 0:
274 raise ExternalError("failed to unzip input target-files \"%s\"" %
275 (filename,))
276 return tmp
277
278
279def GetKeyPasswords(keylist):
280 """Given a list of keys, prompt the user to enter passwords for
281 those which require them. Return a {key: password} dict. password
282 will be None if the key has no password."""
283
Doug Zongker8ce7c252009-05-22 13:34:54 -0700284 no_passwords = []
285 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700286 devnull = open("/dev/null", "w+b")
287 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800288 # We don't need a password for things that aren't really keys.
289 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700290 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700291 continue
292
Doug Zongker602a84e2009-06-18 08:35:12 -0700293 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
294 "-inform", "DER", "-nocrypt"],
295 stdin=devnull.fileno(),
296 stdout=devnull.fileno(),
297 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700298 p.communicate()
299 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700300 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700301 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700302 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700303 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700304
305 key_passwords = PasswordManager().GetPasswords(need_passwords)
306 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700307 return key_passwords
308
309
Doug Zongker951495f2009-08-14 12:44:19 -0700310def SignFile(input_name, output_name, key, password, align=None,
311 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700312 """Sign the input_name zip/jar/apk, producing output_name. Use the
313 given key and password (the latter may be None if the key does not
314 have a password.
315
316 If align is an integer > 1, zipalign is run to align stored files in
317 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700318
319 If whole_file is true, use the "-w" option to SignApk to embed a
320 signature that covers the whole file in the archive comment of the
321 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700322 """
Doug Zongker951495f2009-08-14 12:44:19 -0700323
Doug Zongkereef39442009-04-02 12:14:19 -0700324 if align == 0 or align == 1:
325 align = None
326
327 if align:
328 temp = tempfile.NamedTemporaryFile()
329 sign_name = temp.name
330 else:
331 sign_name = output_name
332
Doug Zongker09cf5602009-08-14 15:25:06 -0700333 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700334 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
335 if whole_file:
336 cmd.append("-w")
337 cmd.extend([key + ".x509.pem", key + ".pk8",
338 input_name, sign_name])
339
340 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700341 if password is not None:
342 password += "\n"
343 p.communicate(password)
344 if p.returncode != 0:
345 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
346
347 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700348 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700349 p.communicate()
350 if p.returncode != 0:
351 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
352 temp.close()
353
354
Doug Zongker37974732010-09-16 17:44:38 -0700355def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700356 """Check the data string passed against the max size limit, if
357 any, for the given target. Raise exception if the data is too big.
358 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700359
Doug Zongker1684d9c2010-09-17 07:44:38 -0700360 if target.endswith(".img"): target = target[:-4]
Doug Zongker258bf462010-09-20 18:04:41 -0700361 mount_point = "/" + target
362
363 if info_dict["fstab"]:
364 if mount_point == "/userdata": mount_point = "/data"
365 p = info_dict["fstab"][mount_point]
366 fs_type = p.fs_type
367 limit = info_dict.get(p.device + "_size", None)
Doug Zongker258bf462010-09-20 18:04:41 -0700368 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700369
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700370 if fs_type == "yaffs2":
371 # image size should be increased by 1/64th to account for the
372 # spare area (64 bytes per 2k page)
373 limit = limit / 2048 * (2048+64)
374
Doug Zongkereef39442009-04-02 12:14:19 -0700375 size = len(data)
376 pct = float(size) * 100.0 / limit
377 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
378 if pct >= 99.0:
379 raise ExternalError(msg)
380 elif pct >= 95.0:
381 print
382 print " WARNING: ", msg
383 print
384 elif OPTIONS.verbose:
385 print " ", msg
386
387
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800388def ReadApkCerts(tf_zip):
389 """Given a target_files ZipFile, parse the META/apkcerts.txt file
390 and return a {package: cert} dict."""
391 certmap = {}
392 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
393 line = line.strip()
394 if not line: continue
395 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
396 r'private_key="(.*)"$', line)
397 if m:
398 name, cert, privkey = m.groups()
399 if cert in SPECIAL_CERT_STRINGS and not privkey:
400 certmap[name] = cert
401 elif (cert.endswith(".x509.pem") and
402 privkey.endswith(".pk8") and
403 cert[:-9] == privkey[:-4]):
404 certmap[name] = cert[:-9]
405 else:
406 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
407 return certmap
408
409
Doug Zongkereef39442009-04-02 12:14:19 -0700410COMMON_DOCSTRING = """
411 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700412 Prepend <dir>/bin to the list of places to search for binaries
413 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700414
Doug Zongker05d3dea2009-06-22 11:32:31 -0700415 -s (--device_specific) <file>
416 Path to the python module containing device-specific
417 releasetools code.
418
Doug Zongker8bec09e2009-11-30 15:37:14 -0800419 -x (--extra) <key=value>
420 Add a key/value pair to the 'extras' dict, which device-specific
421 extension code may look at.
422
Doug Zongkereef39442009-04-02 12:14:19 -0700423 -v (--verbose)
424 Show command lines being executed.
425
426 -h (--help)
427 Display this usage message and exit.
428"""
429
430def Usage(docstring):
431 print docstring.rstrip("\n")
432 print COMMON_DOCSTRING
433
434
435def ParseOptions(argv,
436 docstring,
437 extra_opts="", extra_long_opts=(),
438 extra_option_handler=None):
439 """Parse the options in argv and return any arguments that aren't
440 flags. docstring is the calling module's docstring, to be displayed
441 for errors and -h. extra_opts and extra_long_opts are for flags
442 defined by the caller, which are processed by passing them to
443 extra_option_handler."""
444
445 try:
446 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800447 argv, "hvp:s:x:" + extra_opts,
448 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700449 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700450 except getopt.GetoptError, err:
451 Usage(docstring)
452 print "**", str(err), "**"
453 sys.exit(2)
454
455 path_specified = False
456
457 for o, a in opts:
458 if o in ("-h", "--help"):
459 Usage(docstring)
460 sys.exit()
461 elif o in ("-v", "--verbose"):
462 OPTIONS.verbose = True
463 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700464 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700465 elif o in ("-s", "--device_specific"):
466 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800467 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800468 key, value = a.split("=", 1)
469 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700470 else:
471 if extra_option_handler is None or not extra_option_handler(o, a):
472 assert False, "unknown option \"%s\"" % (o,)
473
Doug Zongker602a84e2009-06-18 08:35:12 -0700474 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
475 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700476
477 return args
478
479
480def Cleanup():
481 for i in OPTIONS.tempfiles:
482 if os.path.isdir(i):
483 shutil.rmtree(i)
484 else:
485 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700486
487
488class PasswordManager(object):
489 def __init__(self):
490 self.editor = os.getenv("EDITOR", None)
491 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
492
493 def GetPasswords(self, items):
494 """Get passwords corresponding to each string in 'items',
495 returning a dict. (The dict may have keys in addition to the
496 values in 'items'.)
497
498 Uses the passwords in $ANDROID_PW_FILE if available, letting the
499 user edit that file to add more needed passwords. If no editor is
500 available, or $ANDROID_PW_FILE isn't define, prompts the user
501 interactively in the ordinary way.
502 """
503
504 current = self.ReadFile()
505
506 first = True
507 while True:
508 missing = []
509 for i in items:
510 if i not in current or not current[i]:
511 missing.append(i)
512 # Are all the passwords already in the file?
513 if not missing: return current
514
515 for i in missing:
516 current[i] = ""
517
518 if not first:
519 print "key file %s still missing some passwords." % (self.pwfile,)
520 answer = raw_input("try to edit again? [y]> ").strip()
521 if answer and answer[0] not in 'yY':
522 raise RuntimeError("key passwords unavailable")
523 first = False
524
525 current = self.UpdateAndReadFile(current)
526
527 def PromptResult(self, current):
528 """Prompt the user to enter a value (password) for each key in
529 'current' whose value is fales. Returns a new dict with all the
530 values.
531 """
532 result = {}
533 for k, v in sorted(current.iteritems()):
534 if v:
535 result[k] = v
536 else:
537 while True:
538 result[k] = getpass.getpass("Enter password for %s key> "
539 % (k,)).strip()
540 if result[k]: break
541 return result
542
543 def UpdateAndReadFile(self, current):
544 if not self.editor or not self.pwfile:
545 return self.PromptResult(current)
546
547 f = open(self.pwfile, "w")
548 os.chmod(self.pwfile, 0600)
549 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
550 f.write("# (Additional spaces are harmless.)\n\n")
551
552 first_line = None
553 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
554 sorted.sort()
555 for i, (_, k, v) in enumerate(sorted):
556 f.write("[[[ %s ]]] %s\n" % (v, k))
557 if not v and first_line is None:
558 # position cursor on first line with no password.
559 first_line = i + 4
560 f.close()
561
562 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
563 _, _ = p.communicate()
564
565 return self.ReadFile()
566
567 def ReadFile(self):
568 result = {}
569 if self.pwfile is None: return result
570 try:
571 f = open(self.pwfile, "r")
572 for line in f:
573 line = line.strip()
574 if not line or line[0] == '#': continue
575 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
576 if not m:
577 print "failed to parse password file: ", line
578 else:
579 result[m.group(2)] = m.group(1)
580 f.close()
581 except IOError, e:
582 if e.errno != errno.ENOENT:
583 print "error reading password file: ", str(e)
584 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700585
586
587def ZipWriteStr(zip, filename, data, perms=0644):
588 # use a fixed timestamp so the output is repeatable.
589 zinfo = zipfile.ZipInfo(filename=filename,
590 date_time=(2009, 1, 1, 0, 0, 0))
591 zinfo.compress_type = zip.compression
592 zinfo.external_attr = perms << 16
593 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700594
595
596class DeviceSpecificParams(object):
597 module = None
598 def __init__(self, **kwargs):
599 """Keyword arguments to the constructor become attributes of this
600 object, which is passed to all functions in the device-specific
601 module."""
602 for k, v in kwargs.iteritems():
603 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800604 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700605
606 if self.module is None:
607 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700608 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700609 try:
610 if os.path.isdir(path):
611 info = imp.find_module("releasetools", [path])
612 else:
613 d, f = os.path.split(path)
614 b, x = os.path.splitext(f)
615 if x == ".py":
616 f = b
617 info = imp.find_module(f, [d])
618 self.module = imp.load_module("device_specific", *info)
619 except ImportError:
620 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700621
622 def _DoCall(self, function_name, *args, **kwargs):
623 """Call the named function in the device-specific module, passing
624 the given args and kwargs. The first argument to the call will be
625 the DeviceSpecific object itself. If there is no module, or the
626 module does not define the function, return the value of the
627 'default' kwarg (which itself defaults to None)."""
628 if self.module is None or not hasattr(self.module, function_name):
629 return kwargs.get("default", None)
630 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
631
632 def FullOTA_Assertions(self):
633 """Called after emitting the block of assertions at the top of a
634 full OTA package. Implementations can add whatever additional
635 assertions they like."""
636 return self._DoCall("FullOTA_Assertions")
637
638 def FullOTA_InstallEnd(self):
639 """Called at the end of full OTA installation; typically this is
640 used to install the image for the device's baseband processor."""
641 return self._DoCall("FullOTA_InstallEnd")
642
643 def IncrementalOTA_Assertions(self):
644 """Called after emitting the block of assertions at the top of an
645 incremental OTA package. Implementations can add whatever
646 additional assertions they like."""
647 return self._DoCall("IncrementalOTA_Assertions")
648
649 def IncrementalOTA_VerifyEnd(self):
650 """Called at the end of the verification phase of incremental OTA
651 installation; additional checks can be placed here to abort the
652 script before any changes are made."""
653 return self._DoCall("IncrementalOTA_VerifyEnd")
654
655 def IncrementalOTA_InstallEnd(self):
656 """Called at the end of incremental OTA installation; typically
657 this is used to install the image for the device's baseband
658 processor."""
659 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700660
661class File(object):
662 def __init__(self, name, data):
663 self.name = name
664 self.data = data
665 self.size = len(data)
davidcad0bb92011-03-15 14:21:38 +0000666 self.sha1 = sha1(data).hexdigest()
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700667
668 def WriteToTemp(self):
669 t = tempfile.NamedTemporaryFile()
670 t.write(self.data)
671 t.flush()
672 return t
673
674 def AddToZip(self, z):
675 ZipWriteStr(z, self.name, self.data)
676
677DIFF_PROGRAM_BY_EXT = {
678 ".gz" : "imgdiff",
679 ".zip" : ["imgdiff", "-z"],
680 ".jar" : ["imgdiff", "-z"],
681 ".apk" : ["imgdiff", "-z"],
682 ".img" : "imgdiff",
683 }
684
685class Difference(object):
686 def __init__(self, tf, sf):
687 self.tf = tf
688 self.sf = sf
689 self.patch = None
690
691 def ComputePatch(self):
692 """Compute the patch (as a string of data) needed to turn sf into
693 tf. Returns the same tuple as GetPatch()."""
694
695 tf = self.tf
696 sf = self.sf
697
698 ext = os.path.splitext(tf.name)[1]
699 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
700
701 ttemp = tf.WriteToTemp()
702 stemp = sf.WriteToTemp()
703
704 ext = os.path.splitext(tf.name)[1]
705
706 try:
707 ptemp = tempfile.NamedTemporaryFile()
708 if isinstance(diff_program, list):
709 cmd = copy.copy(diff_program)
710 else:
711 cmd = [diff_program]
712 cmd.append(stemp.name)
713 cmd.append(ttemp.name)
714 cmd.append(ptemp.name)
715 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
716 _, err = p.communicate()
717 if err or p.returncode != 0:
718 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
719 return None
720 diff = ptemp.read()
721 finally:
722 ptemp.close()
723 stemp.close()
724 ttemp.close()
725
726 self.patch = diff
727 return self.tf, self.sf, self.patch
728
729
730 def GetPatch(self):
731 """Return a tuple (target_file, source_file, patch_data).
732 patch_data may be None if ComputePatch hasn't been called, or if
733 computing the patch failed."""
734 return self.tf, self.sf, self.patch
735
736
737def ComputeDifferences(diffs):
738 """Call ComputePatch on all the Difference objects in 'diffs'."""
739 print len(diffs), "diffs to compute"
740
741 # Do the largest files first, to try and reduce the long-pole effect.
742 by_size = [(i.tf.size, i) for i in diffs]
743 by_size.sort(reverse=True)
744 by_size = [i[1] for i in by_size]
745
746 lock = threading.Lock()
747 diff_iter = iter(by_size) # accessed under lock
748
749 def worker():
750 try:
751 lock.acquire()
752 for d in diff_iter:
753 lock.release()
754 start = time.time()
755 d.ComputePatch()
756 dur = time.time() - start
757 lock.acquire()
758
759 tf, sf, patch = d.GetPatch()
760 if sf.name == tf.name:
761 name = tf.name
762 else:
763 name = "%s (%s)" % (tf.name, sf.name)
764 if patch is None:
765 print "patching failed! %s" % (name,)
766 else:
767 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
768 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
769 lock.release()
770 except Exception, e:
771 print e
772 raise
773
774 # start worker threads; wait for them all to finish.
775 threads = [threading.Thread(target=worker)
776 for i in range(OPTIONS.worker_threads)]
777 for th in threads:
778 th.start()
779 while threads:
780 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700781
782
783# map recovery.fstab's fs_types to mount/format "partition types"
784PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
785 "ext4": "EMMC", "emmc": "EMMC" }
786
787def GetTypeAndDevice(mount_point, info):
788 fstab = info["fstab"]
789 if fstab:
790 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
791 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800792 return None