blob: 3ec4c2d3a3f48f5779960b1ba23885f784fc0ac3 [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.max_image_size = {}
39OPTIONS.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
Doug Zongker37974732010-09-16 17:44:38 -070061def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070062 """Read and parse the META/misc_info.txt key/value pairs from the
63 input target files and return a dict."""
64
65 d = {}
66 try:
Doug Zongker37974732010-09-16 17:44:38 -070067 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070068 line = line.strip()
69 if not line or line.startswith("#"): continue
70 k, v = line.split("=", 1)
71 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070072 except KeyError:
73 # ok if misc_info.txt doesn't exist
74 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070075
Doug Zongker37974732010-09-16 17:44:38 -070076 if "fs_type" not in d: d["fs_type"] = "yaffs2"
77 if "partition_type" not in d: d["partition_type"] = "MTD"
78
79 # backwards compatibility: These values used to be in their own
80 # files. Look for them, in case we're processing an old
81 # target_files zip.
82
83 if "mkyaffs2_extra_flags" not in d:
84 try:
85 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
86 except KeyError:
87 # ok if flags don't exist
88 pass
89
90 if "recovery_api_version" not in d:
91 try:
92 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
93 except KeyError:
94 raise ValueError("can't find recovery API version in input target-files")
95
96 if "tool_extensions" not in d:
97 try:
98 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
99 except KeyError:
100 # ok if extensions don't exist
101 pass
102
103 try:
104 data = zip.read("META/imagesizes.txt")
105 for line in data.split("\n"):
106 if not line: continue
107 name, value = line.strip().split(None, 1)
108 if name == "blocksize":
109 d[name] = value
110 else:
111 d[name + "_size"] = value
112 except KeyError:
113 pass
114
115 def makeint(key):
116 if key in d:
117 d[key] = int(d[key], 0)
118
119 makeint("recovery_api_version")
120 makeint("blocksize")
121 makeint("system_size")
122 makeint("userdata_size")
123 makeint("recovery_size")
124 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700125
126 return d
127
Doug Zongker37974732010-09-16 17:44:38 -0700128def DumpInfoDict(d):
129 for k, v in sorted(d.items()):
130 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700131
Doug Zongker37974732010-09-16 17:44:38 -0700132def BuildAndAddBootableImage(sourcedir, targetname, output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700133 """Take a kernel, cmdline, and ramdisk directory from the input (in
134 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700135 into the output zip file under the name 'targetname'. Returns
136 targetname on success or None on failure (if sourcedir does not
137 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700138
139 print "creating %s..." % (targetname,)
140
141 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700142 if img is None:
143 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700144
Doug Zongker37974732010-09-16 17:44:38 -0700145 CheckSize(img, targetname, info_dict)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700146 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700147 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700148
149def BuildBootableImage(sourcedir):
150 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700151 'sourcedir'), and turn them into a boot image. Return the image
152 data, or None if sourcedir does not appear to contains files for
153 building the requested image."""
154
155 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
156 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
157 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700158
159 ramdisk_img = tempfile.NamedTemporaryFile()
160 img = tempfile.NamedTemporaryFile()
161
162 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
163 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700164 p2 = Run(["minigzip"],
165 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700166
167 p2.wait()
168 p1.wait()
169 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700170 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700171
Doug Zongker38a649f2009-06-17 09:07:09 -0700172 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
173
Doug Zongker171f1cd2009-06-15 22:36:37 -0700174 fn = os.path.join(sourcedir, "cmdline")
175 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700176 cmd.append("--cmdline")
177 cmd.append(open(fn).read().rstrip("\n"))
178
179 fn = os.path.join(sourcedir, "base")
180 if os.access(fn, os.F_OK):
181 cmd.append("--base")
182 cmd.append(open(fn).read().rstrip("\n"))
183
Ying Wang4de6b5b2010-08-25 14:29:34 -0700184 fn = os.path.join(sourcedir, "pagesize")
185 if os.access(fn, os.F_OK):
186 cmd.append("--pagesize")
187 cmd.append(open(fn).read().rstrip("\n"))
188
Doug Zongker38a649f2009-06-17 09:07:09 -0700189 cmd.extend(["--ramdisk", ramdisk_img.name,
190 "--output", img.name])
191
192 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700193 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700194 assert p.returncode == 0, "mkbootimg of %s image failed" % (
195 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700196
197 img.seek(os.SEEK_SET, 0)
198 data = img.read()
199
200 ramdisk_img.close()
201 img.close()
202
203 return data
204
205
Doug Zongker37974732010-09-16 17:44:38 -0700206def AddRecovery(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700207 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
Doug Zongker37974732010-09-16 17:44:38 -0700208 "recovery.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700209
Doug Zongker37974732010-09-16 17:44:38 -0700210def AddBoot(output_zip, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700211 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
Doug Zongker37974732010-09-16 17:44:38 -0700212 "boot.img", output_zip, info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700213
Doug Zongker75f17362009-12-08 13:46:44 -0800214def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700215 """Unzip the given archive into a temporary directory and return the name."""
216
217 tmp = tempfile.mkdtemp(prefix="targetfiles-")
218 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800219 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
220 if pattern is not None:
221 cmd.append(pattern)
222 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700223 p.communicate()
224 if p.returncode != 0:
225 raise ExternalError("failed to unzip input target-files \"%s\"" %
226 (filename,))
227 return tmp
228
229
230def GetKeyPasswords(keylist):
231 """Given a list of keys, prompt the user to enter passwords for
232 those which require them. Return a {key: password} dict. password
233 will be None if the key has no password."""
234
Doug Zongker8ce7c252009-05-22 13:34:54 -0700235 no_passwords = []
236 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700237 devnull = open("/dev/null", "w+b")
238 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800239 # We don't need a password for things that aren't really keys.
240 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700241 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700242 continue
243
Doug Zongker602a84e2009-06-18 08:35:12 -0700244 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
245 "-inform", "DER", "-nocrypt"],
246 stdin=devnull.fileno(),
247 stdout=devnull.fileno(),
248 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700249 p.communicate()
250 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700251 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700252 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700253 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700254 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700255
256 key_passwords = PasswordManager().GetPasswords(need_passwords)
257 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700258 return key_passwords
259
260
Doug Zongker951495f2009-08-14 12:44:19 -0700261def SignFile(input_name, output_name, key, password, align=None,
262 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700263 """Sign the input_name zip/jar/apk, producing output_name. Use the
264 given key and password (the latter may be None if the key does not
265 have a password.
266
267 If align is an integer > 1, zipalign is run to align stored files in
268 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700269
270 If whole_file is true, use the "-w" option to SignApk to embed a
271 signature that covers the whole file in the archive comment of the
272 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700273 """
Doug Zongker951495f2009-08-14 12:44:19 -0700274
Doug Zongkereef39442009-04-02 12:14:19 -0700275 if align == 0 or align == 1:
276 align = None
277
278 if align:
279 temp = tempfile.NamedTemporaryFile()
280 sign_name = temp.name
281 else:
282 sign_name = output_name
283
Doug Zongker09cf5602009-08-14 15:25:06 -0700284 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700285 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
286 if whole_file:
287 cmd.append("-w")
288 cmd.extend([key + ".x509.pem", key + ".pk8",
289 input_name, sign_name])
290
291 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700292 if password is not None:
293 password += "\n"
294 p.communicate(password)
295 if p.returncode != 0:
296 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
297
298 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700299 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700300 p.communicate()
301 if p.returncode != 0:
302 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
303 temp.close()
304
305
Doug Zongker37974732010-09-16 17:44:38 -0700306def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700307 """Check the data string passed against the max size limit, if
308 any, for the given target. Raise exception if the data is too big.
309 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700310
Doug Zongker37974732010-09-16 17:44:38 -0700311 fs_type = info_dict.get("fs_type", None)
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700312 if not fs_type: return
313
Doug Zongkereef39442009-04-02 12:14:19 -0700314 limit = OPTIONS.max_image_size.get(target, None)
315 if limit is None: return
316
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700317 if fs_type == "yaffs2":
318 # image size should be increased by 1/64th to account for the
319 # spare area (64 bytes per 2k page)
320 limit = limit / 2048 * (2048+64)
321
Doug Zongkereef39442009-04-02 12:14:19 -0700322 size = len(data)
323 pct = float(size) * 100.0 / limit
324 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
325 if pct >= 99.0:
326 raise ExternalError(msg)
327 elif pct >= 95.0:
328 print
329 print " WARNING: ", msg
330 print
331 elif OPTIONS.verbose:
332 print " ", msg
333
334
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800335def ReadApkCerts(tf_zip):
336 """Given a target_files ZipFile, parse the META/apkcerts.txt file
337 and return a {package: cert} dict."""
338 certmap = {}
339 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
340 line = line.strip()
341 if not line: continue
342 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
343 r'private_key="(.*)"$', line)
344 if m:
345 name, cert, privkey = m.groups()
346 if cert in SPECIAL_CERT_STRINGS and not privkey:
347 certmap[name] = cert
348 elif (cert.endswith(".x509.pem") and
349 privkey.endswith(".pk8") and
350 cert[:-9] == privkey[:-4]):
351 certmap[name] = cert[:-9]
352 else:
353 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
354 return certmap
355
356
Doug Zongkereef39442009-04-02 12:14:19 -0700357COMMON_DOCSTRING = """
358 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700359 Prepend <dir>/bin to the list of places to search for binaries
360 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700361
Doug Zongker05d3dea2009-06-22 11:32:31 -0700362 -s (--device_specific) <file>
363 Path to the python module containing device-specific
364 releasetools code.
365
Doug Zongker8bec09e2009-11-30 15:37:14 -0800366 -x (--extra) <key=value>
367 Add a key/value pair to the 'extras' dict, which device-specific
368 extension code may look at.
369
Doug Zongkereef39442009-04-02 12:14:19 -0700370 -v (--verbose)
371 Show command lines being executed.
372
373 -h (--help)
374 Display this usage message and exit.
375"""
376
377def Usage(docstring):
378 print docstring.rstrip("\n")
379 print COMMON_DOCSTRING
380
381
382def ParseOptions(argv,
383 docstring,
384 extra_opts="", extra_long_opts=(),
385 extra_option_handler=None):
386 """Parse the options in argv and return any arguments that aren't
387 flags. docstring is the calling module's docstring, to be displayed
388 for errors and -h. extra_opts and extra_long_opts are for flags
389 defined by the caller, which are processed by passing them to
390 extra_option_handler."""
391
392 try:
393 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800394 argv, "hvp:s:x:" + extra_opts,
395 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700396 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700397 except getopt.GetoptError, err:
398 Usage(docstring)
399 print "**", str(err), "**"
400 sys.exit(2)
401
402 path_specified = False
403
404 for o, a in opts:
405 if o in ("-h", "--help"):
406 Usage(docstring)
407 sys.exit()
408 elif o in ("-v", "--verbose"):
409 OPTIONS.verbose = True
410 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700411 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700412 elif o in ("-s", "--device_specific"):
413 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800414 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800415 key, value = a.split("=", 1)
416 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700417 else:
418 if extra_option_handler is None or not extra_option_handler(o, a):
419 assert False, "unknown option \"%s\"" % (o,)
420
Doug Zongker602a84e2009-06-18 08:35:12 -0700421 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
422 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700423
424 return args
425
426
427def Cleanup():
428 for i in OPTIONS.tempfiles:
429 if os.path.isdir(i):
430 shutil.rmtree(i)
431 else:
432 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700433
434
435class PasswordManager(object):
436 def __init__(self):
437 self.editor = os.getenv("EDITOR", None)
438 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
439
440 def GetPasswords(self, items):
441 """Get passwords corresponding to each string in 'items',
442 returning a dict. (The dict may have keys in addition to the
443 values in 'items'.)
444
445 Uses the passwords in $ANDROID_PW_FILE if available, letting the
446 user edit that file to add more needed passwords. If no editor is
447 available, or $ANDROID_PW_FILE isn't define, prompts the user
448 interactively in the ordinary way.
449 """
450
451 current = self.ReadFile()
452
453 first = True
454 while True:
455 missing = []
456 for i in items:
457 if i not in current or not current[i]:
458 missing.append(i)
459 # Are all the passwords already in the file?
460 if not missing: return current
461
462 for i in missing:
463 current[i] = ""
464
465 if not first:
466 print "key file %s still missing some passwords." % (self.pwfile,)
467 answer = raw_input("try to edit again? [y]> ").strip()
468 if answer and answer[0] not in 'yY':
469 raise RuntimeError("key passwords unavailable")
470 first = False
471
472 current = self.UpdateAndReadFile(current)
473
474 def PromptResult(self, current):
475 """Prompt the user to enter a value (password) for each key in
476 'current' whose value is fales. Returns a new dict with all the
477 values.
478 """
479 result = {}
480 for k, v in sorted(current.iteritems()):
481 if v:
482 result[k] = v
483 else:
484 while True:
485 result[k] = getpass.getpass("Enter password for %s key> "
486 % (k,)).strip()
487 if result[k]: break
488 return result
489
490 def UpdateAndReadFile(self, current):
491 if not self.editor or not self.pwfile:
492 return self.PromptResult(current)
493
494 f = open(self.pwfile, "w")
495 os.chmod(self.pwfile, 0600)
496 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
497 f.write("# (Additional spaces are harmless.)\n\n")
498
499 first_line = None
500 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
501 sorted.sort()
502 for i, (_, k, v) in enumerate(sorted):
503 f.write("[[[ %s ]]] %s\n" % (v, k))
504 if not v and first_line is None:
505 # position cursor on first line with no password.
506 first_line = i + 4
507 f.close()
508
509 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
510 _, _ = p.communicate()
511
512 return self.ReadFile()
513
514 def ReadFile(self):
515 result = {}
516 if self.pwfile is None: return result
517 try:
518 f = open(self.pwfile, "r")
519 for line in f:
520 line = line.strip()
521 if not line or line[0] == '#': continue
522 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
523 if not m:
524 print "failed to parse password file: ", line
525 else:
526 result[m.group(2)] = m.group(1)
527 f.close()
528 except IOError, e:
529 if e.errno != errno.ENOENT:
530 print "error reading password file: ", str(e)
531 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700532
533
534def ZipWriteStr(zip, filename, data, perms=0644):
535 # use a fixed timestamp so the output is repeatable.
536 zinfo = zipfile.ZipInfo(filename=filename,
537 date_time=(2009, 1, 1, 0, 0, 0))
538 zinfo.compress_type = zip.compression
539 zinfo.external_attr = perms << 16
540 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700541
542
543class DeviceSpecificParams(object):
544 module = None
545 def __init__(self, **kwargs):
546 """Keyword arguments to the constructor become attributes of this
547 object, which is passed to all functions in the device-specific
548 module."""
549 for k, v in kwargs.iteritems():
550 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800551 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700552
553 if self.module is None:
554 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700555 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700556 try:
557 if os.path.isdir(path):
558 info = imp.find_module("releasetools", [path])
559 else:
560 d, f = os.path.split(path)
561 b, x = os.path.splitext(f)
562 if x == ".py":
563 f = b
564 info = imp.find_module(f, [d])
565 self.module = imp.load_module("device_specific", *info)
566 except ImportError:
567 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700568
569 def _DoCall(self, function_name, *args, **kwargs):
570 """Call the named function in the device-specific module, passing
571 the given args and kwargs. The first argument to the call will be
572 the DeviceSpecific object itself. If there is no module, or the
573 module does not define the function, return the value of the
574 'default' kwarg (which itself defaults to None)."""
575 if self.module is None or not hasattr(self.module, function_name):
576 return kwargs.get("default", None)
577 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
578
579 def FullOTA_Assertions(self):
580 """Called after emitting the block of assertions at the top of a
581 full OTA package. Implementations can add whatever additional
582 assertions they like."""
583 return self._DoCall("FullOTA_Assertions")
584
585 def FullOTA_InstallEnd(self):
586 """Called at the end of full OTA installation; typically this is
587 used to install the image for the device's baseband processor."""
588 return self._DoCall("FullOTA_InstallEnd")
589
590 def IncrementalOTA_Assertions(self):
591 """Called after emitting the block of assertions at the top of an
592 incremental OTA package. Implementations can add whatever
593 additional assertions they like."""
594 return self._DoCall("IncrementalOTA_Assertions")
595
596 def IncrementalOTA_VerifyEnd(self):
597 """Called at the end of the verification phase of incremental OTA
598 installation; additional checks can be placed here to abort the
599 script before any changes are made."""
600 return self._DoCall("IncrementalOTA_VerifyEnd")
601
602 def IncrementalOTA_InstallEnd(self):
603 """Called at the end of incremental OTA installation; typically
604 this is used to install the image for the device's baseband
605 processor."""
606 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700607
608class File(object):
609 def __init__(self, name, data):
610 self.name = name
611 self.data = data
612 self.size = len(data)
613 self.sha1 = sha.sha(data).hexdigest()
614
615 def WriteToTemp(self):
616 t = tempfile.NamedTemporaryFile()
617 t.write(self.data)
618 t.flush()
619 return t
620
621 def AddToZip(self, z):
622 ZipWriteStr(z, self.name, self.data)
623
624DIFF_PROGRAM_BY_EXT = {
625 ".gz" : "imgdiff",
626 ".zip" : ["imgdiff", "-z"],
627 ".jar" : ["imgdiff", "-z"],
628 ".apk" : ["imgdiff", "-z"],
629 ".img" : "imgdiff",
630 }
631
632class Difference(object):
633 def __init__(self, tf, sf):
634 self.tf = tf
635 self.sf = sf
636 self.patch = None
637
638 def ComputePatch(self):
639 """Compute the patch (as a string of data) needed to turn sf into
640 tf. Returns the same tuple as GetPatch()."""
641
642 tf = self.tf
643 sf = self.sf
644
645 ext = os.path.splitext(tf.name)[1]
646 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
647
648 ttemp = tf.WriteToTemp()
649 stemp = sf.WriteToTemp()
650
651 ext = os.path.splitext(tf.name)[1]
652
653 try:
654 ptemp = tempfile.NamedTemporaryFile()
655 if isinstance(diff_program, list):
656 cmd = copy.copy(diff_program)
657 else:
658 cmd = [diff_program]
659 cmd.append(stemp.name)
660 cmd.append(ttemp.name)
661 cmd.append(ptemp.name)
662 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
663 _, err = p.communicate()
664 if err or p.returncode != 0:
665 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
666 return None
667 diff = ptemp.read()
668 finally:
669 ptemp.close()
670 stemp.close()
671 ttemp.close()
672
673 self.patch = diff
674 return self.tf, self.sf, self.patch
675
676
677 def GetPatch(self):
678 """Return a tuple (target_file, source_file, patch_data).
679 patch_data may be None if ComputePatch hasn't been called, or if
680 computing the patch failed."""
681 return self.tf, self.sf, self.patch
682
683
684def ComputeDifferences(diffs):
685 """Call ComputePatch on all the Difference objects in 'diffs'."""
686 print len(diffs), "diffs to compute"
687
688 # Do the largest files first, to try and reduce the long-pole effect.
689 by_size = [(i.tf.size, i) for i in diffs]
690 by_size.sort(reverse=True)
691 by_size = [i[1] for i in by_size]
692
693 lock = threading.Lock()
694 diff_iter = iter(by_size) # accessed under lock
695
696 def worker():
697 try:
698 lock.acquire()
699 for d in diff_iter:
700 lock.release()
701 start = time.time()
702 d.ComputePatch()
703 dur = time.time() - start
704 lock.acquire()
705
706 tf, sf, patch = d.GetPatch()
707 if sf.name == tf.name:
708 name = tf.name
709 else:
710 name = "%s (%s)" % (tf.name, sf.name)
711 if patch is None:
712 print "patching failed! %s" % (name,)
713 else:
714 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
715 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
716 lock.release()
717 except Exception, e:
718 print e
719 raise
720
721 # start worker threads; wait for them all to finish.
722 threads = [threading.Thread(target=worker)
723 for i in range(OPTIONS.worker_threads)]
724 for th in threads:
725 th.start()
726 while threads:
727 threads.pop().join()