#!/usr/bin/python # mkefiboot - a tool to make EFI boot images # Copyright (C) 2011 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Red Hat Author(s): Will Woods import os, tempfile, optparse from subprocess import check_call, PIPE from pylorax.imgutils import mkdosimg, round_to_blocks, LoopDev, DMDev, dm_detach from pylorax.imgutils import mkhfsimg, Mount import struct, shutil, glob def mkefiboot(bootdir, outfile, label): '''Make an EFI boot image with the contents of bootdir in EFI/BOOT''' mkdosimg(None, outfile, label=label, graft={'EFI/BOOT':bootdir}) def mkmacboot(bootdir, outfile, label, icon=None): '''Make an EFI boot image for Apple's EFI implementation''' graft = {'EFI/BOOT':bootdir} if icon: graft['.VolumeIcon.icns'] = icon mkhfsimg(None, outfile, label=label, graft=graft) macbless(outfile) # To make an HFS+ image bootable, we need to fill in parts of the # HFSPlusVolumeHeader structure - specifically, finderInfo[0,1,5]. # For details, see Technical Note TN1150: HFS Plus Volume Format # http://developer.apple.com/library/mac/#technotes/tn/tn1150.html def macbless(imgfile): '''"bless" the EFI bootloader inside the given Mac EFI boot image, by writing its inode info into the HFS+ volume header.''' # Get the inode number for the boot image and its parent directory with LoopDev(outfile) as loopdev: with Mount(loopdev) as mnt: loader = glob.glob(os.path.join(mnt,'EFI/BOOT/BOOT*.efi'))[0] blessnode = os.stat(loader).st_ino dirnode = os.stat(os.path.dirname(loader)).st_ino # format data properly (big-endian UInt32) nodedata = struct.pack(">i", blessnode) dirdata = struct.pack(">i", dirnode) # Write it to the volume header with open(imgfile, "wb") as img: img.seek(0x450) # HFSPlusVolumeHeader->finderInfo img.write(dirdata) # finderInfo[0] img.write(nodedata) # finderInfo[1] img.seek(0x464) # img.write(dirdata) # finderInfo[5] def mkefidisk(efiboot, outfile): '''Make a bootable EFI disk image out of the given EFI boot image.''' partsize = os.path.getsize(efiboot) + 17408 disksize = round_to_blocks(17408 + partsize, 512) with LoopDev(outfile, disksize) as loopdev: with DMDev(loopdev, disksize) as dmdev: check_call(["parted", "--script", "/dev/mapper/%s" % dmdev, "mklabel", "gpt", "unit", "b", "mkpart", "'EFI System Partition'", "fat32", "17408", str(partsize), "set", "1", "boot", "on"], stdout=PIPE, stderr=PIPE) partdev = "/dev/mapper/{0}p1".format(dmdev) with open(efiboot, "rb") as infile: with open(partdev, "wb") as outfile: outfile.write(infile.read()) dm_detach(dmdev+"p1") if __name__ == '__main__': parser = optparse.OptionParser(usage="%prog EFIBOOTDIR IMGFILE") parser.add_option("-d", "--disk", action="store_true", default=False, help="make a full EFI disk image") parser.add_option("-l", "--label", action="store", default="ANACONDA", help="filesystem label to use") parser.add_option("-a", "--apple", action="store_const", const="apple", dest="imgtype", default="default", help="make an Apple EFI image (hfsplus, bless loader)") parser.add_option("-i", "--icon", action="store", help="icon file to include (for Apple EFI image)") # get args (opt, args) = parser.parse_args() if len(args) != 2: parser.error("need exactly two arguments") (bootdir, outfile) = args # sanity checks if not os.path.isdir(bootdir): parser.error("%s is not a directory" % bootdir) if os.getuid() > 0: parser.error("need root permissions") if opt.icon and not opt.imgtype == "apple": print "Warning: --icon is only useful for Apple EFI images" # do the thing! if opt.imgtype == "apple": mkmacboot(bootdir, outfile, opt.label, opt.icon) else: mkefiboot(bootdir, outfile, opt.label) if opt.disk: efiboot = tempfile.NamedTemporaryFile(prefix="mkefiboot.").name shutil.move(outfile, efiboot) mkefidisk(efiboot, outfile)