blob: a7413a009c0aa2755a067eac9bdbabd16a270370 [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:
32 import hashlib
33 sha1 = hashlib.sha1
34except ImportError:
35 import sha
36 sha1 = sha.sha
37
Doug Zongkereef39442009-04-02 12:14:19 -070038# missing in Python 2.4 and before
39if not hasattr(os, "SEEK_SET"):
40 os.SEEK_SET = 0
41
42class Options(object): pass
43OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070044OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070045OPTIONS.verbose = False
46OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070047OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080048OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070049OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070050
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080051
52# Values for "certificate" in apkcerts that mean special things.
53SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
54
55
Doug Zongkereef39442009-04-02 12:14:19 -070056class ExternalError(RuntimeError): pass
57
58
59def Run(args, **kwargs):
60 """Create and return a subprocess.Popen object, printing the command
61 line on the terminal if -v was specified."""
62 if OPTIONS.verbose:
63 print " running: ", " ".join(args)
64 return subprocess.Popen(args, **kwargs)
65
66
Ying Wang7e6d4e42010-12-13 16:25:36 -080067def CloseInheritedPipes():
68 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
69 before doing other work."""
70 if platform.system() != "Darwin":
71 return
72 for d in range(3, 1025):
73 try:
74 stat = os.fstat(d)
75 if stat is not None:
76 pipebit = stat[0] & 0x1000
77 if pipebit != 0:
78 os.close(d)
79 except OSError:
80 pass
81
82
Doug Zongker37974732010-09-16 17:44:38 -070083def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070084 """Read and parse the META/misc_info.txt key/value pairs from the
85 input target files and return a dict."""
86
87 d = {}
88 try:
Doug Zongker37974732010-09-16 17:44:38 -070089 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070090 line = line.strip()
91 if not line or line.startswith("#"): continue
92 k, v = line.split("=", 1)
93 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070094 except KeyError:
95 # ok if misc_info.txt doesn't exist
96 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070097
Doug Zongker37974732010-09-16 17:44:38 -070098 # backwards compatibility: These values used to be in their own
99 # files. Look for them, in case we're processing an old
100 # target_files zip.
101
102 if "mkyaffs2_extra_flags" not in d:
103 try:
104 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
105 except KeyError:
106 # ok if flags don't exist
107 pass
108
109 if "recovery_api_version" not in d:
110 try:
111 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
112 except KeyError:
113 raise ValueError("can't find recovery API version in input target-files")
114
115 if "tool_extensions" not in d:
116 try:
117 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
118 except KeyError:
119 # ok if extensions don't exist
120 pass
121
122 try:
123 data = zip.read("META/imagesizes.txt")
124 for line in data.split("\n"):
125 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700126 name, value = line.split(" ", 1)
127 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700128 if name == "blocksize":
129 d[name] = value
130 else:
131 d[name + "_size"] = value
132 except KeyError:
133 pass
134
135 def makeint(key):
136 if key in d:
137 d[key] = int(d[key], 0)
138
139 makeint("recovery_api_version")
140 makeint("blocksize")
141 makeint("system_size")
142 makeint("userdata_size")
143 makeint("recovery_size")
144 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700145
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700146 d["fstab"] = LoadRecoveryFSTab(zip)
147 if not d["fstab"]:
148 if "fs_type" not in d: d["fs_type"] = "yaffs2"
149 if "partition_type" not in d: d["partition_type"] = "MTD"
150
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700151 return d
152
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700153def LoadRecoveryFSTab(zip):
154 class Partition(object):
155 pass
156
157 try:
158 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
159 except KeyError:
160 # older target-files that doesn't have a recovery.fstab; fall back
161 # to the fs_type and partition_type keys.
162 return
163
164 d = {}
165 for line in data.split("\n"):
166 line = line.strip()
167 if not line or line.startswith("#"): continue
168 pieces = line.split()
169 if not (3 <= len(pieces) <= 4):
170 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
171
172 p = Partition()
173 p.mount_point = pieces[0]
174 p.fs_type = pieces[1]
175 p.device = pieces[2]
176 if len(pieces) == 4:
177 p.device2 = pieces[3]
178 else:
179 p.device2 = None
180
181 d[p.mount_point] = p
182 return d
183
184
Doug Zongker37974732010-09-16 17:44:38 -0700185def DumpInfoDict(d):
186 for k, v in sorted(d.items()):
187 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700188
Doug Zongkereef39442009-04-02 12:14:19 -0700189def BuildBootableImage(sourcedir):
190 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700191 'sourcedir'), and turn them into a boot image. Return the image
192 data, or None if sourcedir does not appear to contains files for
193 building the requested image."""
194
195 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
196 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
197 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700198
199 ramdisk_img = tempfile.NamedTemporaryFile()
200 img = tempfile.NamedTemporaryFile()
201
202 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
203 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700204 p2 = Run(["minigzip"],
205 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700206
207 p2.wait()
208 p1.wait()
209 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700210 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700211
Doug Zongker38a649f2009-06-17 09:07:09 -0700212 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
213
Doug Zongker171f1cd2009-06-15 22:36:37 -0700214 fn = os.path.join(sourcedir, "cmdline")
215 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700216 cmd.append("--cmdline")
217 cmd.append(open(fn).read().rstrip("\n"))
218
219 fn = os.path.join(sourcedir, "base")
220 if os.access(fn, os.F_OK):
221 cmd.append("--base")
222 cmd.append(open(fn).read().rstrip("\n"))
223
Ying Wang4de6b5b2010-08-25 14:29:34 -0700224 fn = os.path.join(sourcedir, "pagesize")
225 if os.access(fn, os.F_OK):
226 cmd.append("--pagesize")
227 cmd.append(open(fn).read().rstrip("\n"))
228
Doug Zongker38a649f2009-06-17 09:07:09 -0700229 cmd.extend(["--ramdisk", ramdisk_img.name,
230 "--output", img.name])
231
232 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700233 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700234 assert p.returncode == 0, "mkbootimg of %s image failed" % (
235 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700236
237 img.seek(os.SEEK_SET, 0)
238 data = img.read()
239
240 ramdisk_img.close()
241 img.close()
242
243 return data
244
245
Doug Zongker55d93282011-01-25 17:03:34 -0800246def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir):
247 """Return a File object (with name 'name') with the desired bootable
248 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
249 'prebuilt_name', otherwise construct it from the source files in
250 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700251
Doug Zongker55d93282011-01-25 17:03:34 -0800252 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
253 if os.path.exists(prebuilt_path):
254 print "using prebuilt %s..." % (prebuilt_name,)
255 return File.FromLocalFile(name, prebuilt_path)
256 else:
257 print "building image from target_files %s..." % (tree_subdir,)
258 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir)))
259
Doug Zongkereef39442009-04-02 12:14:19 -0700260
Doug Zongker75f17362009-12-08 13:46:44 -0800261def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800262 """Unzip the given archive into a temporary directory and return the name.
263
264 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
265 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
266
267 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
268 main file), open for reading.
269 """
Doug Zongkereef39442009-04-02 12:14:19 -0700270
271 tmp = tempfile.mkdtemp(prefix="targetfiles-")
272 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800273
274 def unzip_to_dir(filename, dirname):
275 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
276 if pattern is not None:
277 cmd.append(pattern)
278 p = Run(cmd, stdout=subprocess.PIPE)
279 p.communicate()
280 if p.returncode != 0:
281 raise ExternalError("failed to unzip input target-files \"%s\"" %
282 (filename,))
283
284 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
285 if m:
286 unzip_to_dir(m.group(1), tmp)
287 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
288 filename = m.group(1)
289 else:
290 unzip_to_dir(filename, tmp)
291
292 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700293
294
295def GetKeyPasswords(keylist):
296 """Given a list of keys, prompt the user to enter passwords for
297 those which require them. Return a {key: password} dict. password
298 will be None if the key has no password."""
299
Doug Zongker8ce7c252009-05-22 13:34:54 -0700300 no_passwords = []
301 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700302 devnull = open("/dev/null", "w+b")
303 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800304 # We don't need a password for things that aren't really keys.
305 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700306 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700307 continue
308
Doug Zongker602a84e2009-06-18 08:35:12 -0700309 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
310 "-inform", "DER", "-nocrypt"],
311 stdin=devnull.fileno(),
312 stdout=devnull.fileno(),
313 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700314 p.communicate()
315 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700316 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700317 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700318 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700319 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700320
321 key_passwords = PasswordManager().GetPasswords(need_passwords)
322 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700323 return key_passwords
324
325
Doug Zongker951495f2009-08-14 12:44:19 -0700326def SignFile(input_name, output_name, key, password, align=None,
327 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700328 """Sign the input_name zip/jar/apk, producing output_name. Use the
329 given key and password (the latter may be None if the key does not
330 have a password.
331
332 If align is an integer > 1, zipalign is run to align stored files in
333 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700334
335 If whole_file is true, use the "-w" option to SignApk to embed a
336 signature that covers the whole file in the archive comment of the
337 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700338 """
Doug Zongker951495f2009-08-14 12:44:19 -0700339
Doug Zongkereef39442009-04-02 12:14:19 -0700340 if align == 0 or align == 1:
341 align = None
342
343 if align:
344 temp = tempfile.NamedTemporaryFile()
345 sign_name = temp.name
346 else:
347 sign_name = output_name
348
Doug Zongker09cf5602009-08-14 15:25:06 -0700349 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700350 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
351 if whole_file:
352 cmd.append("-w")
353 cmd.extend([key + ".x509.pem", key + ".pk8",
354 input_name, sign_name])
355
356 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700357 if password is not None:
358 password += "\n"
359 p.communicate(password)
360 if p.returncode != 0:
361 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
362
363 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700364 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700365 p.communicate()
366 if p.returncode != 0:
367 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
368 temp.close()
369
370
Doug Zongker37974732010-09-16 17:44:38 -0700371def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700372 """Check the data string passed against the max size limit, if
373 any, for the given target. Raise exception if the data is too big.
374 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700375
Doug Zongker1684d9c2010-09-17 07:44:38 -0700376 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700377 mount_point = "/" + target
378
379 if info_dict["fstab"]:
380 if mount_point == "/userdata": mount_point = "/data"
381 p = info_dict["fstab"][mount_point]
382 fs_type = p.fs_type
383 limit = info_dict.get(p.device + "_size", None)
384 else:
385 fs_type = info_dict.get("fs_type", None)
386 limit = info_dict.get(target + "_size", None)
387 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700389 if fs_type == "yaffs2":
390 # image size should be increased by 1/64th to account for the
391 # spare area (64 bytes per 2k page)
392 limit = limit / 2048 * (2048+64)
Doug Zongker486de122010-09-16 14:01:56 -0700393 size = len(data)
394 pct = float(size) * 100.0 / limit
395 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
396 if pct >= 99.0:
397 raise ExternalError(msg)
398 elif pct >= 95.0:
399 print
400 print " WARNING: ", msg
401 print
402 elif OPTIONS.verbose:
403 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700404
405
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800406def ReadApkCerts(tf_zip):
407 """Given a target_files ZipFile, parse the META/apkcerts.txt file
408 and return a {package: cert} dict."""
409 certmap = {}
410 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
411 line = line.strip()
412 if not line: continue
413 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
414 r'private_key="(.*)"$', line)
415 if m:
416 name, cert, privkey = m.groups()
417 if cert in SPECIAL_CERT_STRINGS and not privkey:
418 certmap[name] = cert
419 elif (cert.endswith(".x509.pem") and
420 privkey.endswith(".pk8") and
421 cert[:-9] == privkey[:-4]):
422 certmap[name] = cert[:-9]
423 else:
424 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
425 return certmap
426
427
Doug Zongkereef39442009-04-02 12:14:19 -0700428COMMON_DOCSTRING = """
429 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700430 Prepend <dir>/bin to the list of places to search for binaries
431 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700432
Doug Zongker05d3dea2009-06-22 11:32:31 -0700433 -s (--device_specific) <file>
434 Path to the python module containing device-specific
435 releasetools code.
436
Doug Zongker8bec09e2009-11-30 15:37:14 -0800437 -x (--extra) <key=value>
438 Add a key/value pair to the 'extras' dict, which device-specific
439 extension code may look at.
440
Doug Zongkereef39442009-04-02 12:14:19 -0700441 -v (--verbose)
442 Show command lines being executed.
443
444 -h (--help)
445 Display this usage message and exit.
446"""
447
448def Usage(docstring):
449 print docstring.rstrip("\n")
450 print COMMON_DOCSTRING
451
452
453def ParseOptions(argv,
454 docstring,
455 extra_opts="", extra_long_opts=(),
456 extra_option_handler=None):
457 """Parse the options in argv and return any arguments that aren't
458 flags. docstring is the calling module's docstring, to be displayed
459 for errors and -h. extra_opts and extra_long_opts are for flags
460 defined by the caller, which are processed by passing them to
461 extra_option_handler."""
462
463 try:
464 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800465 argv, "hvp:s:x:" + extra_opts,
466 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700467 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700468 except getopt.GetoptError, err:
469 Usage(docstring)
470 print "**", str(err), "**"
471 sys.exit(2)
472
473 path_specified = False
474
475 for o, a in opts:
476 if o in ("-h", "--help"):
477 Usage(docstring)
478 sys.exit()
479 elif o in ("-v", "--verbose"):
480 OPTIONS.verbose = True
481 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700482 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700483 elif o in ("-s", "--device_specific"):
484 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800485 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800486 key, value = a.split("=", 1)
487 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700488 else:
489 if extra_option_handler is None or not extra_option_handler(o, a):
490 assert False, "unknown option \"%s\"" % (o,)
491
Doug Zongker602a84e2009-06-18 08:35:12 -0700492 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
493 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700494
495 return args
496
497
498def Cleanup():
499 for i in OPTIONS.tempfiles:
500 if os.path.isdir(i):
501 shutil.rmtree(i)
502 else:
503 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700504
505
506class PasswordManager(object):
507 def __init__(self):
508 self.editor = os.getenv("EDITOR", None)
509 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
510
511 def GetPasswords(self, items):
512 """Get passwords corresponding to each string in 'items',
513 returning a dict. (The dict may have keys in addition to the
514 values in 'items'.)
515
516 Uses the passwords in $ANDROID_PW_FILE if available, letting the
517 user edit that file to add more needed passwords. If no editor is
518 available, or $ANDROID_PW_FILE isn't define, prompts the user
519 interactively in the ordinary way.
520 """
521
522 current = self.ReadFile()
523
524 first = True
525 while True:
526 missing = []
527 for i in items:
528 if i not in current or not current[i]:
529 missing.append(i)
530 # Are all the passwords already in the file?
531 if not missing: return current
532
533 for i in missing:
534 current[i] = ""
535
536 if not first:
537 print "key file %s still missing some passwords." % (self.pwfile,)
538 answer = raw_input("try to edit again? [y]> ").strip()
539 if answer and answer[0] not in 'yY':
540 raise RuntimeError("key passwords unavailable")
541 first = False
542
543 current = self.UpdateAndReadFile(current)
544
545 def PromptResult(self, current):
546 """Prompt the user to enter a value (password) for each key in
547 'current' whose value is fales. Returns a new dict with all the
548 values.
549 """
550 result = {}
551 for k, v in sorted(current.iteritems()):
552 if v:
553 result[k] = v
554 else:
555 while True:
556 result[k] = getpass.getpass("Enter password for %s key> "
557 % (k,)).strip()
558 if result[k]: break
559 return result
560
561 def UpdateAndReadFile(self, current):
562 if not self.editor or not self.pwfile:
563 return self.PromptResult(current)
564
565 f = open(self.pwfile, "w")
566 os.chmod(self.pwfile, 0600)
567 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
568 f.write("# (Additional spaces are harmless.)\n\n")
569
570 first_line = None
571 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
572 sorted.sort()
573 for i, (_, k, v) in enumerate(sorted):
574 f.write("[[[ %s ]]] %s\n" % (v, k))
575 if not v and first_line is None:
576 # position cursor on first line with no password.
577 first_line = i + 4
578 f.close()
579
580 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
581 _, _ = p.communicate()
582
583 return self.ReadFile()
584
585 def ReadFile(self):
586 result = {}
587 if self.pwfile is None: return result
588 try:
589 f = open(self.pwfile, "r")
590 for line in f:
591 line = line.strip()
592 if not line or line[0] == '#': continue
593 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
594 if not m:
595 print "failed to parse password file: ", line
596 else:
597 result[m.group(2)] = m.group(1)
598 f.close()
599 except IOError, e:
600 if e.errno != errno.ENOENT:
601 print "error reading password file: ", str(e)
602 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700603
604
605def ZipWriteStr(zip, filename, data, perms=0644):
606 # use a fixed timestamp so the output is repeatable.
607 zinfo = zipfile.ZipInfo(filename=filename,
608 date_time=(2009, 1, 1, 0, 0, 0))
609 zinfo.compress_type = zip.compression
610 zinfo.external_attr = perms << 16
611 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700612
613
614class DeviceSpecificParams(object):
615 module = None
616 def __init__(self, **kwargs):
617 """Keyword arguments to the constructor become attributes of this
618 object, which is passed to all functions in the device-specific
619 module."""
620 for k, v in kwargs.iteritems():
621 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800622 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700623
624 if self.module is None:
625 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700626 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700627 try:
628 if os.path.isdir(path):
629 info = imp.find_module("releasetools", [path])
630 else:
631 d, f = os.path.split(path)
632 b, x = os.path.splitext(f)
633 if x == ".py":
634 f = b
635 info = imp.find_module(f, [d])
636 self.module = imp.load_module("device_specific", *info)
637 except ImportError:
638 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700639
640 def _DoCall(self, function_name, *args, **kwargs):
641 """Call the named function in the device-specific module, passing
642 the given args and kwargs. The first argument to the call will be
643 the DeviceSpecific object itself. If there is no module, or the
644 module does not define the function, return the value of the
645 'default' kwarg (which itself defaults to None)."""
646 if self.module is None or not hasattr(self.module, function_name):
647 return kwargs.get("default", None)
648 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
649
650 def FullOTA_Assertions(self):
651 """Called after emitting the block of assertions at the top of a
652 full OTA package. Implementations can add whatever additional
653 assertions they like."""
654 return self._DoCall("FullOTA_Assertions")
655
656 def FullOTA_InstallEnd(self):
657 """Called at the end of full OTA installation; typically this is
658 used to install the image for the device's baseband processor."""
659 return self._DoCall("FullOTA_InstallEnd")
660
661 def IncrementalOTA_Assertions(self):
662 """Called after emitting the block of assertions at the top of an
663 incremental OTA package. Implementations can add whatever
664 additional assertions they like."""
665 return self._DoCall("IncrementalOTA_Assertions")
666
667 def IncrementalOTA_VerifyEnd(self):
668 """Called at the end of the verification phase of incremental OTA
669 installation; additional checks can be placed here to abort the
670 script before any changes are made."""
671 return self._DoCall("IncrementalOTA_VerifyEnd")
672
673 def IncrementalOTA_InstallEnd(self):
674 """Called at the end of incremental OTA installation; typically
675 this is used to install the image for the device's baseband
676 processor."""
677 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700678
679class File(object):
680 def __init__(self, name, data):
681 self.name = name
682 self.data = data
683 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800684 self.sha1 = sha1(data).hexdigest()
685
686 @classmethod
687 def FromLocalFile(cls, name, diskname):
688 f = open(diskname, "rb")
689 data = f.read()
690 f.close()
691 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700692
693 def WriteToTemp(self):
694 t = tempfile.NamedTemporaryFile()
695 t.write(self.data)
696 t.flush()
697 return t
698
699 def AddToZip(self, z):
700 ZipWriteStr(z, self.name, self.data)
701
702DIFF_PROGRAM_BY_EXT = {
703 ".gz" : "imgdiff",
704 ".zip" : ["imgdiff", "-z"],
705 ".jar" : ["imgdiff", "-z"],
706 ".apk" : ["imgdiff", "-z"],
707 ".img" : "imgdiff",
708 }
709
710class Difference(object):
711 def __init__(self, tf, sf):
712 self.tf = tf
713 self.sf = sf
714 self.patch = None
715
716 def ComputePatch(self):
717 """Compute the patch (as a string of data) needed to turn sf into
718 tf. Returns the same tuple as GetPatch()."""
719
720 tf = self.tf
721 sf = self.sf
722
723 ext = os.path.splitext(tf.name)[1]
724 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
725
726 ttemp = tf.WriteToTemp()
727 stemp = sf.WriteToTemp()
728
729 ext = os.path.splitext(tf.name)[1]
730
731 try:
732 ptemp = tempfile.NamedTemporaryFile()
733 if isinstance(diff_program, list):
734 cmd = copy.copy(diff_program)
735 else:
736 cmd = [diff_program]
737 cmd.append(stemp.name)
738 cmd.append(ttemp.name)
739 cmd.append(ptemp.name)
740 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
741 _, err = p.communicate()
742 if err or p.returncode != 0:
743 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
744 return None
745 diff = ptemp.read()
746 finally:
747 ptemp.close()
748 stemp.close()
749 ttemp.close()
750
751 self.patch = diff
752 return self.tf, self.sf, self.patch
753
754
755 def GetPatch(self):
756 """Return a tuple (target_file, source_file, patch_data).
757 patch_data may be None if ComputePatch hasn't been called, or if
758 computing the patch failed."""
759 return self.tf, self.sf, self.patch
760
761
762def ComputeDifferences(diffs):
763 """Call ComputePatch on all the Difference objects in 'diffs'."""
764 print len(diffs), "diffs to compute"
765
766 # Do the largest files first, to try and reduce the long-pole effect.
767 by_size = [(i.tf.size, i) for i in diffs]
768 by_size.sort(reverse=True)
769 by_size = [i[1] for i in by_size]
770
771 lock = threading.Lock()
772 diff_iter = iter(by_size) # accessed under lock
773
774 def worker():
775 try:
776 lock.acquire()
777 for d in diff_iter:
778 lock.release()
779 start = time.time()
780 d.ComputePatch()
781 dur = time.time() - start
782 lock.acquire()
783
784 tf, sf, patch = d.GetPatch()
785 if sf.name == tf.name:
786 name = tf.name
787 else:
788 name = "%s (%s)" % (tf.name, sf.name)
789 if patch is None:
790 print "patching failed! %s" % (name,)
791 else:
792 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
793 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
794 lock.release()
795 except Exception, e:
796 print e
797 raise
798
799 # start worker threads; wait for them all to finish.
800 threads = [threading.Thread(target=worker)
801 for i in range(OPTIONS.worker_threads)]
802 for th in threads:
803 th.start()
804 while threads:
805 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700806
807
808# map recovery.fstab's fs_types to mount/format "partition types"
809PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
810 "ext4": "EMMC", "emmc": "EMMC" }
811
812def GetTypeAndDevice(mount_point, info):
813 fstab = info["fstab"]
814 if fstab:
815 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
816 else:
817 devices = {"/boot": "boot",
818 "/recovery": "recovery",
819 "/radio": "radio",
820 "/data": "userdata",
821 "/cache": "cache"}
822 return info["partition_type"], info.get("partition_path", "") + devices[mount_point]