blob: ac1566be42d0dfe1b50fcd6d6fa7d1adc906894b [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)
141 if not d["fstab"]:
142 if "fs_type" not in d: d["fs_type"] = "yaffs2"
143 if "partition_type" not in d: d["partition_type"] = "MTD"
144
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:
154 # older target-files that doesn't have a recovery.fstab; fall back
155 # to the fs_type and partition_type keys.
156 return
157
158 d = {}
159 for line in data.split("\n"):
160 line = line.strip()
161 if not line or line.startswith("#"): continue
162 pieces = line.split()
163 if not (3 <= len(pieces) <= 4):
164 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
165
166 p = Partition()
167 p.mount_point = pieces[0]
168 p.fs_type = pieces[1]
169 p.device = pieces[2]
170 if len(pieces) == 4:
171 p.device2 = pieces[3]
172 else:
173 p.device2 = None
174
175 d[p.mount_point] = p
176 return d
177
178
Doug Zongker37974732010-09-16 17:44:38 -0700179def DumpInfoDict(d):
180 for k, v in sorted(d.items()):
181 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700182
Doug Zongker37974732010-09-16 17:44:38 -0700183def BuildAndAddBootableImage(sourcedir, targetname, output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700184 """Take a kernel, cmdline, and ramdisk directory from the input (in
185 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700186 into the output zip file under the name 'targetname'. Returns
187 targetname on success or None on failure (if sourcedir does not
188 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700189
190 print "creating %s..." % (targetname,)
191
192 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700193 if img is None:
194 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700195
Doug Zongker37974732010-09-16 17:44:38 -0700196 CheckSize(img, targetname, info_dict)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700197 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700198 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700199
200def BuildBootableImage(sourcedir):
201 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700202 'sourcedir'), and turn them into a boot image. Return the image
203 data, or None if sourcedir does not appear to contains files for
204 building the requested image."""
205
206 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
207 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
208 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700209
210 ramdisk_img = tempfile.NamedTemporaryFile()
211 img = tempfile.NamedTemporaryFile()
212
213 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
214 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700215 p2 = Run(["minigzip"],
216 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700217
218 p2.wait()
219 p1.wait()
220 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700221 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700222
Doug Zongker38a649f2009-06-17 09:07:09 -0700223 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
224
Doug Zongker171f1cd2009-06-15 22:36:37 -0700225 fn = os.path.join(sourcedir, "cmdline")
226 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700227 cmd.append("--cmdline")
228 cmd.append(open(fn).read().rstrip("\n"))
229
230 fn = os.path.join(sourcedir, "base")
231 if os.access(fn, os.F_OK):
232 cmd.append("--base")
233 cmd.append(open(fn).read().rstrip("\n"))
234
Ying Wang4de6b5b2010-08-25 14:29:34 -0700235 fn = os.path.join(sourcedir, "pagesize")
236 if os.access(fn, os.F_OK):
237 cmd.append("--pagesize")
238 cmd.append(open(fn).read().rstrip("\n"))
239
Doug Zongker38a649f2009-06-17 09:07:09 -0700240 cmd.extend(["--ramdisk", ramdisk_img.name,
241 "--output", img.name])
242
243 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700244 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700245 assert p.returncode == 0, "mkbootimg of %s image failed" % (
246 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700247
248 img.seek(os.SEEK_SET, 0)
249 data = img.read()
250
251 ramdisk_img.close()
252 img.close()
253
254 return data
255
256
Doug Zongker37974732010-09-16 17:44:38 -0700257def AddRecovery(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700258 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
Doug Zongker37974732010-09-16 17:44:38 -0700259 "recovery.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700260
Doug Zongker37974732010-09-16 17:44:38 -0700261def AddBoot(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700262 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
Doug Zongker37974732010-09-16 17:44:38 -0700263 "boot.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700264
Doug Zongker75f17362009-12-08 13:46:44 -0800265def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700266 """Unzip the given archive into a temporary directory and return the name."""
267
268 tmp = tempfile.mkdtemp(prefix="targetfiles-")
269 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800270 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
271 if pattern is not None:
272 cmd.append(pattern)
273 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700274 p.communicate()
275 if p.returncode != 0:
276 raise ExternalError("failed to unzip input target-files \"%s\"" %
277 (filename,))
278 return tmp
279
280
281def GetKeyPasswords(keylist):
282 """Given a list of keys, prompt the user to enter passwords for
283 those which require them. Return a {key: password} dict. password
284 will be None if the key has no password."""
285
Doug Zongker8ce7c252009-05-22 13:34:54 -0700286 no_passwords = []
287 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700288 devnull = open("/dev/null", "w+b")
289 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800290 # We don't need a password for things that aren't really keys.
291 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700292 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700293 continue
294
Doug Zongker602a84e2009-06-18 08:35:12 -0700295 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
296 "-inform", "DER", "-nocrypt"],
297 stdin=devnull.fileno(),
298 stdout=devnull.fileno(),
299 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700300 p.communicate()
301 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700302 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700303 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700304 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700305 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700306
307 key_passwords = PasswordManager().GetPasswords(need_passwords)
308 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700309 return key_passwords
310
311
Doug Zongker951495f2009-08-14 12:44:19 -0700312def SignFile(input_name, output_name, key, password, align=None,
313 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700314 """Sign the input_name zip/jar/apk, producing output_name. Use the
315 given key and password (the latter may be None if the key does not
316 have a password.
317
318 If align is an integer > 1, zipalign is run to align stored files in
319 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700320
321 If whole_file is true, use the "-w" option to SignApk to embed a
322 signature that covers the whole file in the archive comment of the
323 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700324 """
Doug Zongker951495f2009-08-14 12:44:19 -0700325
Doug Zongkereef39442009-04-02 12:14:19 -0700326 if align == 0 or align == 1:
327 align = None
328
329 if align:
330 temp = tempfile.NamedTemporaryFile()
331 sign_name = temp.name
332 else:
333 sign_name = output_name
334
Doug Zongker09cf5602009-08-14 15:25:06 -0700335 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700336 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
337 if whole_file:
338 cmd.append("-w")
339 cmd.extend([key + ".x509.pem", key + ".pk8",
340 input_name, sign_name])
341
342 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700343 if password is not None:
344 password += "\n"
345 p.communicate(password)
346 if p.returncode != 0:
347 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
348
349 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700350 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700351 p.communicate()
352 if p.returncode != 0:
353 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
354 temp.close()
355
356
Doug Zongker37974732010-09-16 17:44:38 -0700357def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700358 """Check the data string passed against the max size limit, if
359 any, for the given target. Raise exception if the data is too big.
360 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700361
Doug Zongker1684d9c2010-09-17 07:44:38 -0700362 if target.endswith(".img"): target = target[:-4]
Doug Zongker258bf462010-09-20 18:04:41 -0700363 mount_point = "/" + target
364
365 if info_dict["fstab"]:
366 if mount_point == "/userdata": mount_point = "/data"
367 p = info_dict["fstab"][mount_point]
368 fs_type = p.fs_type
369 limit = info_dict.get(p.device + "_size", None)
370 else:
371 fs_type = info_dict.get("fs_type", None)
372 limit = info_dict.get(target + "_size", None)
373 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700374
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700375 if fs_type == "yaffs2":
376 # image size should be increased by 1/64th to account for the
377 # spare area (64 bytes per 2k page)
378 limit = limit / 2048 * (2048+64)
379
Doug Zongkereef39442009-04-02 12:14:19 -0700380 size = len(data)
381 pct = float(size) * 100.0 / limit
382 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
383 if pct >= 99.0:
384 raise ExternalError(msg)
385 elif pct >= 95.0:
386 print
387 print " WARNING: ", msg
388 print
389 elif OPTIONS.verbose:
390 print " ", msg
391
392
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800393def ReadApkCerts(tf_zip):
394 """Given a target_files ZipFile, parse the META/apkcerts.txt file
395 and return a {package: cert} dict."""
396 certmap = {}
397 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
398 line = line.strip()
399 if not line: continue
400 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
401 r'private_key="(.*)"$', line)
402 if m:
403 name, cert, privkey = m.groups()
404 if cert in SPECIAL_CERT_STRINGS and not privkey:
405 certmap[name] = cert
406 elif (cert.endswith(".x509.pem") and
407 privkey.endswith(".pk8") and
408 cert[:-9] == privkey[:-4]):
409 certmap[name] = cert[:-9]
410 else:
411 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
412 return certmap
413
414
Doug Zongkereef39442009-04-02 12:14:19 -0700415COMMON_DOCSTRING = """
416 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700417 Prepend <dir>/bin to the list of places to search for binaries
418 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700419
Doug Zongker05d3dea2009-06-22 11:32:31 -0700420 -s (--device_specific) <file>
421 Path to the python module containing device-specific
422 releasetools code.
423
Doug Zongker8bec09e2009-11-30 15:37:14 -0800424 -x (--extra) <key=value>
425 Add a key/value pair to the 'extras' dict, which device-specific
426 extension code may look at.
427
Doug Zongkereef39442009-04-02 12:14:19 -0700428 -v (--verbose)
429 Show command lines being executed.
430
431 -h (--help)
432 Display this usage message and exit.
433"""
434
435def Usage(docstring):
436 print docstring.rstrip("\n")
437 print COMMON_DOCSTRING
438
439
440def ParseOptions(argv,
441 docstring,
442 extra_opts="", extra_long_opts=(),
443 extra_option_handler=None):
444 """Parse the options in argv and return any arguments that aren't
445 flags. docstring is the calling module's docstring, to be displayed
446 for errors and -h. extra_opts and extra_long_opts are for flags
447 defined by the caller, which are processed by passing them to
448 extra_option_handler."""
449
450 try:
451 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800452 argv, "hvp:s:x:" + extra_opts,
453 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700454 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700455 except getopt.GetoptError, err:
456 Usage(docstring)
457 print "**", str(err), "**"
458 sys.exit(2)
459
460 path_specified = False
461
462 for o, a in opts:
463 if o in ("-h", "--help"):
464 Usage(docstring)
465 sys.exit()
466 elif o in ("-v", "--verbose"):
467 OPTIONS.verbose = True
468 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700469 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700470 elif o in ("-s", "--device_specific"):
471 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800472 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800473 key, value = a.split("=", 1)
474 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700475 else:
476 if extra_option_handler is None or not extra_option_handler(o, a):
477 assert False, "unknown option \"%s\"" % (o,)
478
Doug Zongker602a84e2009-06-18 08:35:12 -0700479 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
480 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700481
482 return args
483
484
485def Cleanup():
486 for i in OPTIONS.tempfiles:
487 if os.path.isdir(i):
488 shutil.rmtree(i)
489 else:
490 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700491
492
493class PasswordManager(object):
494 def __init__(self):
495 self.editor = os.getenv("EDITOR", None)
496 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
497
498 def GetPasswords(self, items):
499 """Get passwords corresponding to each string in 'items',
500 returning a dict. (The dict may have keys in addition to the
501 values in 'items'.)
502
503 Uses the passwords in $ANDROID_PW_FILE if available, letting the
504 user edit that file to add more needed passwords. If no editor is
505 available, or $ANDROID_PW_FILE isn't define, prompts the user
506 interactively in the ordinary way.
507 """
508
509 current = self.ReadFile()
510
511 first = True
512 while True:
513 missing = []
514 for i in items:
515 if i not in current or not current[i]:
516 missing.append(i)
517 # Are all the passwords already in the file?
518 if not missing: return current
519
520 for i in missing:
521 current[i] = ""
522
523 if not first:
524 print "key file %s still missing some passwords." % (self.pwfile,)
525 answer = raw_input("try to edit again? [y]> ").strip()
526 if answer and answer[0] not in 'yY':
527 raise RuntimeError("key passwords unavailable")
528 first = False
529
530 current = self.UpdateAndReadFile(current)
531
532 def PromptResult(self, current):
533 """Prompt the user to enter a value (password) for each key in
534 'current' whose value is fales. Returns a new dict with all the
535 values.
536 """
537 result = {}
538 for k, v in sorted(current.iteritems()):
539 if v:
540 result[k] = v
541 else:
542 while True:
543 result[k] = getpass.getpass("Enter password for %s key> "
544 % (k,)).strip()
545 if result[k]: break
546 return result
547
548 def UpdateAndReadFile(self, current):
549 if not self.editor or not self.pwfile:
550 return self.PromptResult(current)
551
552 f = open(self.pwfile, "w")
553 os.chmod(self.pwfile, 0600)
554 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
555 f.write("# (Additional spaces are harmless.)\n\n")
556
557 first_line = None
558 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
559 sorted.sort()
560 for i, (_, k, v) in enumerate(sorted):
561 f.write("[[[ %s ]]] %s\n" % (v, k))
562 if not v and first_line is None:
563 # position cursor on first line with no password.
564 first_line = i + 4
565 f.close()
566
567 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
568 _, _ = p.communicate()
569
570 return self.ReadFile()
571
572 def ReadFile(self):
573 result = {}
574 if self.pwfile is None: return result
575 try:
576 f = open(self.pwfile, "r")
577 for line in f:
578 line = line.strip()
579 if not line or line[0] == '#': continue
580 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
581 if not m:
582 print "failed to parse password file: ", line
583 else:
584 result[m.group(2)] = m.group(1)
585 f.close()
586 except IOError, e:
587 if e.errno != errno.ENOENT:
588 print "error reading password file: ", str(e)
589 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700590
591
592def ZipWriteStr(zip, filename, data, perms=0644):
593 # use a fixed timestamp so the output is repeatable.
594 zinfo = zipfile.ZipInfo(filename=filename,
595 date_time=(2009, 1, 1, 0, 0, 0))
596 zinfo.compress_type = zip.compression
597 zinfo.external_attr = perms << 16
598 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700599
600
601class DeviceSpecificParams(object):
602 module = None
603 def __init__(self, **kwargs):
604 """Keyword arguments to the constructor become attributes of this
605 object, which is passed to all functions in the device-specific
606 module."""
607 for k, v in kwargs.iteritems():
608 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800609 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700610
611 if self.module is None:
612 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700613 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700614 try:
615 if os.path.isdir(path):
616 info = imp.find_module("releasetools", [path])
617 else:
618 d, f = os.path.split(path)
619 b, x = os.path.splitext(f)
620 if x == ".py":
621 f = b
622 info = imp.find_module(f, [d])
623 self.module = imp.load_module("device_specific", *info)
624 except ImportError:
625 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700626
627 def _DoCall(self, function_name, *args, **kwargs):
628 """Call the named function in the device-specific module, passing
629 the given args and kwargs. The first argument to the call will be
630 the DeviceSpecific object itself. If there is no module, or the
631 module does not define the function, return the value of the
632 'default' kwarg (which itself defaults to None)."""
633 if self.module is None or not hasattr(self.module, function_name):
634 return kwargs.get("default", None)
635 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
636
637 def FullOTA_Assertions(self):
638 """Called after emitting the block of assertions at the top of a
639 full OTA package. Implementations can add whatever additional
640 assertions they like."""
641 return self._DoCall("FullOTA_Assertions")
642
643 def FullOTA_InstallEnd(self):
644 """Called at the end of full OTA installation; typically this is
645 used to install the image for the device's baseband processor."""
646 return self._DoCall("FullOTA_InstallEnd")
647
648 def IncrementalOTA_Assertions(self):
649 """Called after emitting the block of assertions at the top of an
650 incremental OTA package. Implementations can add whatever
651 additional assertions they like."""
652 return self._DoCall("IncrementalOTA_Assertions")
653
654 def IncrementalOTA_VerifyEnd(self):
655 """Called at the end of the verification phase of incremental OTA
656 installation; additional checks can be placed here to abort the
657 script before any changes are made."""
658 return self._DoCall("IncrementalOTA_VerifyEnd")
659
660 def IncrementalOTA_InstallEnd(self):
661 """Called at the end of incremental OTA installation; typically
662 this is used to install the image for the device's baseband
663 processor."""
664 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700665
666class File(object):
667 def __init__(self, name, data):
668 self.name = name
669 self.data = data
670 self.size = len(data)
671 self.sha1 = sha.sha(data).hexdigest()
672
673 def WriteToTemp(self):
674 t = tempfile.NamedTemporaryFile()
675 t.write(self.data)
676 t.flush()
677 return t
678
679 def AddToZip(self, z):
680 ZipWriteStr(z, self.name, self.data)
681
682DIFF_PROGRAM_BY_EXT = {
683 ".gz" : "imgdiff",
684 ".zip" : ["imgdiff", "-z"],
685 ".jar" : ["imgdiff", "-z"],
686 ".apk" : ["imgdiff", "-z"],
687 ".img" : "imgdiff",
688 }
689
690class Difference(object):
691 def __init__(self, tf, sf):
692 self.tf = tf
693 self.sf = sf
694 self.patch = None
695
696 def ComputePatch(self):
697 """Compute the patch (as a string of data) needed to turn sf into
698 tf. Returns the same tuple as GetPatch()."""
699
700 tf = self.tf
701 sf = self.sf
702
703 ext = os.path.splitext(tf.name)[1]
704 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
705
706 ttemp = tf.WriteToTemp()
707 stemp = sf.WriteToTemp()
708
709 ext = os.path.splitext(tf.name)[1]
710
711 try:
712 ptemp = tempfile.NamedTemporaryFile()
713 if isinstance(diff_program, list):
714 cmd = copy.copy(diff_program)
715 else:
716 cmd = [diff_program]
717 cmd.append(stemp.name)
718 cmd.append(ttemp.name)
719 cmd.append(ptemp.name)
720 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
721 _, err = p.communicate()
722 if err or p.returncode != 0:
723 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
724 return None
725 diff = ptemp.read()
726 finally:
727 ptemp.close()
728 stemp.close()
729 ttemp.close()
730
731 self.patch = diff
732 return self.tf, self.sf, self.patch
733
734
735 def GetPatch(self):
736 """Return a tuple (target_file, source_file, patch_data).
737 patch_data may be None if ComputePatch hasn't been called, or if
738 computing the patch failed."""
739 return self.tf, self.sf, self.patch
740
741
742def ComputeDifferences(diffs):
743 """Call ComputePatch on all the Difference objects in 'diffs'."""
744 print len(diffs), "diffs to compute"
745
746 # Do the largest files first, to try and reduce the long-pole effect.
747 by_size = [(i.tf.size, i) for i in diffs]
748 by_size.sort(reverse=True)
749 by_size = [i[1] for i in by_size]
750
751 lock = threading.Lock()
752 diff_iter = iter(by_size) # accessed under lock
753
754 def worker():
755 try:
756 lock.acquire()
757 for d in diff_iter:
758 lock.release()
759 start = time.time()
760 d.ComputePatch()
761 dur = time.time() - start
762 lock.acquire()
763
764 tf, sf, patch = d.GetPatch()
765 if sf.name == tf.name:
766 name = tf.name
767 else:
768 name = "%s (%s)" % (tf.name, sf.name)
769 if patch is None:
770 print "patching failed! %s" % (name,)
771 else:
772 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
773 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
774 lock.release()
775 except Exception, e:
776 print e
777 raise
778
779 # start worker threads; wait for them all to finish.
780 threads = [threading.Thread(target=worker)
781 for i in range(OPTIONS.worker_threads)]
782 for th in threads:
783 th.start()
784 while threads:
785 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700786
787
788# map recovery.fstab's fs_types to mount/format "partition types"
789PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
790 "ext4": "EMMC", "emmc": "EMMC" }
791
792def GetTypeAndDevice(mount_point, info):
793 fstab = info["fstab"]
794 if fstab:
795 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
796 else:
797 devices = {"/boot": "boot",
798 "/recovery": "recovery",
799 "/radio": "radio",
800 "/data": "userdata",
801 "/cache": "cache"}
802 return info["partition_type"], info.get("partition_path", "") + devices[mount_point]