blob: ab6678ac69e2d917029998d4660ab7f3e31166d8 [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
124 cmd.extend(["--ramdisk", ramdisk_img.name,
125 "--output", img.name])
126
127 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700128 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700129 assert p.returncode == 0, "mkbootimg of %s image failed" % (
130 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700131
132 img.seek(os.SEEK_SET, 0)
133 data = img.read()
134
135 ramdisk_img.close()
136 img.close()
137
138 return data
139
140
141def AddRecovery(output_zip):
142 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
143 "recovery.img", output_zip)
144
145def AddBoot(output_zip):
146 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
147 "boot.img", output_zip)
148
Doug Zongker75f17362009-12-08 13:46:44 -0800149def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700150 """Unzip the given archive into a temporary directory and return the name."""
151
152 tmp = tempfile.mkdtemp(prefix="targetfiles-")
153 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800154 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
155 if pattern is not None:
156 cmd.append(pattern)
157 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700158 p.communicate()
159 if p.returncode != 0:
160 raise ExternalError("failed to unzip input target-files \"%s\"" %
161 (filename,))
162 return tmp
163
164
165def GetKeyPasswords(keylist):
166 """Given a list of keys, prompt the user to enter passwords for
167 those which require them. Return a {key: password} dict. password
168 will be None if the key has no password."""
169
Doug Zongker8ce7c252009-05-22 13:34:54 -0700170 no_passwords = []
171 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700172 devnull = open("/dev/null", "w+b")
173 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800174 # We don't need a password for things that aren't really keys.
175 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700176 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700177 continue
178
Doug Zongker602a84e2009-06-18 08:35:12 -0700179 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
180 "-inform", "DER", "-nocrypt"],
181 stdin=devnull.fileno(),
182 stdout=devnull.fileno(),
183 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700184 p.communicate()
185 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700186 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700187 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700188 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700189 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700190
191 key_passwords = PasswordManager().GetPasswords(need_passwords)
192 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700193 return key_passwords
194
195
Doug Zongker951495f2009-08-14 12:44:19 -0700196def SignFile(input_name, output_name, key, password, align=None,
197 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700198 """Sign the input_name zip/jar/apk, producing output_name. Use the
199 given key and password (the latter may be None if the key does not
200 have a password.
201
202 If align is an integer > 1, zipalign is run to align stored files in
203 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700204
205 If whole_file is true, use the "-w" option to SignApk to embed a
206 signature that covers the whole file in the archive comment of the
207 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700208 """
Doug Zongker951495f2009-08-14 12:44:19 -0700209
Doug Zongkereef39442009-04-02 12:14:19 -0700210 if align == 0 or align == 1:
211 align = None
212
213 if align:
214 temp = tempfile.NamedTemporaryFile()
215 sign_name = temp.name
216 else:
217 sign_name = output_name
218
Doug Zongker09cf5602009-08-14 15:25:06 -0700219 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700220 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
221 if whole_file:
222 cmd.append("-w")
223 cmd.extend([key + ".x509.pem", key + ".pk8",
224 input_name, sign_name])
225
226 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700227 if password is not None:
228 password += "\n"
229 p.communicate(password)
230 if p.returncode != 0:
231 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
232
233 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700234 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700235 p.communicate()
236 if p.returncode != 0:
237 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
238 temp.close()
239
240
241def CheckSize(data, target):
242 """Check the data string passed against the max size limit, if
243 any, for the given target. Raise exception if the data is too big.
244 Print a warning if the data is nearing the maximum size."""
245 limit = OPTIONS.max_image_size.get(target, None)
246 if limit is None: return
247
248 size = len(data)
249 pct = float(size) * 100.0 / limit
250 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
251 if pct >= 99.0:
252 raise ExternalError(msg)
253 elif pct >= 95.0:
254 print
255 print " WARNING: ", msg
256 print
257 elif OPTIONS.verbose:
258 print " ", msg
259
260
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800261def ReadApkCerts(tf_zip):
262 """Given a target_files ZipFile, parse the META/apkcerts.txt file
263 and return a {package: cert} dict."""
264 certmap = {}
265 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
266 line = line.strip()
267 if not line: continue
268 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
269 r'private_key="(.*)"$', line)
270 if m:
271 name, cert, privkey = m.groups()
272 if cert in SPECIAL_CERT_STRINGS and not privkey:
273 certmap[name] = cert
274 elif (cert.endswith(".x509.pem") and
275 privkey.endswith(".pk8") and
276 cert[:-9] == privkey[:-4]):
277 certmap[name] = cert[:-9]
278 else:
279 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
280 return certmap
281
282
Doug Zongkereef39442009-04-02 12:14:19 -0700283COMMON_DOCSTRING = """
284 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700285 Prepend <dir>/bin to the list of places to search for binaries
286 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700287
Doug Zongker05d3dea2009-06-22 11:32:31 -0700288 -s (--device_specific) <file>
289 Path to the python module containing device-specific
290 releasetools code.
291
Doug Zongker8bec09e2009-11-30 15:37:14 -0800292 -x (--extra) <key=value>
293 Add a key/value pair to the 'extras' dict, which device-specific
294 extension code may look at.
295
Doug Zongkereef39442009-04-02 12:14:19 -0700296 -v (--verbose)
297 Show command lines being executed.
298
299 -h (--help)
300 Display this usage message and exit.
301"""
302
303def Usage(docstring):
304 print docstring.rstrip("\n")
305 print COMMON_DOCSTRING
306
307
308def ParseOptions(argv,
309 docstring,
310 extra_opts="", extra_long_opts=(),
311 extra_option_handler=None):
312 """Parse the options in argv and return any arguments that aren't
313 flags. docstring is the calling module's docstring, to be displayed
314 for errors and -h. extra_opts and extra_long_opts are for flags
315 defined by the caller, which are processed by passing them to
316 extra_option_handler."""
317
318 try:
319 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800320 argv, "hvp:s:x:" + extra_opts,
321 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700322 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700323 except getopt.GetoptError, err:
324 Usage(docstring)
325 print "**", str(err), "**"
326 sys.exit(2)
327
328 path_specified = False
329
330 for o, a in opts:
331 if o in ("-h", "--help"):
332 Usage(docstring)
333 sys.exit()
334 elif o in ("-v", "--verbose"):
335 OPTIONS.verbose = True
336 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700337 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700338 elif o in ("-s", "--device_specific"):
339 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800340 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800341 key, value = a.split("=", 1)
342 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700343 else:
344 if extra_option_handler is None or not extra_option_handler(o, a):
345 assert False, "unknown option \"%s\"" % (o,)
346
Doug Zongker602a84e2009-06-18 08:35:12 -0700347 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
348 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700349
350 return args
351
352
353def Cleanup():
354 for i in OPTIONS.tempfiles:
355 if os.path.isdir(i):
356 shutil.rmtree(i)
357 else:
358 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700359
360
361class PasswordManager(object):
362 def __init__(self):
363 self.editor = os.getenv("EDITOR", None)
364 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
365
366 def GetPasswords(self, items):
367 """Get passwords corresponding to each string in 'items',
368 returning a dict. (The dict may have keys in addition to the
369 values in 'items'.)
370
371 Uses the passwords in $ANDROID_PW_FILE if available, letting the
372 user edit that file to add more needed passwords. If no editor is
373 available, or $ANDROID_PW_FILE isn't define, prompts the user
374 interactively in the ordinary way.
375 """
376
377 current = self.ReadFile()
378
379 first = True
380 while True:
381 missing = []
382 for i in items:
383 if i not in current or not current[i]:
384 missing.append(i)
385 # Are all the passwords already in the file?
386 if not missing: return current
387
388 for i in missing:
389 current[i] = ""
390
391 if not first:
392 print "key file %s still missing some passwords." % (self.pwfile,)
393 answer = raw_input("try to edit again? [y]> ").strip()
394 if answer and answer[0] not in 'yY':
395 raise RuntimeError("key passwords unavailable")
396 first = False
397
398 current = self.UpdateAndReadFile(current)
399
400 def PromptResult(self, current):
401 """Prompt the user to enter a value (password) for each key in
402 'current' whose value is fales. Returns a new dict with all the
403 values.
404 """
405 result = {}
406 for k, v in sorted(current.iteritems()):
407 if v:
408 result[k] = v
409 else:
410 while True:
411 result[k] = getpass.getpass("Enter password for %s key> "
412 % (k,)).strip()
413 if result[k]: break
414 return result
415
416 def UpdateAndReadFile(self, current):
417 if not self.editor or not self.pwfile:
418 return self.PromptResult(current)
419
420 f = open(self.pwfile, "w")
421 os.chmod(self.pwfile, 0600)
422 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
423 f.write("# (Additional spaces are harmless.)\n\n")
424
425 first_line = None
426 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
427 sorted.sort()
428 for i, (_, k, v) in enumerate(sorted):
429 f.write("[[[ %s ]]] %s\n" % (v, k))
430 if not v and first_line is None:
431 # position cursor on first line with no password.
432 first_line = i + 4
433 f.close()
434
435 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
436 _, _ = p.communicate()
437
438 return self.ReadFile()
439
440 def ReadFile(self):
441 result = {}
442 if self.pwfile is None: return result
443 try:
444 f = open(self.pwfile, "r")
445 for line in f:
446 line = line.strip()
447 if not line or line[0] == '#': continue
448 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
449 if not m:
450 print "failed to parse password file: ", line
451 else:
452 result[m.group(2)] = m.group(1)
453 f.close()
454 except IOError, e:
455 if e.errno != errno.ENOENT:
456 print "error reading password file: ", str(e)
457 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700458
459
460def ZipWriteStr(zip, filename, data, perms=0644):
461 # use a fixed timestamp so the output is repeatable.
462 zinfo = zipfile.ZipInfo(filename=filename,
463 date_time=(2009, 1, 1, 0, 0, 0))
464 zinfo.compress_type = zip.compression
465 zinfo.external_attr = perms << 16
466 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700467
468
469class DeviceSpecificParams(object):
470 module = None
471 def __init__(self, **kwargs):
472 """Keyword arguments to the constructor become attributes of this
473 object, which is passed to all functions in the device-specific
474 module."""
475 for k, v in kwargs.iteritems():
476 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800477 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700478
479 if self.module is None:
480 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700481 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700482 try:
483 if os.path.isdir(path):
484 info = imp.find_module("releasetools", [path])
485 else:
486 d, f = os.path.split(path)
487 b, x = os.path.splitext(f)
488 if x == ".py":
489 f = b
490 info = imp.find_module(f, [d])
491 self.module = imp.load_module("device_specific", *info)
492 except ImportError:
493 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700494
495 def _DoCall(self, function_name, *args, **kwargs):
496 """Call the named function in the device-specific module, passing
497 the given args and kwargs. The first argument to the call will be
498 the DeviceSpecific object itself. If there is no module, or the
499 module does not define the function, return the value of the
500 'default' kwarg (which itself defaults to None)."""
501 if self.module is None or not hasattr(self.module, function_name):
502 return kwargs.get("default", None)
503 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
504
505 def FullOTA_Assertions(self):
506 """Called after emitting the block of assertions at the top of a
507 full OTA package. Implementations can add whatever additional
508 assertions they like."""
509 return self._DoCall("FullOTA_Assertions")
510
511 def FullOTA_InstallEnd(self):
512 """Called at the end of full OTA installation; typically this is
513 used to install the image for the device's baseband processor."""
514 return self._DoCall("FullOTA_InstallEnd")
515
516 def IncrementalOTA_Assertions(self):
517 """Called after emitting the block of assertions at the top of an
518 incremental OTA package. Implementations can add whatever
519 additional assertions they like."""
520 return self._DoCall("IncrementalOTA_Assertions")
521
522 def IncrementalOTA_VerifyEnd(self):
523 """Called at the end of the verification phase of incremental OTA
524 installation; additional checks can be placed here to abort the
525 script before any changes are made."""
526 return self._DoCall("IncrementalOTA_VerifyEnd")
527
528 def IncrementalOTA_InstallEnd(self):
529 """Called at the end of incremental OTA installation; typically
530 this is used to install the image for the device's baseband
531 processor."""
532 return self._DoCall("IncrementalOTA_InstallEnd")