blob: 52e7aed7189bbb2d18918b760631b027341bda67 [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
Doug Zongkerea5d7a92010-09-12 15:26:16 -070023import sha
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
32# missing in Python 2.4 and before
33if not hasattr(os, "SEEK_SET"):
34 os.SEEK_SET = 0
35
36class Options(object): pass
37OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070038OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070039OPTIONS.verbose = False
40OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070041OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080042OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070043OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070044
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080045
46# Values for "certificate" in apkcerts that mean special things.
47SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
48
49
Doug Zongkereef39442009-04-02 12:14:19 -070050class ExternalError(RuntimeError): pass
51
52
53def Run(args, **kwargs):
54 """Create and return a subprocess.Popen object, printing the command
55 line on the terminal if -v was specified."""
56 if OPTIONS.verbose:
57 print " running: ", " ".join(args)
58 return subprocess.Popen(args, **kwargs)
59
60
Ying Wangf9bbfb52010-12-13 16:25:36 -080061def CloseInheritedPipes():
62 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
63 before doing other work."""
64 if platform.system() != "Darwin":
65 return
66 for d in range(3, 1025):
67 try:
68 stat = os.fstat(d)
69 if stat is not None:
70 pipebit = stat[0] & 0x1000
71 if pipebit != 0:
72 os.close(d)
73 except OSError:
74 pass
75
76
Doug Zongker37974732010-09-16 17:44:38 -070077def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070078 """Read and parse the META/misc_info.txt key/value pairs from the
79 input target files and return a dict."""
80
81 d = {}
82 try:
Doug Zongker37974732010-09-16 17:44:38 -070083 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070084 line = line.strip()
85 if not line or line.startswith("#"): continue
86 k, v = line.split("=", 1)
87 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070088 except KeyError:
89 # ok if misc_info.txt doesn't exist
90 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070091
Doug Zongker37974732010-09-16 17:44:38 -070092 # backwards compatibility: These values used to be in their own
93 # files. Look for them, in case we're processing an old
94 # target_files zip.
95
96 if "mkyaffs2_extra_flags" not in d:
97 try:
98 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
99 except KeyError:
100 # ok if flags don't exist
101 pass
102
103 if "recovery_api_version" not in d:
104 try:
105 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
106 except KeyError:
107 raise ValueError("can't find recovery API version in input target-files")
108
109 if "tool_extensions" not in d:
110 try:
111 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
112 except KeyError:
113 # ok if extensions don't exist
114 pass
115
116 try:
117 data = zip.read("META/imagesizes.txt")
118 for line in data.split("\n"):
119 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700120 name, value = line.split(" ", 1)
121 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700122 if name == "blocksize":
123 d[name] = value
124 else:
125 d[name + "_size"] = value
126 except KeyError:
127 pass
128
129 def makeint(key):
130 if key in d:
131 d[key] = int(d[key], 0)
132
133 makeint("recovery_api_version")
134 makeint("blocksize")
135 makeint("system_size")
136 makeint("userdata_size")
137 makeint("recovery_size")
138 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700139
Doug Zongker258bf462010-09-20 18:04:41 -0700140 d["fstab"] = LoadRecoveryFSTab(zip)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700141 return d
142
Doug Zongker258bf462010-09-20 18:04:41 -0700143def LoadRecoveryFSTab(zip):
144 class Partition(object):
145 pass
146
147 try:
148 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
149 except KeyError:
Ying Wanga73b6562011-03-03 21:52:08 -0800150 raise ValueError("Could not find RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker258bf462010-09-20 18:04:41 -0700151
152 d = {}
153 for line in data.split("\n"):
154 line = line.strip()
155 if not line or line.startswith("#"): continue
156 pieces = line.split()
157 if not (3 <= len(pieces) <= 4):
158 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
159
160 p = Partition()
161 p.mount_point = pieces[0]
162 p.fs_type = pieces[1]
163 p.device = pieces[2]
164 if len(pieces) == 4:
165 p.device2 = pieces[3]
166 else:
167 p.device2 = None
168
169 d[p.mount_point] = p
170 return d
171
172
Doug Zongker37974732010-09-16 17:44:38 -0700173def DumpInfoDict(d):
174 for k, v in sorted(d.items()):
175 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700176
Doug Zongker37974732010-09-16 17:44:38 -0700177def BuildAndAddBootableImage(sourcedir, targetname, output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700178 """Take a kernel, cmdline, and ramdisk directory from the input (in
179 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700180 into the output zip file under the name 'targetname'. Returns
181 targetname on success or None on failure (if sourcedir does not
182 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700183
184 print "creating %s..." % (targetname,)
185
186 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700187 if img is None:
188 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700189
Doug Zongker37974732010-09-16 17:44:38 -0700190 CheckSize(img, targetname, info_dict)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700191 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700192 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700193
194def BuildBootableImage(sourcedir):
195 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700196 'sourcedir'), and turn them into a boot image. Return the image
197 data, or None if sourcedir does not appear to contains files for
198 building the requested image."""
199
200 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
201 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
202 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700203
204 ramdisk_img = tempfile.NamedTemporaryFile()
205 img = tempfile.NamedTemporaryFile()
206
207 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
208 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700209 p2 = Run(["minigzip"],
210 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700211
212 p2.wait()
213 p1.wait()
214 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700215 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700216
Doug Zongker38a649f2009-06-17 09:07:09 -0700217 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
218
Doug Zongker171f1cd2009-06-15 22:36:37 -0700219 fn = os.path.join(sourcedir, "cmdline")
220 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700221 cmd.append("--cmdline")
222 cmd.append(open(fn).read().rstrip("\n"))
223
224 fn = os.path.join(sourcedir, "base")
225 if os.access(fn, os.F_OK):
226 cmd.append("--base")
227 cmd.append(open(fn).read().rstrip("\n"))
228
Ying Wang4de6b5b2010-08-25 14:29:34 -0700229 fn = os.path.join(sourcedir, "pagesize")
230 if os.access(fn, os.F_OK):
231 cmd.append("--pagesize")
232 cmd.append(open(fn).read().rstrip("\n"))
233
Doug Zongker38a649f2009-06-17 09:07:09 -0700234 cmd.extend(["--ramdisk", ramdisk_img.name,
235 "--output", img.name])
236
237 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700238 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700239 assert p.returncode == 0, "mkbootimg of %s image failed" % (
240 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700241
242 img.seek(os.SEEK_SET, 0)
243 data = img.read()
244
245 ramdisk_img.close()
246 img.close()
247
248 return data
249
250
Doug Zongker37974732010-09-16 17:44:38 -0700251def AddRecovery(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700252 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
Doug Zongker37974732010-09-16 17:44:38 -0700253 "recovery.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700254
Doug Zongker37974732010-09-16 17:44:38 -0700255def AddBoot(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700256 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
Doug Zongker37974732010-09-16 17:44:38 -0700257 "boot.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700258
Doug Zongker75f17362009-12-08 13:46:44 -0800259def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700260 """Unzip the given archive into a temporary directory and return the name."""
261
262 tmp = tempfile.mkdtemp(prefix="targetfiles-")
263 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800264 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
265 if pattern is not None:
266 cmd.append(pattern)
267 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700268 p.communicate()
269 if p.returncode != 0:
270 raise ExternalError("failed to unzip input target-files \"%s\"" %
271 (filename,))
272 return tmp
273
274
275def GetKeyPasswords(keylist):
276 """Given a list of keys, prompt the user to enter passwords for
277 those which require them. Return a {key: password} dict. password
278 will be None if the key has no password."""
279
Doug Zongker8ce7c252009-05-22 13:34:54 -0700280 no_passwords = []
281 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700282 devnull = open("/dev/null", "w+b")
283 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800284 # We don't need a password for things that aren't really keys.
285 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700286 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700287 continue
288
Doug Zongker602a84e2009-06-18 08:35:12 -0700289 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
290 "-inform", "DER", "-nocrypt"],
291 stdin=devnull.fileno(),
292 stdout=devnull.fileno(),
293 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700294 p.communicate()
295 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700296 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700297 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700298 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700299 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700300
301 key_passwords = PasswordManager().GetPasswords(need_passwords)
302 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700303 return key_passwords
304
305
Doug Zongker951495f2009-08-14 12:44:19 -0700306def SignFile(input_name, output_name, key, password, align=None,
307 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700308 """Sign the input_name zip/jar/apk, producing output_name. Use the
309 given key and password (the latter may be None if the key does not
310 have a password.
311
312 If align is an integer > 1, zipalign is run to align stored files in
313 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700314
315 If whole_file is true, use the "-w" option to SignApk to embed a
316 signature that covers the whole file in the archive comment of the
317 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700318 """
Doug Zongker951495f2009-08-14 12:44:19 -0700319
Doug Zongkereef39442009-04-02 12:14:19 -0700320 if align == 0 or align == 1:
321 align = None
322
323 if align:
324 temp = tempfile.NamedTemporaryFile()
325 sign_name = temp.name
326 else:
327 sign_name = output_name
328
Doug Zongker09cf5602009-08-14 15:25:06 -0700329 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700330 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
331 if whole_file:
332 cmd.append("-w")
333 cmd.extend([key + ".x509.pem", key + ".pk8",
334 input_name, sign_name])
335
336 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700337 if password is not None:
338 password += "\n"
339 p.communicate(password)
340 if p.returncode != 0:
341 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
342
343 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700344 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700345 p.communicate()
346 if p.returncode != 0:
347 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
348 temp.close()
349
350
Doug Zongker37974732010-09-16 17:44:38 -0700351def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700352 """Check the data string passed against the max size limit, if
353 any, for the given target. Raise exception if the data is too big.
354 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700355
Doug Zongker1684d9c2010-09-17 07:44:38 -0700356 if target.endswith(".img"): target = target[:-4]
Doug Zongker258bf462010-09-20 18:04:41 -0700357 mount_point = "/" + target
358
359 if info_dict["fstab"]:
360 if mount_point == "/userdata": mount_point = "/data"
361 p = info_dict["fstab"][mount_point]
362 fs_type = p.fs_type
363 limit = info_dict.get(p.device + "_size", None)
Doug Zongker258bf462010-09-20 18:04:41 -0700364 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700365
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700366 if fs_type == "yaffs2":
367 # image size should be increased by 1/64th to account for the
368 # spare area (64 bytes per 2k page)
369 limit = limit / 2048 * (2048+64)
370
Doug Zongkereef39442009-04-02 12:14:19 -0700371 size = len(data)
372 pct = float(size) * 100.0 / limit
373 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
374 if pct >= 99.0:
375 raise ExternalError(msg)
376 elif pct >= 95.0:
377 print
378 print " WARNING: ", msg
379 print
380 elif OPTIONS.verbose:
381 print " ", msg
382
383
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800384def ReadApkCerts(tf_zip):
385 """Given a target_files ZipFile, parse the META/apkcerts.txt file
386 and return a {package: cert} dict."""
387 certmap = {}
388 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
389 line = line.strip()
390 if not line: continue
391 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
392 r'private_key="(.*)"$', line)
393 if m:
394 name, cert, privkey = m.groups()
395 if cert in SPECIAL_CERT_STRINGS and not privkey:
396 certmap[name] = cert
397 elif (cert.endswith(".x509.pem") and
398 privkey.endswith(".pk8") and
399 cert[:-9] == privkey[:-4]):
400 certmap[name] = cert[:-9]
401 else:
402 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
403 return certmap
404
405
Doug Zongkereef39442009-04-02 12:14:19 -0700406COMMON_DOCSTRING = """
407 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700408 Prepend <dir>/bin to the list of places to search for binaries
409 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700410
Doug Zongker05d3dea2009-06-22 11:32:31 -0700411 -s (--device_specific) <file>
412 Path to the python module containing device-specific
413 releasetools code.
414
Doug Zongker8bec09e2009-11-30 15:37:14 -0800415 -x (--extra) <key=value>
416 Add a key/value pair to the 'extras' dict, which device-specific
417 extension code may look at.
418
Doug Zongkereef39442009-04-02 12:14:19 -0700419 -v (--verbose)
420 Show command lines being executed.
421
422 -h (--help)
423 Display this usage message and exit.
424"""
425
426def Usage(docstring):
427 print docstring.rstrip("\n")
428 print COMMON_DOCSTRING
429
430
431def ParseOptions(argv,
432 docstring,
433 extra_opts="", extra_long_opts=(),
434 extra_option_handler=None):
435 """Parse the options in argv and return any arguments that aren't
436 flags. docstring is the calling module's docstring, to be displayed
437 for errors and -h. extra_opts and extra_long_opts are for flags
438 defined by the caller, which are processed by passing them to
439 extra_option_handler."""
440
441 try:
442 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800443 argv, "hvp:s:x:" + extra_opts,
444 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700445 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700446 except getopt.GetoptError, err:
447 Usage(docstring)
448 print "**", str(err), "**"
449 sys.exit(2)
450
451 path_specified = False
452
453 for o, a in opts:
454 if o in ("-h", "--help"):
455 Usage(docstring)
456 sys.exit()
457 elif o in ("-v", "--verbose"):
458 OPTIONS.verbose = True
459 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700460 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700461 elif o in ("-s", "--device_specific"):
462 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800463 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800464 key, value = a.split("=", 1)
465 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700466 else:
467 if extra_option_handler is None or not extra_option_handler(o, a):
468 assert False, "unknown option \"%s\"" % (o,)
469
Doug Zongker602a84e2009-06-18 08:35:12 -0700470 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
471 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700472
473 return args
474
475
476def Cleanup():
477 for i in OPTIONS.tempfiles:
478 if os.path.isdir(i):
479 shutil.rmtree(i)
480 else:
481 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700482
483
484class PasswordManager(object):
485 def __init__(self):
486 self.editor = os.getenv("EDITOR", None)
487 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
488
489 def GetPasswords(self, items):
490 """Get passwords corresponding to each string in 'items',
491 returning a dict. (The dict may have keys in addition to the
492 values in 'items'.)
493
494 Uses the passwords in $ANDROID_PW_FILE if available, letting the
495 user edit that file to add more needed passwords. If no editor is
496 available, or $ANDROID_PW_FILE isn't define, prompts the user
497 interactively in the ordinary way.
498 """
499
500 current = self.ReadFile()
501
502 first = True
503 while True:
504 missing = []
505 for i in items:
506 if i not in current or not current[i]:
507 missing.append(i)
508 # Are all the passwords already in the file?
509 if not missing: return current
510
511 for i in missing:
512 current[i] = ""
513
514 if not first:
515 print "key file %s still missing some passwords." % (self.pwfile,)
516 answer = raw_input("try to edit again? [y]> ").strip()
517 if answer and answer[0] not in 'yY':
518 raise RuntimeError("key passwords unavailable")
519 first = False
520
521 current = self.UpdateAndReadFile(current)
522
523 def PromptResult(self, current):
524 """Prompt the user to enter a value (password) for each key in
525 'current' whose value is fales. Returns a new dict with all the
526 values.
527 """
528 result = {}
529 for k, v in sorted(current.iteritems()):
530 if v:
531 result[k] = v
532 else:
533 while True:
534 result[k] = getpass.getpass("Enter password for %s key> "
535 % (k,)).strip()
536 if result[k]: break
537 return result
538
539 def UpdateAndReadFile(self, current):
540 if not self.editor or not self.pwfile:
541 return self.PromptResult(current)
542
543 f = open(self.pwfile, "w")
544 os.chmod(self.pwfile, 0600)
545 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
546 f.write("# (Additional spaces are harmless.)\n\n")
547
548 first_line = None
549 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
550 sorted.sort()
551 for i, (_, k, v) in enumerate(sorted):
552 f.write("[[[ %s ]]] %s\n" % (v, k))
553 if not v and first_line is None:
554 # position cursor on first line with no password.
555 first_line = i + 4
556 f.close()
557
558 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
559 _, _ = p.communicate()
560
561 return self.ReadFile()
562
563 def ReadFile(self):
564 result = {}
565 if self.pwfile is None: return result
566 try:
567 f = open(self.pwfile, "r")
568 for line in f:
569 line = line.strip()
570 if not line or line[0] == '#': continue
571 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
572 if not m:
573 print "failed to parse password file: ", line
574 else:
575 result[m.group(2)] = m.group(1)
576 f.close()
577 except IOError, e:
578 if e.errno != errno.ENOENT:
579 print "error reading password file: ", str(e)
580 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700581
582
583def ZipWriteStr(zip, filename, data, perms=0644):
584 # use a fixed timestamp so the output is repeatable.
585 zinfo = zipfile.ZipInfo(filename=filename,
586 date_time=(2009, 1, 1, 0, 0, 0))
587 zinfo.compress_type = zip.compression
588 zinfo.external_attr = perms << 16
589 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700590
591
592class DeviceSpecificParams(object):
593 module = None
594 def __init__(self, **kwargs):
595 """Keyword arguments to the constructor become attributes of this
596 object, which is passed to all functions in the device-specific
597 module."""
598 for k, v in kwargs.iteritems():
599 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800600 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700601
602 if self.module is None:
603 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700604 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700605 try:
606 if os.path.isdir(path):
607 info = imp.find_module("releasetools", [path])
608 else:
609 d, f = os.path.split(path)
610 b, x = os.path.splitext(f)
611 if x == ".py":
612 f = b
613 info = imp.find_module(f, [d])
614 self.module = imp.load_module("device_specific", *info)
615 except ImportError:
616 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700617
618 def _DoCall(self, function_name, *args, **kwargs):
619 """Call the named function in the device-specific module, passing
620 the given args and kwargs. The first argument to the call will be
621 the DeviceSpecific object itself. If there is no module, or the
622 module does not define the function, return the value of the
623 'default' kwarg (which itself defaults to None)."""
624 if self.module is None or not hasattr(self.module, function_name):
625 return kwargs.get("default", None)
626 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
627
628 def FullOTA_Assertions(self):
629 """Called after emitting the block of assertions at the top of a
630 full OTA package. Implementations can add whatever additional
631 assertions they like."""
632 return self._DoCall("FullOTA_Assertions")
633
634 def FullOTA_InstallEnd(self):
635 """Called at the end of full OTA installation; typically this is
636 used to install the image for the device's baseband processor."""
637 return self._DoCall("FullOTA_InstallEnd")
638
639 def IncrementalOTA_Assertions(self):
640 """Called after emitting the block of assertions at the top of an
641 incremental OTA package. Implementations can add whatever
642 additional assertions they like."""
643 return self._DoCall("IncrementalOTA_Assertions")
644
645 def IncrementalOTA_VerifyEnd(self):
646 """Called at the end of the verification phase of incremental OTA
647 installation; additional checks can be placed here to abort the
648 script before any changes are made."""
649 return self._DoCall("IncrementalOTA_VerifyEnd")
650
651 def IncrementalOTA_InstallEnd(self):
652 """Called at the end of incremental OTA installation; typically
653 this is used to install the image for the device's baseband
654 processor."""
655 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700656
657class File(object):
658 def __init__(self, name, data):
659 self.name = name
660 self.data = data
661 self.size = len(data)
662 self.sha1 = sha.sha(data).hexdigest()
663
664 def WriteToTemp(self):
665 t = tempfile.NamedTemporaryFile()
666 t.write(self.data)
667 t.flush()
668 return t
669
670 def AddToZip(self, z):
671 ZipWriteStr(z, self.name, self.data)
672
673DIFF_PROGRAM_BY_EXT = {
674 ".gz" : "imgdiff",
675 ".zip" : ["imgdiff", "-z"],
676 ".jar" : ["imgdiff", "-z"],
677 ".apk" : ["imgdiff", "-z"],
678 ".img" : "imgdiff",
679 }
680
681class Difference(object):
682 def __init__(self, tf, sf):
683 self.tf = tf
684 self.sf = sf
685 self.patch = None
686
687 def ComputePatch(self):
688 """Compute the patch (as a string of data) needed to turn sf into
689 tf. Returns the same tuple as GetPatch()."""
690
691 tf = self.tf
692 sf = self.sf
693
694 ext = os.path.splitext(tf.name)[1]
695 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
696
697 ttemp = tf.WriteToTemp()
698 stemp = sf.WriteToTemp()
699
700 ext = os.path.splitext(tf.name)[1]
701
702 try:
703 ptemp = tempfile.NamedTemporaryFile()
704 if isinstance(diff_program, list):
705 cmd = copy.copy(diff_program)
706 else:
707 cmd = [diff_program]
708 cmd.append(stemp.name)
709 cmd.append(ttemp.name)
710 cmd.append(ptemp.name)
711 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
712 _, err = p.communicate()
713 if err or p.returncode != 0:
714 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
715 return None
716 diff = ptemp.read()
717 finally:
718 ptemp.close()
719 stemp.close()
720 ttemp.close()
721
722 self.patch = diff
723 return self.tf, self.sf, self.patch
724
725
726 def GetPatch(self):
727 """Return a tuple (target_file, source_file, patch_data).
728 patch_data may be None if ComputePatch hasn't been called, or if
729 computing the patch failed."""
730 return self.tf, self.sf, self.patch
731
732
733def ComputeDifferences(diffs):
734 """Call ComputePatch on all the Difference objects in 'diffs'."""
735 print len(diffs), "diffs to compute"
736
737 # Do the largest files first, to try and reduce the long-pole effect.
738 by_size = [(i.tf.size, i) for i in diffs]
739 by_size.sort(reverse=True)
740 by_size = [i[1] for i in by_size]
741
742 lock = threading.Lock()
743 diff_iter = iter(by_size) # accessed under lock
744
745 def worker():
746 try:
747 lock.acquire()
748 for d in diff_iter:
749 lock.release()
750 start = time.time()
751 d.ComputePatch()
752 dur = time.time() - start
753 lock.acquire()
754
755 tf, sf, patch = d.GetPatch()
756 if sf.name == tf.name:
757 name = tf.name
758 else:
759 name = "%s (%s)" % (tf.name, sf.name)
760 if patch is None:
761 print "patching failed! %s" % (name,)
762 else:
763 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
764 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
765 lock.release()
766 except Exception, e:
767 print e
768 raise
769
770 # start worker threads; wait for them all to finish.
771 threads = [threading.Thread(target=worker)
772 for i in range(OPTIONS.worker_threads)]
773 for th in threads:
774 th.start()
775 while threads:
776 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700777
778
779# map recovery.fstab's fs_types to mount/format "partition types"
780PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
781 "ext4": "EMMC", "emmc": "EMMC" }
782
783def GetTypeAndDevice(mount_point, info):
784 fstab = info["fstab"]
785 if fstab:
786 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
787 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800788 return None