blob: a236a12931a2c54038312801953efe52deabfa4e [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
21import re
Doug Zongkerea5d7a92010-09-12 15:26:16 -070022import sha
Doug Zongkereef39442009-04-02 12:14:19 -070023import 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
31# missing in Python 2.4 and before
32if not hasattr(os, "SEEK_SET"):
33 os.SEEK_SET = 0
34
35class Options(object): pass
36OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070037OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070038OPTIONS.verbose = False
39OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070040OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080041OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070042OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070043
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080044
45# Values for "certificate" in apkcerts that mean special things.
46SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
47
48
Doug Zongkereef39442009-04-02 12:14:19 -070049class ExternalError(RuntimeError): pass
50
51
52def Run(args, **kwargs):
53 """Create and return a subprocess.Popen object, printing the command
54 line on the terminal if -v was specified."""
55 if OPTIONS.verbose:
56 print " running: ", " ".join(args)
57 return subprocess.Popen(args, **kwargs)
58
59
Doug Zongker37974732010-09-16 17:44:38 -070060def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070061 """Read and parse the META/misc_info.txt key/value pairs from the
62 input target files and return a dict."""
63
64 d = {}
65 try:
Doug Zongker37974732010-09-16 17:44:38 -070066 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070067 line = line.strip()
68 if not line or line.startswith("#"): continue
69 k, v = line.split("=", 1)
70 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070071 except KeyError:
72 # ok if misc_info.txt doesn't exist
73 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070074
Doug Zongker37974732010-09-16 17:44:38 -070075 # backwards compatibility: These values used to be in their own
76 # files. Look for them, in case we're processing an old
77 # target_files zip.
78
79 if "mkyaffs2_extra_flags" not in d:
80 try:
81 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
82 except KeyError:
83 # ok if flags don't exist
84 pass
85
86 if "recovery_api_version" not in d:
87 try:
88 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
89 except KeyError:
90 raise ValueError("can't find recovery API version in input target-files")
91
92 if "tool_extensions" not in d:
93 try:
94 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
95 except KeyError:
96 # ok if extensions don't exist
97 pass
98
99 try:
100 data = zip.read("META/imagesizes.txt")
101 for line in data.split("\n"):
102 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700103 name, value = line.split(" ", 1)
104 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700105 if name == "blocksize":
106 d[name] = value
107 else:
108 d[name + "_size"] = value
109 except KeyError:
110 pass
111
112 def makeint(key):
113 if key in d:
114 d[key] = int(d[key], 0)
115
116 makeint("recovery_api_version")
117 makeint("blocksize")
118 makeint("system_size")
119 makeint("userdata_size")
120 makeint("recovery_size")
121 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700122
Doug Zongker258bf462010-09-20 18:04:41 -0700123 d["fstab"] = LoadRecoveryFSTab(zip)
124 if not d["fstab"]:
125 if "fs_type" not in d: d["fs_type"] = "yaffs2"
126 if "partition_type" not in d: d["partition_type"] = "MTD"
127
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700128 return d
129
Doug Zongker258bf462010-09-20 18:04:41 -0700130def LoadRecoveryFSTab(zip):
131 class Partition(object):
132 pass
133
134 try:
135 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
136 except KeyError:
137 # older target-files that doesn't have a recovery.fstab; fall back
138 # to the fs_type and partition_type keys.
139 return
140
141 d = {}
142 for line in data.split("\n"):
143 line = line.strip()
144 if not line or line.startswith("#"): continue
145 pieces = line.split()
146 if not (3 <= len(pieces) <= 4):
147 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
148
149 p = Partition()
150 p.mount_point = pieces[0]
151 p.fs_type = pieces[1]
152 p.device = pieces[2]
153 if len(pieces) == 4:
154 p.device2 = pieces[3]
155 else:
156 p.device2 = None
157
158 d[p.mount_point] = p
159 return d
160
161
Doug Zongker37974732010-09-16 17:44:38 -0700162def DumpInfoDict(d):
163 for k, v in sorted(d.items()):
164 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700165
Doug Zongker37974732010-09-16 17:44:38 -0700166def BuildAndAddBootableImage(sourcedir, targetname, output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700167 """Take a kernel, cmdline, and ramdisk directory from the input (in
168 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700169 into the output zip file under the name 'targetname'. Returns
170 targetname on success or None on failure (if sourcedir does not
171 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700172
173 print "creating %s..." % (targetname,)
174
175 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700176 if img is None:
177 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700178
Doug Zongker37974732010-09-16 17:44:38 -0700179 CheckSize(img, targetname, info_dict)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700180 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700181 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700182
183def BuildBootableImage(sourcedir):
184 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700185 'sourcedir'), and turn them into a boot image. Return the image
186 data, or None if sourcedir does not appear to contains files for
187 building the requested image."""
188
189 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
190 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
191 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700192
193 ramdisk_img = tempfile.NamedTemporaryFile()
194 img = tempfile.NamedTemporaryFile()
195
196 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
197 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700198 p2 = Run(["minigzip"],
199 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700200
201 p2.wait()
202 p1.wait()
203 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700204 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700205
Doug Zongker38a649f2009-06-17 09:07:09 -0700206 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
207
Doug Zongker171f1cd2009-06-15 22:36:37 -0700208 fn = os.path.join(sourcedir, "cmdline")
209 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700210 cmd.append("--cmdline")
211 cmd.append(open(fn).read().rstrip("\n"))
212
213 fn = os.path.join(sourcedir, "base")
214 if os.access(fn, os.F_OK):
215 cmd.append("--base")
216 cmd.append(open(fn).read().rstrip("\n"))
217
Ying Wang4de6b5b2010-08-25 14:29:34 -0700218 fn = os.path.join(sourcedir, "pagesize")
219 if os.access(fn, os.F_OK):
220 cmd.append("--pagesize")
221 cmd.append(open(fn).read().rstrip("\n"))
222
Doug Zongker38a649f2009-06-17 09:07:09 -0700223 cmd.extend(["--ramdisk", ramdisk_img.name,
224 "--output", img.name])
225
226 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700227 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700228 assert p.returncode == 0, "mkbootimg of %s image failed" % (
229 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700230
231 img.seek(os.SEEK_SET, 0)
232 data = img.read()
233
234 ramdisk_img.close()
235 img.close()
236
237 return data
238
239
Doug Zongker37974732010-09-16 17:44:38 -0700240def AddRecovery(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700241 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
Doug Zongker37974732010-09-16 17:44:38 -0700242 "recovery.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700243
Doug Zongker37974732010-09-16 17:44:38 -0700244def AddBoot(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700245 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
Doug Zongker37974732010-09-16 17:44:38 -0700246 "boot.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700247
Doug Zongker75f17362009-12-08 13:46:44 -0800248def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700249 """Unzip the given archive into a temporary directory and return the name."""
250
251 tmp = tempfile.mkdtemp(prefix="targetfiles-")
252 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800253 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
254 if pattern is not None:
255 cmd.append(pattern)
256 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700257 p.communicate()
258 if p.returncode != 0:
259 raise ExternalError("failed to unzip input target-files \"%s\"" %
260 (filename,))
261 return tmp
262
263
264def GetKeyPasswords(keylist):
265 """Given a list of keys, prompt the user to enter passwords for
266 those which require them. Return a {key: password} dict. password
267 will be None if the key has no password."""
268
Doug Zongker8ce7c252009-05-22 13:34:54 -0700269 no_passwords = []
270 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700271 devnull = open("/dev/null", "w+b")
272 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800273 # We don't need a password for things that aren't really keys.
274 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700275 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700276 continue
277
Doug Zongker602a84e2009-06-18 08:35:12 -0700278 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
279 "-inform", "DER", "-nocrypt"],
280 stdin=devnull.fileno(),
281 stdout=devnull.fileno(),
282 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700283 p.communicate()
284 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700285 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700286 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700287 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700288 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700289
290 key_passwords = PasswordManager().GetPasswords(need_passwords)
291 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700292 return key_passwords
293
294
Doug Zongker951495f2009-08-14 12:44:19 -0700295def SignFile(input_name, output_name, key, password, align=None,
296 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700297 """Sign the input_name zip/jar/apk, producing output_name. Use the
298 given key and password (the latter may be None if the key does not
299 have a password.
300
301 If align is an integer > 1, zipalign is run to align stored files in
302 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700303
304 If whole_file is true, use the "-w" option to SignApk to embed a
305 signature that covers the whole file in the archive comment of the
306 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700307 """
Doug Zongker951495f2009-08-14 12:44:19 -0700308
Doug Zongkereef39442009-04-02 12:14:19 -0700309 if align == 0 or align == 1:
310 align = None
311
312 if align:
313 temp = tempfile.NamedTemporaryFile()
314 sign_name = temp.name
315 else:
316 sign_name = output_name
317
Doug Zongker09cf5602009-08-14 15:25:06 -0700318 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700319 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
320 if whole_file:
321 cmd.append("-w")
322 cmd.extend([key + ".x509.pem", key + ".pk8",
323 input_name, sign_name])
324
325 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700326 if password is not None:
327 password += "\n"
328 p.communicate(password)
329 if p.returncode != 0:
330 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
331
332 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700333 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700334 p.communicate()
335 if p.returncode != 0:
336 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
337 temp.close()
338
339
Doug Zongker37974732010-09-16 17:44:38 -0700340def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700341 """Check the data string passed against the max size limit, if
342 any, for the given target. Raise exception if the data is too big.
343 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700344
Doug Zongker1684d9c2010-09-17 07:44:38 -0700345 if target.endswith(".img"): target = target[:-4]
Doug Zongker258bf462010-09-20 18:04:41 -0700346 mount_point = "/" + target
347
348 if info_dict["fstab"]:
349 if mount_point == "/userdata": mount_point = "/data"
350 p = info_dict["fstab"][mount_point]
351 fs_type = p.fs_type
352 limit = info_dict.get(p.device + "_size", None)
353 else:
354 fs_type = info_dict.get("fs_type", None)
355 limit = info_dict.get(target + "_size", None)
356 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700357
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700358 if fs_type == "yaffs2":
359 # image size should be increased by 1/64th to account for the
360 # spare area (64 bytes per 2k page)
361 limit = limit / 2048 * (2048+64)
362
Doug Zongkereef39442009-04-02 12:14:19 -0700363 size = len(data)
364 pct = float(size) * 100.0 / limit
365 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
366 if pct >= 99.0:
367 raise ExternalError(msg)
368 elif pct >= 95.0:
369 print
370 print " WARNING: ", msg
371 print
372 elif OPTIONS.verbose:
373 print " ", msg
374
375
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800376def ReadApkCerts(tf_zip):
377 """Given a target_files ZipFile, parse the META/apkcerts.txt file
378 and return a {package: cert} dict."""
379 certmap = {}
380 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
381 line = line.strip()
382 if not line: continue
383 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
384 r'private_key="(.*)"$', line)
385 if m:
386 name, cert, privkey = m.groups()
387 if cert in SPECIAL_CERT_STRINGS and not privkey:
388 certmap[name] = cert
389 elif (cert.endswith(".x509.pem") and
390 privkey.endswith(".pk8") and
391 cert[:-9] == privkey[:-4]):
392 certmap[name] = cert[:-9]
393 else:
394 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
395 return certmap
396
397
Doug Zongkereef39442009-04-02 12:14:19 -0700398COMMON_DOCSTRING = """
399 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700400 Prepend <dir>/bin to the list of places to search for binaries
401 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700402
Doug Zongker05d3dea2009-06-22 11:32:31 -0700403 -s (--device_specific) <file>
404 Path to the python module containing device-specific
405 releasetools code.
406
Doug Zongker8bec09e2009-11-30 15:37:14 -0800407 -x (--extra) <key=value>
408 Add a key/value pair to the 'extras' dict, which device-specific
409 extension code may look at.
410
Doug Zongkereef39442009-04-02 12:14:19 -0700411 -v (--verbose)
412 Show command lines being executed.
413
414 -h (--help)
415 Display this usage message and exit.
416"""
417
418def Usage(docstring):
419 print docstring.rstrip("\n")
420 print COMMON_DOCSTRING
421
422
423def ParseOptions(argv,
424 docstring,
425 extra_opts="", extra_long_opts=(),
426 extra_option_handler=None):
427 """Parse the options in argv and return any arguments that aren't
428 flags. docstring is the calling module's docstring, to be displayed
429 for errors and -h. extra_opts and extra_long_opts are for flags
430 defined by the caller, which are processed by passing them to
431 extra_option_handler."""
432
433 try:
434 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800435 argv, "hvp:s:x:" + extra_opts,
436 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700437 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700438 except getopt.GetoptError, err:
439 Usage(docstring)
440 print "**", str(err), "**"
441 sys.exit(2)
442
443 path_specified = False
444
445 for o, a in opts:
446 if o in ("-h", "--help"):
447 Usage(docstring)
448 sys.exit()
449 elif o in ("-v", "--verbose"):
450 OPTIONS.verbose = True
451 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700452 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700453 elif o in ("-s", "--device_specific"):
454 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800455 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800456 key, value = a.split("=", 1)
457 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700458 else:
459 if extra_option_handler is None or not extra_option_handler(o, a):
460 assert False, "unknown option \"%s\"" % (o,)
461
Doug Zongker602a84e2009-06-18 08:35:12 -0700462 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
463 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700464
465 return args
466
467
468def Cleanup():
469 for i in OPTIONS.tempfiles:
470 if os.path.isdir(i):
471 shutil.rmtree(i)
472 else:
473 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700474
475
476class PasswordManager(object):
477 def __init__(self):
478 self.editor = os.getenv("EDITOR", None)
479 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
480
481 def GetPasswords(self, items):
482 """Get passwords corresponding to each string in 'items',
483 returning a dict. (The dict may have keys in addition to the
484 values in 'items'.)
485
486 Uses the passwords in $ANDROID_PW_FILE if available, letting the
487 user edit that file to add more needed passwords. If no editor is
488 available, or $ANDROID_PW_FILE isn't define, prompts the user
489 interactively in the ordinary way.
490 """
491
492 current = self.ReadFile()
493
494 first = True
495 while True:
496 missing = []
497 for i in items:
498 if i not in current or not current[i]:
499 missing.append(i)
500 # Are all the passwords already in the file?
501 if not missing: return current
502
503 for i in missing:
504 current[i] = ""
505
506 if not first:
507 print "key file %s still missing some passwords." % (self.pwfile,)
508 answer = raw_input("try to edit again? [y]> ").strip()
509 if answer and answer[0] not in 'yY':
510 raise RuntimeError("key passwords unavailable")
511 first = False
512
513 current = self.UpdateAndReadFile(current)
514
515 def PromptResult(self, current):
516 """Prompt the user to enter a value (password) for each key in
517 'current' whose value is fales. Returns a new dict with all the
518 values.
519 """
520 result = {}
521 for k, v in sorted(current.iteritems()):
522 if v:
523 result[k] = v
524 else:
525 while True:
526 result[k] = getpass.getpass("Enter password for %s key> "
527 % (k,)).strip()
528 if result[k]: break
529 return result
530
531 def UpdateAndReadFile(self, current):
532 if not self.editor or not self.pwfile:
533 return self.PromptResult(current)
534
535 f = open(self.pwfile, "w")
536 os.chmod(self.pwfile, 0600)
537 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
538 f.write("# (Additional spaces are harmless.)\n\n")
539
540 first_line = None
541 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
542 sorted.sort()
543 for i, (_, k, v) in enumerate(sorted):
544 f.write("[[[ %s ]]] %s\n" % (v, k))
545 if not v and first_line is None:
546 # position cursor on first line with no password.
547 first_line = i + 4
548 f.close()
549
550 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
551 _, _ = p.communicate()
552
553 return self.ReadFile()
554
555 def ReadFile(self):
556 result = {}
557 if self.pwfile is None: return result
558 try:
559 f = open(self.pwfile, "r")
560 for line in f:
561 line = line.strip()
562 if not line or line[0] == '#': continue
563 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
564 if not m:
565 print "failed to parse password file: ", line
566 else:
567 result[m.group(2)] = m.group(1)
568 f.close()
569 except IOError, e:
570 if e.errno != errno.ENOENT:
571 print "error reading password file: ", str(e)
572 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700573
574
575def ZipWriteStr(zip, filename, data, perms=0644):
576 # use a fixed timestamp so the output is repeatable.
577 zinfo = zipfile.ZipInfo(filename=filename,
578 date_time=(2009, 1, 1, 0, 0, 0))
579 zinfo.compress_type = zip.compression
580 zinfo.external_attr = perms << 16
581 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700582
583
584class DeviceSpecificParams(object):
585 module = None
586 def __init__(self, **kwargs):
587 """Keyword arguments to the constructor become attributes of this
588 object, which is passed to all functions in the device-specific
589 module."""
590 for k, v in kwargs.iteritems():
591 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800592 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700593
594 if self.module is None:
595 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700596 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700597 try:
598 if os.path.isdir(path):
599 info = imp.find_module("releasetools", [path])
600 else:
601 d, f = os.path.split(path)
602 b, x = os.path.splitext(f)
603 if x == ".py":
604 f = b
605 info = imp.find_module(f, [d])
606 self.module = imp.load_module("device_specific", *info)
607 except ImportError:
608 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700609
610 def _DoCall(self, function_name, *args, **kwargs):
611 """Call the named function in the device-specific module, passing
612 the given args and kwargs. The first argument to the call will be
613 the DeviceSpecific object itself. If there is no module, or the
614 module does not define the function, return the value of the
615 'default' kwarg (which itself defaults to None)."""
616 if self.module is None or not hasattr(self.module, function_name):
617 return kwargs.get("default", None)
618 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
619
620 def FullOTA_Assertions(self):
621 """Called after emitting the block of assertions at the top of a
622 full OTA package. Implementations can add whatever additional
623 assertions they like."""
624 return self._DoCall("FullOTA_Assertions")
625
626 def FullOTA_InstallEnd(self):
627 """Called at the end of full OTA installation; typically this is
628 used to install the image for the device's baseband processor."""
629 return self._DoCall("FullOTA_InstallEnd")
630
631 def IncrementalOTA_Assertions(self):
632 """Called after emitting the block of assertions at the top of an
633 incremental OTA package. Implementations can add whatever
634 additional assertions they like."""
635 return self._DoCall("IncrementalOTA_Assertions")
636
637 def IncrementalOTA_VerifyEnd(self):
638 """Called at the end of the verification phase of incremental OTA
639 installation; additional checks can be placed here to abort the
640 script before any changes are made."""
641 return self._DoCall("IncrementalOTA_VerifyEnd")
642
643 def IncrementalOTA_InstallEnd(self):
644 """Called at the end of incremental OTA installation; typically
645 this is used to install the image for the device's baseband
646 processor."""
647 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700648
649class File(object):
650 def __init__(self, name, data):
651 self.name = name
652 self.data = data
653 self.size = len(data)
654 self.sha1 = sha.sha(data).hexdigest()
655
656 def WriteToTemp(self):
657 t = tempfile.NamedTemporaryFile()
658 t.write(self.data)
659 t.flush()
660 return t
661
662 def AddToZip(self, z):
663 ZipWriteStr(z, self.name, self.data)
664
665DIFF_PROGRAM_BY_EXT = {
666 ".gz" : "imgdiff",
667 ".zip" : ["imgdiff", "-z"],
668 ".jar" : ["imgdiff", "-z"],
669 ".apk" : ["imgdiff", "-z"],
670 ".img" : "imgdiff",
671 }
672
673class Difference(object):
674 def __init__(self, tf, sf):
675 self.tf = tf
676 self.sf = sf
677 self.patch = None
678
679 def ComputePatch(self):
680 """Compute the patch (as a string of data) needed to turn sf into
681 tf. Returns the same tuple as GetPatch()."""
682
683 tf = self.tf
684 sf = self.sf
685
686 ext = os.path.splitext(tf.name)[1]
687 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
688
689 ttemp = tf.WriteToTemp()
690 stemp = sf.WriteToTemp()
691
692 ext = os.path.splitext(tf.name)[1]
693
694 try:
695 ptemp = tempfile.NamedTemporaryFile()
696 if isinstance(diff_program, list):
697 cmd = copy.copy(diff_program)
698 else:
699 cmd = [diff_program]
700 cmd.append(stemp.name)
701 cmd.append(ttemp.name)
702 cmd.append(ptemp.name)
703 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
704 _, err = p.communicate()
705 if err or p.returncode != 0:
706 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
707 return None
708 diff = ptemp.read()
709 finally:
710 ptemp.close()
711 stemp.close()
712 ttemp.close()
713
714 self.patch = diff
715 return self.tf, self.sf, self.patch
716
717
718 def GetPatch(self):
719 """Return a tuple (target_file, source_file, patch_data).
720 patch_data may be None if ComputePatch hasn't been called, or if
721 computing the patch failed."""
722 return self.tf, self.sf, self.patch
723
724
725def ComputeDifferences(diffs):
726 """Call ComputePatch on all the Difference objects in 'diffs'."""
727 print len(diffs), "diffs to compute"
728
729 # Do the largest files first, to try and reduce the long-pole effect.
730 by_size = [(i.tf.size, i) for i in diffs]
731 by_size.sort(reverse=True)
732 by_size = [i[1] for i in by_size]
733
734 lock = threading.Lock()
735 diff_iter = iter(by_size) # accessed under lock
736
737 def worker():
738 try:
739 lock.acquire()
740 for d in diff_iter:
741 lock.release()
742 start = time.time()
743 d.ComputePatch()
744 dur = time.time() - start
745 lock.acquire()
746
747 tf, sf, patch = d.GetPatch()
748 if sf.name == tf.name:
749 name = tf.name
750 else:
751 name = "%s (%s)" % (tf.name, sf.name)
752 if patch is None:
753 print "patching failed! %s" % (name,)
754 else:
755 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
756 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
757 lock.release()
758 except Exception, e:
759 print e
760 raise
761
762 # start worker threads; wait for them all to finish.
763 threads = [threading.Thread(target=worker)
764 for i in range(OPTIONS.worker_threads)]
765 for th in threads:
766 th.start()
767 while threads:
768 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700769
770
771# map recovery.fstab's fs_types to mount/format "partition types"
772PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
773 "ext4": "EMMC", "emmc": "EMMC" }
774
775def GetTypeAndDevice(mount_point, info):
776 fstab = info["fstab"]
777 if fstab:
778 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
779 else:
780 devices = {"/boot": "boot",
781 "/recovery": "recovery",
782 "/radio": "radio",
783 "/data": "userdata",
784 "/cache": "cache"}
785 return info["partition_type"], info.get("partition_path", "") + devices[mount_point]