blob: 0e17a5fa7033e3cbc40d1354d95cfc3d25f6df57 [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
40class ExternalError(RuntimeError): pass
41
42
43def Run(args, **kwargs):
44 """Create and return a subprocess.Popen object, printing the command
45 line on the terminal if -v was specified."""
46 if OPTIONS.verbose:
47 print " running: ", " ".join(args)
48 return subprocess.Popen(args, **kwargs)
49
50
Doug Zongkerfdd8e692009-08-03 17:27:48 -070051def LoadMaxSizes():
52 """Load the maximum allowable images sizes from the input
53 target_files size."""
Doug Zongkereef39442009-04-02 12:14:19 -070054 OPTIONS.max_image_size = {}
Doug Zongkerfdd8e692009-08-03 17:27:48 -070055 try:
56 for line in open(os.path.join(OPTIONS.input_tmp, "META", "imagesizes.txt")):
Doug Zongker1aca9622009-08-04 15:09:27 -070057 pieces = line.split()
58 if len(pieces) != 2: continue
59 image = pieces[0]
60 size = int(pieces[1])
Doug Zongkerfdd8e692009-08-03 17:27:48 -070061 OPTIONS.max_image_size[image + ".img"] = size
62 except IOError, e:
63 if e.errno == errno.ENOENT:
64 pass
Doug Zongkereef39442009-04-02 12:14:19 -070065
66
67def BuildAndAddBootableImage(sourcedir, targetname, output_zip):
68 """Take a kernel, cmdline, and ramdisk directory from the input (in
69 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -070070 into the output zip file under the name 'targetname'. Returns
71 targetname on success or None on failure (if sourcedir does not
72 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -070073
74 print "creating %s..." % (targetname,)
75
76 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -070077 if img is None:
78 return None
Doug Zongkereef39442009-04-02 12:14:19 -070079
80 CheckSize(img, targetname)
Doug Zongker048e7ca2009-06-15 14:31:53 -070081 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -070082 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -070083
84def BuildBootableImage(sourcedir):
85 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -070086 'sourcedir'), and turn them into a boot image. Return the image
87 data, or None if sourcedir does not appear to contains files for
88 building the requested image."""
89
90 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
91 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
92 return None
Doug Zongkereef39442009-04-02 12:14:19 -070093
94 ramdisk_img = tempfile.NamedTemporaryFile()
95 img = tempfile.NamedTemporaryFile()
96
97 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
98 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -070099 p2 = Run(["minigzip"],
100 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700101
102 p2.wait()
103 p1.wait()
104 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700105 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700106
Doug Zongker38a649f2009-06-17 09:07:09 -0700107 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
108
Doug Zongker171f1cd2009-06-15 22:36:37 -0700109 fn = os.path.join(sourcedir, "cmdline")
110 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700111 cmd.append("--cmdline")
112 cmd.append(open(fn).read().rstrip("\n"))
113
114 fn = os.path.join(sourcedir, "base")
115 if os.access(fn, os.F_OK):
116 cmd.append("--base")
117 cmd.append(open(fn).read().rstrip("\n"))
118
119 cmd.extend(["--ramdisk", ramdisk_img.name,
120 "--output", img.name])
121
122 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700123 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700124 assert p.returncode == 0, "mkbootimg of %s image failed" % (
125 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700126
127 img.seek(os.SEEK_SET, 0)
128 data = img.read()
129
130 ramdisk_img.close()
131 img.close()
132
133 return data
134
135
136def AddRecovery(output_zip):
137 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
138 "recovery.img", output_zip)
139
140def AddBoot(output_zip):
141 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
142 "boot.img", output_zip)
143
Doug Zongker75f17362009-12-08 13:46:44 -0800144def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700145 """Unzip the given archive into a temporary directory and return the name."""
146
147 tmp = tempfile.mkdtemp(prefix="targetfiles-")
148 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800149 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
150 if pattern is not None:
151 cmd.append(pattern)
152 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700153 p.communicate()
154 if p.returncode != 0:
155 raise ExternalError("failed to unzip input target-files \"%s\"" %
156 (filename,))
157 return tmp
158
159
160def GetKeyPasswords(keylist):
161 """Given a list of keys, prompt the user to enter passwords for
162 those which require them. Return a {key: password} dict. password
163 will be None if the key has no password."""
164
Doug Zongker8ce7c252009-05-22 13:34:54 -0700165 no_passwords = []
166 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700167 devnull = open("/dev/null", "w+b")
168 for k in sorted(keylist):
Doug Zongker43874f82009-04-14 14:05:15 -0700169 # An empty-string key is used to mean don't re-sign this package.
170 # Obviously we don't need a password for this non-key.
171 if not k:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700172 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700173 continue
174
Doug Zongker602a84e2009-06-18 08:35:12 -0700175 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
176 "-inform", "DER", "-nocrypt"],
177 stdin=devnull.fileno(),
178 stdout=devnull.fileno(),
179 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700180 p.communicate()
181 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700182 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700183 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700184 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700185 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700186
187 key_passwords = PasswordManager().GetPasswords(need_passwords)
188 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700189 return key_passwords
190
191
Doug Zongker951495f2009-08-14 12:44:19 -0700192def SignFile(input_name, output_name, key, password, align=None,
193 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700194 """Sign the input_name zip/jar/apk, producing output_name. Use the
195 given key and password (the latter may be None if the key does not
196 have a password.
197
198 If align is an integer > 1, zipalign is run to align stored files in
199 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700200
201 If whole_file is true, use the "-w" option to SignApk to embed a
202 signature that covers the whole file in the archive comment of the
203 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700204 """
Doug Zongker951495f2009-08-14 12:44:19 -0700205
Doug Zongkereef39442009-04-02 12:14:19 -0700206 if align == 0 or align == 1:
207 align = None
208
209 if align:
210 temp = tempfile.NamedTemporaryFile()
211 sign_name = temp.name
212 else:
213 sign_name = output_name
214
Doug Zongker09cf5602009-08-14 15:25:06 -0700215 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700216 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
217 if whole_file:
218 cmd.append("-w")
219 cmd.extend([key + ".x509.pem", key + ".pk8",
220 input_name, sign_name])
221
222 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700223 if password is not None:
224 password += "\n"
225 p.communicate(password)
226 if p.returncode != 0:
227 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
228
229 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700230 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700231 p.communicate()
232 if p.returncode != 0:
233 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
234 temp.close()
235
236
237def CheckSize(data, target):
238 """Check the data string passed against the max size limit, if
239 any, for the given target. Raise exception if the data is too big.
240 Print a warning if the data is nearing the maximum size."""
241 limit = OPTIONS.max_image_size.get(target, None)
242 if limit is None: return
243
244 size = len(data)
245 pct = float(size) * 100.0 / limit
246 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
247 if pct >= 99.0:
248 raise ExternalError(msg)
249 elif pct >= 95.0:
250 print
251 print " WARNING: ", msg
252 print
253 elif OPTIONS.verbose:
254 print " ", msg
255
256
257COMMON_DOCSTRING = """
258 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700259 Prepend <dir>/bin to the list of places to search for binaries
260 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700261
Doug Zongker05d3dea2009-06-22 11:32:31 -0700262 -s (--device_specific) <file>
263 Path to the python module containing device-specific
264 releasetools code.
265
Doug Zongker8bec09e2009-11-30 15:37:14 -0800266 -x (--extra) <key=value>
267 Add a key/value pair to the 'extras' dict, which device-specific
268 extension code may look at.
269
Doug Zongkereef39442009-04-02 12:14:19 -0700270 -v (--verbose)
271 Show command lines being executed.
272
273 -h (--help)
274 Display this usage message and exit.
275"""
276
277def Usage(docstring):
278 print docstring.rstrip("\n")
279 print COMMON_DOCSTRING
280
281
282def ParseOptions(argv,
283 docstring,
284 extra_opts="", extra_long_opts=(),
285 extra_option_handler=None):
286 """Parse the options in argv and return any arguments that aren't
287 flags. docstring is the calling module's docstring, to be displayed
288 for errors and -h. extra_opts and extra_long_opts are for flags
289 defined by the caller, which are processed by passing them to
290 extra_option_handler."""
291
292 try:
293 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800294 argv, "hvp:s:x:" + extra_opts,
295 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700296 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700297 except getopt.GetoptError, err:
298 Usage(docstring)
299 print "**", str(err), "**"
300 sys.exit(2)
301
302 path_specified = False
303
304 for o, a in opts:
305 if o in ("-h", "--help"):
306 Usage(docstring)
307 sys.exit()
308 elif o in ("-v", "--verbose"):
309 OPTIONS.verbose = True
310 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700311 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700312 elif o in ("-s", "--device_specific"):
313 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800314 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800315 key, value = a.split("=", 1)
316 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700317 else:
318 if extra_option_handler is None or not extra_option_handler(o, a):
319 assert False, "unknown option \"%s\"" % (o,)
320
Doug Zongker602a84e2009-06-18 08:35:12 -0700321 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
322 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700323
324 return args
325
326
327def Cleanup():
328 for i in OPTIONS.tempfiles:
329 if os.path.isdir(i):
330 shutil.rmtree(i)
331 else:
332 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700333
334
335class PasswordManager(object):
336 def __init__(self):
337 self.editor = os.getenv("EDITOR", None)
338 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
339
340 def GetPasswords(self, items):
341 """Get passwords corresponding to each string in 'items',
342 returning a dict. (The dict may have keys in addition to the
343 values in 'items'.)
344
345 Uses the passwords in $ANDROID_PW_FILE if available, letting the
346 user edit that file to add more needed passwords. If no editor is
347 available, or $ANDROID_PW_FILE isn't define, prompts the user
348 interactively in the ordinary way.
349 """
350
351 current = self.ReadFile()
352
353 first = True
354 while True:
355 missing = []
356 for i in items:
357 if i not in current or not current[i]:
358 missing.append(i)
359 # Are all the passwords already in the file?
360 if not missing: return current
361
362 for i in missing:
363 current[i] = ""
364
365 if not first:
366 print "key file %s still missing some passwords." % (self.pwfile,)
367 answer = raw_input("try to edit again? [y]> ").strip()
368 if answer and answer[0] not in 'yY':
369 raise RuntimeError("key passwords unavailable")
370 first = False
371
372 current = self.UpdateAndReadFile(current)
373
374 def PromptResult(self, current):
375 """Prompt the user to enter a value (password) for each key in
376 'current' whose value is fales. Returns a new dict with all the
377 values.
378 """
379 result = {}
380 for k, v in sorted(current.iteritems()):
381 if v:
382 result[k] = v
383 else:
384 while True:
385 result[k] = getpass.getpass("Enter password for %s key> "
386 % (k,)).strip()
387 if result[k]: break
388 return result
389
390 def UpdateAndReadFile(self, current):
391 if not self.editor or not self.pwfile:
392 return self.PromptResult(current)
393
394 f = open(self.pwfile, "w")
395 os.chmod(self.pwfile, 0600)
396 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
397 f.write("# (Additional spaces are harmless.)\n\n")
398
399 first_line = None
400 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
401 sorted.sort()
402 for i, (_, k, v) in enumerate(sorted):
403 f.write("[[[ %s ]]] %s\n" % (v, k))
404 if not v and first_line is None:
405 # position cursor on first line with no password.
406 first_line = i + 4
407 f.close()
408
409 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
410 _, _ = p.communicate()
411
412 return self.ReadFile()
413
414 def ReadFile(self):
415 result = {}
416 if self.pwfile is None: return result
417 try:
418 f = open(self.pwfile, "r")
419 for line in f:
420 line = line.strip()
421 if not line or line[0] == '#': continue
422 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
423 if not m:
424 print "failed to parse password file: ", line
425 else:
426 result[m.group(2)] = m.group(1)
427 f.close()
428 except IOError, e:
429 if e.errno != errno.ENOENT:
430 print "error reading password file: ", str(e)
431 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700432
433
434def ZipWriteStr(zip, filename, data, perms=0644):
435 # use a fixed timestamp so the output is repeatable.
436 zinfo = zipfile.ZipInfo(filename=filename,
437 date_time=(2009, 1, 1, 0, 0, 0))
438 zinfo.compress_type = zip.compression
439 zinfo.external_attr = perms << 16
440 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700441
442
443class DeviceSpecificParams(object):
444 module = None
445 def __init__(self, **kwargs):
446 """Keyword arguments to the constructor become attributes of this
447 object, which is passed to all functions in the device-specific
448 module."""
449 for k, v in kwargs.iteritems():
450 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800451 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700452
453 if self.module is None:
454 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700455 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700456 try:
457 if os.path.isdir(path):
458 info = imp.find_module("releasetools", [path])
459 else:
460 d, f = os.path.split(path)
461 b, x = os.path.splitext(f)
462 if x == ".py":
463 f = b
464 info = imp.find_module(f, [d])
465 self.module = imp.load_module("device_specific", *info)
466 except ImportError:
467 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700468
469 def _DoCall(self, function_name, *args, **kwargs):
470 """Call the named function in the device-specific module, passing
471 the given args and kwargs. The first argument to the call will be
472 the DeviceSpecific object itself. If there is no module, or the
473 module does not define the function, return the value of the
474 'default' kwarg (which itself defaults to None)."""
475 if self.module is None or not hasattr(self.module, function_name):
476 return kwargs.get("default", None)
477 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
478
479 def FullOTA_Assertions(self):
480 """Called after emitting the block of assertions at the top of a
481 full OTA package. Implementations can add whatever additional
482 assertions they like."""
483 return self._DoCall("FullOTA_Assertions")
484
485 def FullOTA_InstallEnd(self):
486 """Called at the end of full OTA installation; typically this is
487 used to install the image for the device's baseband processor."""
488 return self._DoCall("FullOTA_InstallEnd")
489
490 def IncrementalOTA_Assertions(self):
491 """Called after emitting the block of assertions at the top of an
492 incremental OTA package. Implementations can add whatever
493 additional assertions they like."""
494 return self._DoCall("IncrementalOTA_Assertions")
495
496 def IncrementalOTA_VerifyEnd(self):
497 """Called at the end of the verification phase of incremental OTA
498 installation; additional checks can be placed here to abort the
499 script before any changes are made."""
500 return self._DoCall("IncrementalOTA_VerifyEnd")
501
502 def IncrementalOTA_InstallEnd(self):
503 """Called at the end of incremental OTA installation; typically
504 this is used to install the image for the device's baseband
505 processor."""
506 return self._DoCall("IncrementalOTA_InstallEnd")