blob: 041daf44027195b0ecb1eda1bc618b1b7457ee74 [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
144def UnzipTemp(filename):
145 """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 Zongkere05628c2009-08-20 17:38:42 -0700149 p = Run(["unzip", "-o", "-q", filename, "-d", tmp], stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700150 p.communicate()
151 if p.returncode != 0:
152 raise ExternalError("failed to unzip input target-files \"%s\"" %
153 (filename,))
154 return tmp
155
156
157def GetKeyPasswords(keylist):
158 """Given a list of keys, prompt the user to enter passwords for
159 those which require them. Return a {key: password} dict. password
160 will be None if the key has no password."""
161
Doug Zongker8ce7c252009-05-22 13:34:54 -0700162 no_passwords = []
163 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700164 devnull = open("/dev/null", "w+b")
165 for k in sorted(keylist):
Doug Zongker43874f82009-04-14 14:05:15 -0700166 # An empty-string key is used to mean don't re-sign this package.
167 # Obviously we don't need a password for this non-key.
168 if not k:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700169 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700170 continue
171
Doug Zongker602a84e2009-06-18 08:35:12 -0700172 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
173 "-inform", "DER", "-nocrypt"],
174 stdin=devnull.fileno(),
175 stdout=devnull.fileno(),
176 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700177 p.communicate()
178 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700179 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700180 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700181 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700182 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700183
184 key_passwords = PasswordManager().GetPasswords(need_passwords)
185 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700186 return key_passwords
187
188
Doug Zongker951495f2009-08-14 12:44:19 -0700189def SignFile(input_name, output_name, key, password, align=None,
190 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700191 """Sign the input_name zip/jar/apk, producing output_name. Use the
192 given key and password (the latter may be None if the key does not
193 have a password.
194
195 If align is an integer > 1, zipalign is run to align stored files in
196 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700197
198 If whole_file is true, use the "-w" option to SignApk to embed a
199 signature that covers the whole file in the archive comment of the
200 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700201 """
Doug Zongker951495f2009-08-14 12:44:19 -0700202
Doug Zongkereef39442009-04-02 12:14:19 -0700203 if align == 0 or align == 1:
204 align = None
205
206 if align:
207 temp = tempfile.NamedTemporaryFile()
208 sign_name = temp.name
209 else:
210 sign_name = output_name
211
Doug Zongker09cf5602009-08-14 15:25:06 -0700212 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700213 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
214 if whole_file:
215 cmd.append("-w")
216 cmd.extend([key + ".x509.pem", key + ".pk8",
217 input_name, sign_name])
218
219 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700220 if password is not None:
221 password += "\n"
222 p.communicate(password)
223 if p.returncode != 0:
224 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
225
226 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700227 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700228 p.communicate()
229 if p.returncode != 0:
230 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
231 temp.close()
232
233
234def CheckSize(data, target):
235 """Check the data string passed against the max size limit, if
236 any, for the given target. Raise exception if the data is too big.
237 Print a warning if the data is nearing the maximum size."""
238 limit = OPTIONS.max_image_size.get(target, None)
239 if limit is None: return
240
241 size = len(data)
242 pct = float(size) * 100.0 / limit
243 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
244 if pct >= 99.0:
245 raise ExternalError(msg)
246 elif pct >= 95.0:
247 print
248 print " WARNING: ", msg
249 print
250 elif OPTIONS.verbose:
251 print " ", msg
252
253
254COMMON_DOCSTRING = """
255 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700256 Prepend <dir>/bin to the list of places to search for binaries
257 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700258
Doug Zongker05d3dea2009-06-22 11:32:31 -0700259 -s (--device_specific) <file>
260 Path to the python module containing device-specific
261 releasetools code.
262
Doug Zongker8bec09e2009-11-30 15:37:14 -0800263 -x (--extra) <key=value>
264 Add a key/value pair to the 'extras' dict, which device-specific
265 extension code may look at.
266
Doug Zongkereef39442009-04-02 12:14:19 -0700267 -v (--verbose)
268 Show command lines being executed.
269
270 -h (--help)
271 Display this usage message and exit.
272"""
273
274def Usage(docstring):
275 print docstring.rstrip("\n")
276 print COMMON_DOCSTRING
277
278
279def ParseOptions(argv,
280 docstring,
281 extra_opts="", extra_long_opts=(),
282 extra_option_handler=None):
283 """Parse the options in argv and return any arguments that aren't
284 flags. docstring is the calling module's docstring, to be displayed
285 for errors and -h. extra_opts and extra_long_opts are for flags
286 defined by the caller, which are processed by passing them to
287 extra_option_handler."""
288
289 try:
290 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800291 argv, "hvp:s:x:" + extra_opts,
292 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700293 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700294 except getopt.GetoptError, err:
295 Usage(docstring)
296 print "**", str(err), "**"
297 sys.exit(2)
298
299 path_specified = False
300
301 for o, a in opts:
302 if o in ("-h", "--help"):
303 Usage(docstring)
304 sys.exit()
305 elif o in ("-v", "--verbose"):
306 OPTIONS.verbose = True
307 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700308 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700309 elif o in ("-s", "--device_specific"):
310 OPTIONS.device_specific = a
Doug Zongker8bec09e2009-11-30 15:37:14 -0800311 elif o in ("-x" "--extra"):
312 key, value = a.split("=", 1)
313 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700314 else:
315 if extra_option_handler is None or not extra_option_handler(o, a):
316 assert False, "unknown option \"%s\"" % (o,)
317
Doug Zongker602a84e2009-06-18 08:35:12 -0700318 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
319 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700320
321 return args
322
323
324def Cleanup():
325 for i in OPTIONS.tempfiles:
326 if os.path.isdir(i):
327 shutil.rmtree(i)
328 else:
329 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700330
331
332class PasswordManager(object):
333 def __init__(self):
334 self.editor = os.getenv("EDITOR", None)
335 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
336
337 def GetPasswords(self, items):
338 """Get passwords corresponding to each string in 'items',
339 returning a dict. (The dict may have keys in addition to the
340 values in 'items'.)
341
342 Uses the passwords in $ANDROID_PW_FILE if available, letting the
343 user edit that file to add more needed passwords. If no editor is
344 available, or $ANDROID_PW_FILE isn't define, prompts the user
345 interactively in the ordinary way.
346 """
347
348 current = self.ReadFile()
349
350 first = True
351 while True:
352 missing = []
353 for i in items:
354 if i not in current or not current[i]:
355 missing.append(i)
356 # Are all the passwords already in the file?
357 if not missing: return current
358
359 for i in missing:
360 current[i] = ""
361
362 if not first:
363 print "key file %s still missing some passwords." % (self.pwfile,)
364 answer = raw_input("try to edit again? [y]> ").strip()
365 if answer and answer[0] not in 'yY':
366 raise RuntimeError("key passwords unavailable")
367 first = False
368
369 current = self.UpdateAndReadFile(current)
370
371 def PromptResult(self, current):
372 """Prompt the user to enter a value (password) for each key in
373 'current' whose value is fales. Returns a new dict with all the
374 values.
375 """
376 result = {}
377 for k, v in sorted(current.iteritems()):
378 if v:
379 result[k] = v
380 else:
381 while True:
382 result[k] = getpass.getpass("Enter password for %s key> "
383 % (k,)).strip()
384 if result[k]: break
385 return result
386
387 def UpdateAndReadFile(self, current):
388 if not self.editor or not self.pwfile:
389 return self.PromptResult(current)
390
391 f = open(self.pwfile, "w")
392 os.chmod(self.pwfile, 0600)
393 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
394 f.write("# (Additional spaces are harmless.)\n\n")
395
396 first_line = None
397 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
398 sorted.sort()
399 for i, (_, k, v) in enumerate(sorted):
400 f.write("[[[ %s ]]] %s\n" % (v, k))
401 if not v and first_line is None:
402 # position cursor on first line with no password.
403 first_line = i + 4
404 f.close()
405
406 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
407 _, _ = p.communicate()
408
409 return self.ReadFile()
410
411 def ReadFile(self):
412 result = {}
413 if self.pwfile is None: return result
414 try:
415 f = open(self.pwfile, "r")
416 for line in f:
417 line = line.strip()
418 if not line or line[0] == '#': continue
419 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
420 if not m:
421 print "failed to parse password file: ", line
422 else:
423 result[m.group(2)] = m.group(1)
424 f.close()
425 except IOError, e:
426 if e.errno != errno.ENOENT:
427 print "error reading password file: ", str(e)
428 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700429
430
431def ZipWriteStr(zip, filename, data, perms=0644):
432 # use a fixed timestamp so the output is repeatable.
433 zinfo = zipfile.ZipInfo(filename=filename,
434 date_time=(2009, 1, 1, 0, 0, 0))
435 zinfo.compress_type = zip.compression
436 zinfo.external_attr = perms << 16
437 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700438
439
440class DeviceSpecificParams(object):
441 module = None
442 def __init__(self, **kwargs):
443 """Keyword arguments to the constructor become attributes of this
444 object, which is passed to all functions in the device-specific
445 module."""
446 for k, v in kwargs.iteritems():
447 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800448 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700449
450 if self.module is None:
451 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700452 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700453 try:
454 if os.path.isdir(path):
455 info = imp.find_module("releasetools", [path])
456 else:
457 d, f = os.path.split(path)
458 b, x = os.path.splitext(f)
459 if x == ".py":
460 f = b
461 info = imp.find_module(f, [d])
462 self.module = imp.load_module("device_specific", *info)
463 except ImportError:
464 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700465
466 def _DoCall(self, function_name, *args, **kwargs):
467 """Call the named function in the device-specific module, passing
468 the given args and kwargs. The first argument to the call will be
469 the DeviceSpecific object itself. If there is no module, or the
470 module does not define the function, return the value of the
471 'default' kwarg (which itself defaults to None)."""
472 if self.module is None or not hasattr(self.module, function_name):
473 return kwargs.get("default", None)
474 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
475
476 def FullOTA_Assertions(self):
477 """Called after emitting the block of assertions at the top of a
478 full OTA package. Implementations can add whatever additional
479 assertions they like."""
480 return self._DoCall("FullOTA_Assertions")
481
482 def FullOTA_InstallEnd(self):
483 """Called at the end of full OTA installation; typically this is
484 used to install the image for the device's baseband processor."""
485 return self._DoCall("FullOTA_InstallEnd")
486
487 def IncrementalOTA_Assertions(self):
488 """Called after emitting the block of assertions at the top of an
489 incremental OTA package. Implementations can add whatever
490 additional assertions they like."""
491 return self._DoCall("IncrementalOTA_Assertions")
492
493 def IncrementalOTA_VerifyEnd(self):
494 """Called at the end of the verification phase of incremental OTA
495 installation; additional checks can be placed here to abort the
496 script before any changes are made."""
497 return self._DoCall("IncrementalOTA_VerifyEnd")
498
499 def IncrementalOTA_InstallEnd(self):
500 """Called at the end of incremental OTA installation; typically
501 this is used to install the image for the device's baseband
502 processor."""
503 return self._DoCall("IncrementalOTA_InstallEnd")