blob: b182088db8ec98ea8736c7f467b0cf4a3dda5eff [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]
Doug Zongker086cbb02011-02-17 15:54:20 -0800176 p.length = 0
177 options = None
178 if len(pieces) >= 4:
179 if pieces[3].startswith("/"):
180 p.device2 = pieces[3]
181 if len(pieces) >= 5:
182 options = pieces[4]
183 else:
184 p.device2 = None
185 options = pieces[3]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700186 else:
187 p.device2 = None
188
Doug Zongker086cbb02011-02-17 15:54:20 -0800189 if options:
190 options = options.split(",")
191 for i in options:
192 if i.startswith("length="):
193 p.length = int(i[7:])
194 else:
195 print "%s: unknown option \"%s\"" % (p.mount_point, i)
196
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700197 d[p.mount_point] = p
198 return d
199
200
Doug Zongker37974732010-09-16 17:44:38 -0700201def DumpInfoDict(d):
202 for k, v in sorted(d.items()):
203 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700204
Doug Zongkereef39442009-04-02 12:14:19 -0700205def BuildBootableImage(sourcedir):
206 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700207 'sourcedir'), and turn them into a boot image. Return the image
208 data, or None if sourcedir does not appear to contains files for
209 building the requested image."""
210
211 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
212 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
213 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700214
215 ramdisk_img = tempfile.NamedTemporaryFile()
216 img = tempfile.NamedTemporaryFile()
217
218 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
219 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700220 p2 = Run(["minigzip"],
221 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700222
223 p2.wait()
224 p1.wait()
225 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700226 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700227
Doug Zongker38a649f2009-06-17 09:07:09 -0700228 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
229
Doug Zongker171f1cd2009-06-15 22:36:37 -0700230 fn = os.path.join(sourcedir, "cmdline")
231 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700232 cmd.append("--cmdline")
233 cmd.append(open(fn).read().rstrip("\n"))
234
235 fn = os.path.join(sourcedir, "base")
236 if os.access(fn, os.F_OK):
237 cmd.append("--base")
238 cmd.append(open(fn).read().rstrip("\n"))
239
Ying Wang4de6b5b2010-08-25 14:29:34 -0700240 fn = os.path.join(sourcedir, "pagesize")
241 if os.access(fn, os.F_OK):
242 cmd.append("--pagesize")
243 cmd.append(open(fn).read().rstrip("\n"))
244
Doug Zongker38a649f2009-06-17 09:07:09 -0700245 cmd.extend(["--ramdisk", ramdisk_img.name,
246 "--output", img.name])
247
248 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700249 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700250 assert p.returncode == 0, "mkbootimg of %s image failed" % (
251 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700252
253 img.seek(os.SEEK_SET, 0)
254 data = img.read()
255
256 ramdisk_img.close()
257 img.close()
258
259 return data
260
261
Doug Zongker55d93282011-01-25 17:03:34 -0800262def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir):
263 """Return a File object (with name 'name') with the desired bootable
264 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
265 'prebuilt_name', otherwise construct it from the source files in
266 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700267
Doug Zongker55d93282011-01-25 17:03:34 -0800268 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
269 if os.path.exists(prebuilt_path):
270 print "using prebuilt %s..." % (prebuilt_name,)
271 return File.FromLocalFile(name, prebuilt_path)
272 else:
273 print "building image from target_files %s..." % (tree_subdir,)
274 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir)))
275
Doug Zongkereef39442009-04-02 12:14:19 -0700276
Doug Zongker75f17362009-12-08 13:46:44 -0800277def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800278 """Unzip the given archive into a temporary directory and return the name.
279
280 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
281 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
282
283 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
284 main file), open for reading.
285 """
Doug Zongkereef39442009-04-02 12:14:19 -0700286
287 tmp = tempfile.mkdtemp(prefix="targetfiles-")
288 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800289
290 def unzip_to_dir(filename, dirname):
291 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
292 if pattern is not None:
293 cmd.append(pattern)
294 p = Run(cmd, stdout=subprocess.PIPE)
295 p.communicate()
296 if p.returncode != 0:
297 raise ExternalError("failed to unzip input target-files \"%s\"" %
298 (filename,))
299
300 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
301 if m:
302 unzip_to_dir(m.group(1), tmp)
303 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
304 filename = m.group(1)
305 else:
306 unzip_to_dir(filename, tmp)
307
308 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700309
310
311def GetKeyPasswords(keylist):
312 """Given a list of keys, prompt the user to enter passwords for
313 those which require them. Return a {key: password} dict. password
314 will be None if the key has no password."""
315
Doug Zongker8ce7c252009-05-22 13:34:54 -0700316 no_passwords = []
317 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700318 devnull = open("/dev/null", "w+b")
319 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800320 # We don't need a password for things that aren't really keys.
321 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700322 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700323 continue
324
Doug Zongker602a84e2009-06-18 08:35:12 -0700325 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
326 "-inform", "DER", "-nocrypt"],
327 stdin=devnull.fileno(),
328 stdout=devnull.fileno(),
329 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700330 p.communicate()
331 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700332 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700333 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700334 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700335 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700336
337 key_passwords = PasswordManager().GetPasswords(need_passwords)
338 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700339 return key_passwords
340
341
Doug Zongker951495f2009-08-14 12:44:19 -0700342def SignFile(input_name, output_name, key, password, align=None,
343 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700344 """Sign the input_name zip/jar/apk, producing output_name. Use the
345 given key and password (the latter may be None if the key does not
346 have a password.
347
348 If align is an integer > 1, zipalign is run to align stored files in
349 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700350
351 If whole_file is true, use the "-w" option to SignApk to embed a
352 signature that covers the whole file in the archive comment of the
353 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700354 """
Doug Zongker951495f2009-08-14 12:44:19 -0700355
Doug Zongkereef39442009-04-02 12:14:19 -0700356 if align == 0 or align == 1:
357 align = None
358
359 if align:
360 temp = tempfile.NamedTemporaryFile()
361 sign_name = temp.name
362 else:
363 sign_name = output_name
364
Doug Zongker09cf5602009-08-14 15:25:06 -0700365 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700366 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
367 if whole_file:
368 cmd.append("-w")
369 cmd.extend([key + ".x509.pem", key + ".pk8",
370 input_name, sign_name])
371
372 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700373 if password is not None:
374 password += "\n"
375 p.communicate(password)
376 if p.returncode != 0:
377 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
378
379 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700380 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700381 p.communicate()
382 if p.returncode != 0:
383 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
384 temp.close()
385
386
Doug Zongker37974732010-09-16 17:44:38 -0700387def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700388 """Check the data string passed against the max size limit, if
389 any, for the given target. Raise exception if the data is too big.
390 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700391
Doug Zongker1684d9c2010-09-17 07:44:38 -0700392 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700393 mount_point = "/" + target
394
395 if info_dict["fstab"]:
396 if mount_point == "/userdata": mount_point = "/data"
397 p = info_dict["fstab"][mount_point]
398 fs_type = p.fs_type
399 limit = info_dict.get(p.device + "_size", None)
400 else:
401 fs_type = info_dict.get("fs_type", None)
402 limit = info_dict.get(target + "_size", None)
403 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700404
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700405 if fs_type == "yaffs2":
406 # image size should be increased by 1/64th to account for the
407 # spare area (64 bytes per 2k page)
408 limit = limit / 2048 * (2048+64)
Doug Zongker486de122010-09-16 14:01:56 -0700409 size = len(data)
410 pct = float(size) * 100.0 / limit
411 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
412 if pct >= 99.0:
413 raise ExternalError(msg)
414 elif pct >= 95.0:
415 print
416 print " WARNING: ", msg
417 print
418 elif OPTIONS.verbose:
419 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700420
421
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800422def ReadApkCerts(tf_zip):
423 """Given a target_files ZipFile, parse the META/apkcerts.txt file
424 and return a {package: cert} dict."""
425 certmap = {}
426 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
427 line = line.strip()
428 if not line: continue
429 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
430 r'private_key="(.*)"$', line)
431 if m:
432 name, cert, privkey = m.groups()
433 if cert in SPECIAL_CERT_STRINGS and not privkey:
434 certmap[name] = cert
435 elif (cert.endswith(".x509.pem") and
436 privkey.endswith(".pk8") and
437 cert[:-9] == privkey[:-4]):
438 certmap[name] = cert[:-9]
439 else:
440 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
441 return certmap
442
443
Doug Zongkereef39442009-04-02 12:14:19 -0700444COMMON_DOCSTRING = """
445 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700446 Prepend <dir>/bin to the list of places to search for binaries
447 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700448
Doug Zongker05d3dea2009-06-22 11:32:31 -0700449 -s (--device_specific) <file>
450 Path to the python module containing device-specific
451 releasetools code.
452
Doug Zongker8bec09e2009-11-30 15:37:14 -0800453 -x (--extra) <key=value>
454 Add a key/value pair to the 'extras' dict, which device-specific
455 extension code may look at.
456
Doug Zongkereef39442009-04-02 12:14:19 -0700457 -v (--verbose)
458 Show command lines being executed.
459
460 -h (--help)
461 Display this usage message and exit.
462"""
463
464def Usage(docstring):
465 print docstring.rstrip("\n")
466 print COMMON_DOCSTRING
467
468
469def ParseOptions(argv,
470 docstring,
471 extra_opts="", extra_long_opts=(),
472 extra_option_handler=None):
473 """Parse the options in argv and return any arguments that aren't
474 flags. docstring is the calling module's docstring, to be displayed
475 for errors and -h. extra_opts and extra_long_opts are for flags
476 defined by the caller, which are processed by passing them to
477 extra_option_handler."""
478
479 try:
480 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800481 argv, "hvp:s:x:" + extra_opts,
482 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700483 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700484 except getopt.GetoptError, err:
485 Usage(docstring)
486 print "**", str(err), "**"
487 sys.exit(2)
488
489 path_specified = False
490
491 for o, a in opts:
492 if o in ("-h", "--help"):
493 Usage(docstring)
494 sys.exit()
495 elif o in ("-v", "--verbose"):
496 OPTIONS.verbose = True
497 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700498 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700499 elif o in ("-s", "--device_specific"):
500 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800501 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800502 key, value = a.split("=", 1)
503 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700504 else:
505 if extra_option_handler is None or not extra_option_handler(o, a):
506 assert False, "unknown option \"%s\"" % (o,)
507
Doug Zongker602a84e2009-06-18 08:35:12 -0700508 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
509 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700510
511 return args
512
513
514def Cleanup():
515 for i in OPTIONS.tempfiles:
516 if os.path.isdir(i):
517 shutil.rmtree(i)
518 else:
519 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700520
521
522class PasswordManager(object):
523 def __init__(self):
524 self.editor = os.getenv("EDITOR", None)
525 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
526
527 def GetPasswords(self, items):
528 """Get passwords corresponding to each string in 'items',
529 returning a dict. (The dict may have keys in addition to the
530 values in 'items'.)
531
532 Uses the passwords in $ANDROID_PW_FILE if available, letting the
533 user edit that file to add more needed passwords. If no editor is
534 available, or $ANDROID_PW_FILE isn't define, prompts the user
535 interactively in the ordinary way.
536 """
537
538 current = self.ReadFile()
539
540 first = True
541 while True:
542 missing = []
543 for i in items:
544 if i not in current or not current[i]:
545 missing.append(i)
546 # Are all the passwords already in the file?
547 if not missing: return current
548
549 for i in missing:
550 current[i] = ""
551
552 if not first:
553 print "key file %s still missing some passwords." % (self.pwfile,)
554 answer = raw_input("try to edit again? [y]> ").strip()
555 if answer and answer[0] not in 'yY':
556 raise RuntimeError("key passwords unavailable")
557 first = False
558
559 current = self.UpdateAndReadFile(current)
560
561 def PromptResult(self, current):
562 """Prompt the user to enter a value (password) for each key in
563 'current' whose value is fales. Returns a new dict with all the
564 values.
565 """
566 result = {}
567 for k, v in sorted(current.iteritems()):
568 if v:
569 result[k] = v
570 else:
571 while True:
572 result[k] = getpass.getpass("Enter password for %s key> "
573 % (k,)).strip()
574 if result[k]: break
575 return result
576
577 def UpdateAndReadFile(self, current):
578 if not self.editor or not self.pwfile:
579 return self.PromptResult(current)
580
581 f = open(self.pwfile, "w")
582 os.chmod(self.pwfile, 0600)
583 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
584 f.write("# (Additional spaces are harmless.)\n\n")
585
586 first_line = None
587 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
588 sorted.sort()
589 for i, (_, k, v) in enumerate(sorted):
590 f.write("[[[ %s ]]] %s\n" % (v, k))
591 if not v and first_line is None:
592 # position cursor on first line with no password.
593 first_line = i + 4
594 f.close()
595
596 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
597 _, _ = p.communicate()
598
599 return self.ReadFile()
600
601 def ReadFile(self):
602 result = {}
603 if self.pwfile is None: return result
604 try:
605 f = open(self.pwfile, "r")
606 for line in f:
607 line = line.strip()
608 if not line or line[0] == '#': continue
609 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
610 if not m:
611 print "failed to parse password file: ", line
612 else:
613 result[m.group(2)] = m.group(1)
614 f.close()
615 except IOError, e:
616 if e.errno != errno.ENOENT:
617 print "error reading password file: ", str(e)
618 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700619
620
621def ZipWriteStr(zip, filename, data, perms=0644):
622 # use a fixed timestamp so the output is repeatable.
623 zinfo = zipfile.ZipInfo(filename=filename,
624 date_time=(2009, 1, 1, 0, 0, 0))
625 zinfo.compress_type = zip.compression
626 zinfo.external_attr = perms << 16
627 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700628
629
630class DeviceSpecificParams(object):
631 module = None
632 def __init__(self, **kwargs):
633 """Keyword arguments to the constructor become attributes of this
634 object, which is passed to all functions in the device-specific
635 module."""
636 for k, v in kwargs.iteritems():
637 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800638 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700639
640 if self.module is None:
641 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700642 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700643 try:
644 if os.path.isdir(path):
645 info = imp.find_module("releasetools", [path])
646 else:
647 d, f = os.path.split(path)
648 b, x = os.path.splitext(f)
649 if x == ".py":
650 f = b
651 info = imp.find_module(f, [d])
652 self.module = imp.load_module("device_specific", *info)
653 except ImportError:
654 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700655
656 def _DoCall(self, function_name, *args, **kwargs):
657 """Call the named function in the device-specific module, passing
658 the given args and kwargs. The first argument to the call will be
659 the DeviceSpecific object itself. If there is no module, or the
660 module does not define the function, return the value of the
661 'default' kwarg (which itself defaults to None)."""
662 if self.module is None or not hasattr(self.module, function_name):
663 return kwargs.get("default", None)
664 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
665
666 def FullOTA_Assertions(self):
667 """Called after emitting the block of assertions at the top of a
668 full OTA package. Implementations can add whatever additional
669 assertions they like."""
670 return self._DoCall("FullOTA_Assertions")
671
672 def FullOTA_InstallEnd(self):
673 """Called at the end of full OTA installation; typically this is
674 used to install the image for the device's baseband processor."""
675 return self._DoCall("FullOTA_InstallEnd")
676
677 def IncrementalOTA_Assertions(self):
678 """Called after emitting the block of assertions at the top of an
679 incremental OTA package. Implementations can add whatever
680 additional assertions they like."""
681 return self._DoCall("IncrementalOTA_Assertions")
682
683 def IncrementalOTA_VerifyEnd(self):
684 """Called at the end of the verification phase of incremental OTA
685 installation; additional checks can be placed here to abort the
686 script before any changes are made."""
687 return self._DoCall("IncrementalOTA_VerifyEnd")
688
689 def IncrementalOTA_InstallEnd(self):
690 """Called at the end of incremental OTA installation; typically
691 this is used to install the image for the device's baseband
692 processor."""
693 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700694
695class File(object):
696 def __init__(self, name, data):
697 self.name = name
698 self.data = data
699 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800700 self.sha1 = sha1(data).hexdigest()
701
702 @classmethod
703 def FromLocalFile(cls, name, diskname):
704 f = open(diskname, "rb")
705 data = f.read()
706 f.close()
707 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700708
709 def WriteToTemp(self):
710 t = tempfile.NamedTemporaryFile()
711 t.write(self.data)
712 t.flush()
713 return t
714
715 def AddToZip(self, z):
716 ZipWriteStr(z, self.name, self.data)
717
718DIFF_PROGRAM_BY_EXT = {
719 ".gz" : "imgdiff",
720 ".zip" : ["imgdiff", "-z"],
721 ".jar" : ["imgdiff", "-z"],
722 ".apk" : ["imgdiff", "-z"],
723 ".img" : "imgdiff",
724 }
725
726class Difference(object):
727 def __init__(self, tf, sf):
728 self.tf = tf
729 self.sf = sf
730 self.patch = None
731
732 def ComputePatch(self):
733 """Compute the patch (as a string of data) needed to turn sf into
734 tf. Returns the same tuple as GetPatch()."""
735
736 tf = self.tf
737 sf = self.sf
738
739 ext = os.path.splitext(tf.name)[1]
740 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
741
742 ttemp = tf.WriteToTemp()
743 stemp = sf.WriteToTemp()
744
745 ext = os.path.splitext(tf.name)[1]
746
747 try:
748 ptemp = tempfile.NamedTemporaryFile()
749 if isinstance(diff_program, list):
750 cmd = copy.copy(diff_program)
751 else:
752 cmd = [diff_program]
753 cmd.append(stemp.name)
754 cmd.append(ttemp.name)
755 cmd.append(ptemp.name)
756 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
757 _, err = p.communicate()
758 if err or p.returncode != 0:
759 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
760 return None
761 diff = ptemp.read()
762 finally:
763 ptemp.close()
764 stemp.close()
765 ttemp.close()
766
767 self.patch = diff
768 return self.tf, self.sf, self.patch
769
770
771 def GetPatch(self):
772 """Return a tuple (target_file, source_file, patch_data).
773 patch_data may be None if ComputePatch hasn't been called, or if
774 computing the patch failed."""
775 return self.tf, self.sf, self.patch
776
777
778def ComputeDifferences(diffs):
779 """Call ComputePatch on all the Difference objects in 'diffs'."""
780 print len(diffs), "diffs to compute"
781
782 # Do the largest files first, to try and reduce the long-pole effect.
783 by_size = [(i.tf.size, i) for i in diffs]
784 by_size.sort(reverse=True)
785 by_size = [i[1] for i in by_size]
786
787 lock = threading.Lock()
788 diff_iter = iter(by_size) # accessed under lock
789
790 def worker():
791 try:
792 lock.acquire()
793 for d in diff_iter:
794 lock.release()
795 start = time.time()
796 d.ComputePatch()
797 dur = time.time() - start
798 lock.acquire()
799
800 tf, sf, patch = d.GetPatch()
801 if sf.name == tf.name:
802 name = tf.name
803 else:
804 name = "%s (%s)" % (tf.name, sf.name)
805 if patch is None:
806 print "patching failed! %s" % (name,)
807 else:
808 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
809 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
810 lock.release()
811 except Exception, e:
812 print e
813 raise
814
815 # start worker threads; wait for them all to finish.
816 threads = [threading.Thread(target=worker)
817 for i in range(OPTIONS.worker_threads)]
818 for th in threads:
819 th.start()
820 while threads:
821 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700822
823
824# map recovery.fstab's fs_types to mount/format "partition types"
825PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
826 "ext4": "EMMC", "emmc": "EMMC" }
827
828def GetTypeAndDevice(mount_point, info):
829 fstab = info["fstab"]
830 if fstab:
831 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
832 else:
833 devices = {"/boot": "boot",
834 "/recovery": "recovery",
835 "/radio": "radio",
836 "/data": "userdata",
837 "/cache": "cache"}
838 return info["partition_type"], info.get("partition_path", "") + devices[mount_point]