blob: 037c4cf3ebd1bfc8669d97c31188bea15501ccee [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 Zongker8ce7c252009-05-22 13:34:54 -070015import errno
Doug Zongkereef39442009-04-02 12:14:19 -070016import getopt
17import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070018import imp
Doug Zongkereef39442009-04-02 12:14:19 -070019import os
20import re
21import shutil
22import subprocess
23import sys
24import tempfile
Doug Zongker048e7ca2009-06-15 14:31:53 -070025import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070026
27# missing in Python 2.4 and before
28if not hasattr(os, "SEEK_SET"):
29 os.SEEK_SET = 0
30
31class Options(object): pass
32OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070033OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070034OPTIONS.max_image_size = {}
35OPTIONS.verbose = False
36OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070037OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080038OPTIONS.extras = {}
Doug Zongkereef39442009-04-02 12:14:19 -070039
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080040
41# Values for "certificate" in apkcerts that mean special things.
42SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
43
44
Doug Zongkereef39442009-04-02 12:14:19 -070045class ExternalError(RuntimeError): pass
46
47
48def Run(args, **kwargs):
49 """Create and return a subprocess.Popen object, printing the command
50 line on the terminal if -v was specified."""
51 if OPTIONS.verbose:
52 print " running: ", " ".join(args)
53 return subprocess.Popen(args, **kwargs)
54
55
Doug Zongkerfdd8e692009-08-03 17:27:48 -070056def LoadMaxSizes():
57 """Load the maximum allowable images sizes from the input
58 target_files size."""
Doug Zongkereef39442009-04-02 12:14:19 -070059 OPTIONS.max_image_size = {}
Doug Zongkerfdd8e692009-08-03 17:27:48 -070060 try:
61 for line in open(os.path.join(OPTIONS.input_tmp, "META", "imagesizes.txt")):
Doug Zongker1aca9622009-08-04 15:09:27 -070062 pieces = line.split()
63 if len(pieces) != 2: continue
64 image = pieces[0]
65 size = int(pieces[1])
Doug Zongkerfdd8e692009-08-03 17:27:48 -070066 OPTIONS.max_image_size[image + ".img"] = size
67 except IOError, e:
68 if e.errno == errno.ENOENT:
69 pass
Doug Zongkereef39442009-04-02 12:14:19 -070070
71
72def BuildAndAddBootableImage(sourcedir, targetname, output_zip):
73 """Take a kernel, cmdline, and ramdisk directory from the input (in
74 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -070075 into the output zip file under the name 'targetname'. Returns
76 targetname on success or None on failure (if sourcedir does not
77 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -070078
79 print "creating %s..." % (targetname,)
80
81 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -070082 if img is None:
83 return None
Doug Zongkereef39442009-04-02 12:14:19 -070084
85 CheckSize(img, targetname)
Doug Zongker048e7ca2009-06-15 14:31:53 -070086 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -070087 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -070088
89def BuildBootableImage(sourcedir):
90 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -070091 'sourcedir'), and turn them into a boot image. Return the image
92 data, or None if sourcedir does not appear to contains files for
93 building the requested image."""
94
95 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
96 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
97 return None
Doug Zongkereef39442009-04-02 12:14:19 -070098
99 ramdisk_img = tempfile.NamedTemporaryFile()
100 img = tempfile.NamedTemporaryFile()
101
102 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
103 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700104 p2 = Run(["minigzip"],
105 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700106
107 p2.wait()
108 p1.wait()
109 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700110 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700111
Doug Zongker38a649f2009-06-17 09:07:09 -0700112 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
113
Doug Zongker171f1cd2009-06-15 22:36:37 -0700114 fn = os.path.join(sourcedir, "cmdline")
115 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700116 cmd.append("--cmdline")
117 cmd.append(open(fn).read().rstrip("\n"))
118
119 fn = os.path.join(sourcedir, "base")
120 if os.access(fn, os.F_OK):
121 cmd.append("--base")
122 cmd.append(open(fn).read().rstrip("\n"))
123
Ying Wang4de6b5b2010-08-25 14:29:34 -0700124 fn = os.path.join(sourcedir, "pagesize")
125 if os.access(fn, os.F_OK):
126 cmd.append("--pagesize")
127 cmd.append(open(fn).read().rstrip("\n"))
128
Doug Zongker38a649f2009-06-17 09:07:09 -0700129 cmd.extend(["--ramdisk", ramdisk_img.name,
130 "--output", img.name])
131
132 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700133 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700134 assert p.returncode == 0, "mkbootimg of %s image failed" % (
135 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700136
137 img.seek(os.SEEK_SET, 0)
138 data = img.read()
139
140 ramdisk_img.close()
141 img.close()
142
143 return data
144
145
146def AddRecovery(output_zip):
147 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
148 "recovery.img", output_zip)
149
150def AddBoot(output_zip):
151 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
152 "boot.img", output_zip)
153
Doug Zongker75f17362009-12-08 13:46:44 -0800154def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700155 """Unzip the given archive into a temporary directory and return the name."""
156
157 tmp = tempfile.mkdtemp(prefix="targetfiles-")
158 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800159 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
160 if pattern is not None:
161 cmd.append(pattern)
162 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700163 p.communicate()
164 if p.returncode != 0:
165 raise ExternalError("failed to unzip input target-files \"%s\"" %
166 (filename,))
167 return tmp
168
169
170def GetKeyPasswords(keylist):
171 """Given a list of keys, prompt the user to enter passwords for
172 those which require them. Return a {key: password} dict. password
173 will be None if the key has no password."""
174
Doug Zongker8ce7c252009-05-22 13:34:54 -0700175 no_passwords = []
176 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700177 devnull = open("/dev/null", "w+b")
178 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800179 # We don't need a password for things that aren't really keys.
180 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700181 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700182 continue
183
Doug Zongker602a84e2009-06-18 08:35:12 -0700184 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
185 "-inform", "DER", "-nocrypt"],
186 stdin=devnull.fileno(),
187 stdout=devnull.fileno(),
188 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700189 p.communicate()
190 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700191 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700192 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700193 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700194 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700195
196 key_passwords = PasswordManager().GetPasswords(need_passwords)
197 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700198 return key_passwords
199
200
Doug Zongker951495f2009-08-14 12:44:19 -0700201def SignFile(input_name, output_name, key, password, align=None,
202 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700203 """Sign the input_name zip/jar/apk, producing output_name. Use the
204 given key and password (the latter may be None if the key does not
205 have a password.
206
207 If align is an integer > 1, zipalign is run to align stored files in
208 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700209
210 If whole_file is true, use the "-w" option to SignApk to embed a
211 signature that covers the whole file in the archive comment of the
212 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700213 """
Doug Zongker951495f2009-08-14 12:44:19 -0700214
Doug Zongkereef39442009-04-02 12:14:19 -0700215 if align == 0 or align == 1:
216 align = None
217
218 if align:
219 temp = tempfile.NamedTemporaryFile()
220 sign_name = temp.name
221 else:
222 sign_name = output_name
223
Doug Zongker09cf5602009-08-14 15:25:06 -0700224 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700225 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
226 if whole_file:
227 cmd.append("-w")
228 cmd.extend([key + ".x509.pem", key + ".pk8",
229 input_name, sign_name])
230
231 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700232 if password is not None:
233 password += "\n"
234 p.communicate(password)
235 if p.returncode != 0:
236 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
237
238 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700239 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700240 p.communicate()
241 if p.returncode != 0:
242 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
243 temp.close()
244
245
246def CheckSize(data, target):
247 """Check the data string passed against the max size limit, if
248 any, for the given target. Raise exception if the data is too big.
249 Print a warning if the data is nearing the maximum size."""
250 limit = OPTIONS.max_image_size.get(target, None)
251 if limit is None: return
252
253 size = len(data)
254 pct = float(size) * 100.0 / limit
255 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
256 if pct >= 99.0:
257 raise ExternalError(msg)
258 elif pct >= 95.0:
259 print
260 print " WARNING: ", msg
261 print
262 elif OPTIONS.verbose:
263 print " ", msg
264
265
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800266def ReadApkCerts(tf_zip):
267 """Given a target_files ZipFile, parse the META/apkcerts.txt file
268 and return a {package: cert} dict."""
269 certmap = {}
270 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
271 line = line.strip()
272 if not line: continue
273 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
274 r'private_key="(.*)"$', line)
275 if m:
276 name, cert, privkey = m.groups()
277 if cert in SPECIAL_CERT_STRINGS and not privkey:
278 certmap[name] = cert
279 elif (cert.endswith(".x509.pem") and
280 privkey.endswith(".pk8") and
281 cert[:-9] == privkey[:-4]):
282 certmap[name] = cert[:-9]
283 else:
284 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
285 return certmap
286
287
Doug Zongkereef39442009-04-02 12:14:19 -0700288COMMON_DOCSTRING = """
289 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700290 Prepend <dir>/bin to the list of places to search for binaries
291 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700292
Doug Zongker05d3dea2009-06-22 11:32:31 -0700293 -s (--device_specific) <file>
294 Path to the python module containing device-specific
295 releasetools code.
296
Doug Zongker8bec09e2009-11-30 15:37:14 -0800297 -x (--extra) <key=value>
298 Add a key/value pair to the 'extras' dict, which device-specific
299 extension code may look at.
300
Doug Zongkereef39442009-04-02 12:14:19 -0700301 -v (--verbose)
302 Show command lines being executed.
303
304 -h (--help)
305 Display this usage message and exit.
306"""
307
308def Usage(docstring):
309 print docstring.rstrip("\n")
310 print COMMON_DOCSTRING
311
312
313def ParseOptions(argv,
314 docstring,
315 extra_opts="", extra_long_opts=(),
316 extra_option_handler=None):
317 """Parse the options in argv and return any arguments that aren't
318 flags. docstring is the calling module's docstring, to be displayed
319 for errors and -h. extra_opts and extra_long_opts are for flags
320 defined by the caller, which are processed by passing them to
321 extra_option_handler."""
322
323 try:
324 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800325 argv, "hvp:s:x:" + extra_opts,
326 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700327 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700328 except getopt.GetoptError, err:
329 Usage(docstring)
330 print "**", str(err), "**"
331 sys.exit(2)
332
333 path_specified = False
334
335 for o, a in opts:
336 if o in ("-h", "--help"):
337 Usage(docstring)
338 sys.exit()
339 elif o in ("-v", "--verbose"):
340 OPTIONS.verbose = True
341 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700342 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700343 elif o in ("-s", "--device_specific"):
344 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800345 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800346 key, value = a.split("=", 1)
347 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700348 else:
349 if extra_option_handler is None or not extra_option_handler(o, a):
350 assert False, "unknown option \"%s\"" % (o,)
351
Doug Zongker602a84e2009-06-18 08:35:12 -0700352 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
353 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700354
355 return args
356
357
358def Cleanup():
359 for i in OPTIONS.tempfiles:
360 if os.path.isdir(i):
361 shutil.rmtree(i)
362 else:
363 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700364
365
366class PasswordManager(object):
367 def __init__(self):
368 self.editor = os.getenv("EDITOR", None)
369 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
370
371 def GetPasswords(self, items):
372 """Get passwords corresponding to each string in 'items',
373 returning a dict. (The dict may have keys in addition to the
374 values in 'items'.)
375
376 Uses the passwords in $ANDROID_PW_FILE if available, letting the
377 user edit that file to add more needed passwords. If no editor is
378 available, or $ANDROID_PW_FILE isn't define, prompts the user
379 interactively in the ordinary way.
380 """
381
382 current = self.ReadFile()
383
384 first = True
385 while True:
386 missing = []
387 for i in items:
388 if i not in current or not current[i]:
389 missing.append(i)
390 # Are all the passwords already in the file?
391 if not missing: return current
392
393 for i in missing:
394 current[i] = ""
395
396 if not first:
397 print "key file %s still missing some passwords." % (self.pwfile,)
398 answer = raw_input("try to edit again? [y]> ").strip()
399 if answer and answer[0] not in 'yY':
400 raise RuntimeError("key passwords unavailable")
401 first = False
402
403 current = self.UpdateAndReadFile(current)
404
405 def PromptResult(self, current):
406 """Prompt the user to enter a value (password) for each key in
407 'current' whose value is fales. Returns a new dict with all the
408 values.
409 """
410 result = {}
411 for k, v in sorted(current.iteritems()):
412 if v:
413 result[k] = v
414 else:
415 while True:
416 result[k] = getpass.getpass("Enter password for %s key> "
417 % (k,)).strip()
418 if result[k]: break
419 return result
420
421 def UpdateAndReadFile(self, current):
422 if not self.editor or not self.pwfile:
423 return self.PromptResult(current)
424
425 f = open(self.pwfile, "w")
426 os.chmod(self.pwfile, 0600)
427 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
428 f.write("# (Additional spaces are harmless.)\n\n")
429
430 first_line = None
431 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
432 sorted.sort()
433 for i, (_, k, v) in enumerate(sorted):
434 f.write("[[[ %s ]]] %s\n" % (v, k))
435 if not v and first_line is None:
436 # position cursor on first line with no password.
437 first_line = i + 4
438 f.close()
439
440 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
441 _, _ = p.communicate()
442
443 return self.ReadFile()
444
445 def ReadFile(self):
446 result = {}
447 if self.pwfile is None: return result
448 try:
449 f = open(self.pwfile, "r")
450 for line in f:
451 line = line.strip()
452 if not line or line[0] == '#': continue
453 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
454 if not m:
455 print "failed to parse password file: ", line
456 else:
457 result[m.group(2)] = m.group(1)
458 f.close()
459 except IOError, e:
460 if e.errno != errno.ENOENT:
461 print "error reading password file: ", str(e)
462 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700463
464
465def ZipWriteStr(zip, filename, data, perms=0644):
466 # use a fixed timestamp so the output is repeatable.
467 zinfo = zipfile.ZipInfo(filename=filename,
468 date_time=(2009, 1, 1, 0, 0, 0))
469 zinfo.compress_type = zip.compression
470 zinfo.external_attr = perms << 16
471 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700472
473
474class DeviceSpecificParams(object):
475 module = None
476 def __init__(self, **kwargs):
477 """Keyword arguments to the constructor become attributes of this
478 object, which is passed to all functions in the device-specific
479 module."""
480 for k, v in kwargs.iteritems():
481 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800482 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700483
484 if self.module is None:
485 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700486 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700487 try:
488 if os.path.isdir(path):
489 info = imp.find_module("releasetools", [path])
490 else:
491 d, f = os.path.split(path)
492 b, x = os.path.splitext(f)
493 if x == ".py":
494 f = b
495 info = imp.find_module(f, [d])
496 self.module = imp.load_module("device_specific", *info)
497 except ImportError:
498 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700499
500 def _DoCall(self, function_name, *args, **kwargs):
501 """Call the named function in the device-specific module, passing
502 the given args and kwargs. The first argument to the call will be
503 the DeviceSpecific object itself. If there is no module, or the
504 module does not define the function, return the value of the
505 'default' kwarg (which itself defaults to None)."""
506 if self.module is None or not hasattr(self.module, function_name):
507 return kwargs.get("default", None)
508 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
509
510 def FullOTA_Assertions(self):
511 """Called after emitting the block of assertions at the top of a
512 full OTA package. Implementations can add whatever additional
513 assertions they like."""
514 return self._DoCall("FullOTA_Assertions")
515
516 def FullOTA_InstallEnd(self):
517 """Called at the end of full OTA installation; typically this is
518 used to install the image for the device's baseband processor."""
519 return self._DoCall("FullOTA_InstallEnd")
520
521 def IncrementalOTA_Assertions(self):
522 """Called after emitting the block of assertions at the top of an
523 incremental OTA package. Implementations can add whatever
524 additional assertions they like."""
525 return self._DoCall("IncrementalOTA_Assertions")
526
527 def IncrementalOTA_VerifyEnd(self):
528 """Called at the end of the verification phase of incremental OTA
529 installation; additional checks can be placed here to abort the
530 script before any changes are made."""
531 return self._DoCall("IncrementalOTA_VerifyEnd")
532
533 def IncrementalOTA_InstallEnd(self):
534 """Called at the end of incremental OTA installation; typically
535 this is used to install the image for the device's baseband
536 processor."""
537 return self._DoCall("IncrementalOTA_InstallEnd")