# Copyright (C) 2008 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import errno import getopt import getpass import os import re import shutil import subprocess import sys import tempfile # missing in Python 2.4 and before if not hasattr(os, "SEEK_SET"): os.SEEK_SET = 0 class Options(object): pass OPTIONS = Options() OPTIONS.signapk_jar = "out/host/linux-x86/framework/signapk.jar" OPTIONS.dumpkey_jar = "out/host/linux-x86/framework/dumpkey.jar" OPTIONS.max_image_size = {} OPTIONS.verbose = False OPTIONS.tempfiles = [] class ExternalError(RuntimeError): pass def Run(args, **kwargs): """Create and return a subprocess.Popen object, printing the command line on the terminal if -v was specified.""" if OPTIONS.verbose: print " running: ", " ".join(args) return subprocess.Popen(args, **kwargs) def LoadBoardConfig(fn): """Parse a board_config.mk file looking for lines that specify the maximum size of various images, and parse them into the OPTIONS.max_image_size dict.""" OPTIONS.max_image_size = {} for line in open(fn): line = line.strip() m = re.match(r"BOARD_(BOOT|RECOVERY|SYSTEM|USERDATA)IMAGE_MAX_SIZE" r"\s*:=\s*(\d+)", line) if not m: continue OPTIONS.max_image_size[m.group(1).lower() + ".img"] = int(m.group(2)) def BuildAndAddBootableImage(sourcedir, targetname, output_zip): """Take a kernel, cmdline, and ramdisk directory from the input (in 'sourcedir'), and turn them into a boot image. Put the boot image into the output zip file under the name 'targetname'.""" print "creating %s..." % (targetname,) img = BuildBootableImage(sourcedir) CheckSize(img, targetname) output_zip.writestr(targetname, img) def BuildBootableImage(sourcedir): """Take a kernel, cmdline, and ramdisk directory from the input (in 'sourcedir'), and turn them into a boot image. Return the image data.""" ramdisk_img = tempfile.NamedTemporaryFile() img = tempfile.NamedTemporaryFile() p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")], stdout=subprocess.PIPE) p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno()) p2.wait() p1.wait() assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,) assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,) cmdline = open(os.path.join(sourcedir, "cmdline")).read().rstrip("\n") p = Run(["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel"), "--cmdline", cmdline, "--ramdisk", ramdisk_img.name, "--output", img.name], stdout=subprocess.PIPE) p.communicate() assert p.returncode == 0, "mkbootimg of %s image failed" % (targetname,) img.seek(os.SEEK_SET, 0) data = img.read() ramdisk_img.close() img.close() return data def AddRecovery(output_zip): BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"), "recovery.img", output_zip) def AddBoot(output_zip): BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"), "boot.img", output_zip) def UnzipTemp(filename): """Unzip the given archive into a temporary directory and return the name.""" tmp = tempfile.mkdtemp(prefix="targetfiles-") OPTIONS.tempfiles.append(tmp) p = Run(["unzip", "-q", filename, "-d", tmp], stdout=subprocess.PIPE) p.communicate() if p.returncode != 0: raise ExternalError("failed to unzip input target-files \"%s\"" % (filename,)) return tmp def GetKeyPasswords(keylist): """Given a list of keys, prompt the user to enter passwords for those which require them. Return a {key: password} dict. password will be None if the key has no password.""" no_passwords = [] need_passwords = [] devnull = open("/dev/null", "w+b") for k in sorted(keylist): # An empty-string key is used to mean don't re-sign this package. # Obviously we don't need a password for this non-key. if not k: no_passwords.append(k) continue p = subprocess.Popen(["openssl", "pkcs8", "-in", k+".pk8", "-inform", "DER", "-nocrypt"], stdin=devnull.fileno(), stdout=devnull.fileno(), stderr=subprocess.STDOUT) p.communicate() if p.returncode == 0: no_passwords.append(k) else: need_passwords.append(k) devnull.close() key_passwords = PasswordManager().GetPasswords(need_passwords) key_passwords.update(dict.fromkeys(no_passwords, None)) return key_passwords def SignFile(input_name, output_name, key, password, align=None): """Sign the input_name zip/jar/apk, producing output_name. Use the given key and password (the latter may be None if the key does not have a password. If align is an integer > 1, zipalign is run to align stored files in the output zip on 'align'-byte boundaries. """ if align == 0 or align == 1: align = None if align: temp = tempfile.NamedTemporaryFile() sign_name = temp.name else: sign_name = output_name p = subprocess.Popen(["java", "-jar", OPTIONS.signapk_jar, key + ".x509.pem", key + ".pk8", input_name, sign_name], stdin=subprocess.PIPE, stdout=subprocess.PIPE) if password is not None: password += "\n" p.communicate(password) if p.returncode != 0: raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,)) if align: p = subprocess.Popen(["zipalign", "-f", str(align), sign_name, output_name]) p.communicate() if p.returncode != 0: raise ExternalError("zipalign failed: return code %s" % (p.returncode,)) temp.close() def CheckSize(data, target): """Check the data string passed against the max size limit, if any, for the given target. Raise exception if the data is too big. Print a warning if the data is nearing the maximum size.""" limit = OPTIONS.max_image_size.get(target, None) if limit is None: return size = len(data) pct = float(size) * 100.0 / limit msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit) if pct >= 99.0: raise ExternalError(msg) elif pct >= 95.0: print print " WARNING: ", msg print elif OPTIONS.verbose: print " ", msg COMMON_DOCSTRING = """ -p (--path)