The Apple boot picker provides drive names by reading small icon files off the filesystem. Add support for including them.
146 lines
6.6 KiB
Python
Executable File
146 lines
6.6 KiB
Python
Executable File
#!/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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
# Red Hat Author(s): Will Woods <wwoods@redhat.com>
|
|
|
|
import os, tempfile, argparse
|
|
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, product='Generic',
|
|
diskname=None):
|
|
'''Make an EFI boot image for Apple's EFI implementation'''
|
|
graft = {'EFI/BOOT':bootdir}
|
|
if icon and os.path.exists(icon):
|
|
graft['.VolumeIcon.icns'] = icon
|
|
if diskname and os.path.exists(diskname):
|
|
graft['EFI/BOOT/.disk_label'] = diskname
|
|
mkhfsimg(None, outfile, label=label, graft=graft)
|
|
macmunge(outfile, product)
|
|
|
|
# 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
|
|
#
|
|
# Additionally, we want to do some fixups to make it play nicely with
|
|
# the startup disk preferences panel.
|
|
def macmunge(imgfile, product):
|
|
'''"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(imgfile) as loopdev:
|
|
with Mount(loopdev) as mnt:
|
|
loader = glob.glob(os.path.join(mnt,'EFI/BOOT/BOOT*.efi'))[0]
|
|
config = glob.glob(os.path.join(mnt,'EFI/BOOT/BOOT*.conf'))[0]
|
|
blessnode = os.stat(loader).st_ino
|
|
dirnode = os.stat(os.path.dirname(loader)).st_ino
|
|
with open(os.path.join(mnt,'mach_kernel'), 'w') as kernel:
|
|
kernel.write('Dummy kernel for booting')
|
|
sysdir = os.path.join(mnt,'System/Library/CoreServices/')
|
|
os.makedirs(sysdir)
|
|
with open(os.path.join(sysdir,'SystemVersion.plist'), 'w') as plist:
|
|
plist.write('''<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>ProductBuildVersion</key>
|
|
<string></string>
|
|
<key>ProductName</key>
|
|
<string>Linux</string>
|
|
<key>ProductVersion</key>
|
|
<string>%s</string>
|
|
</dict>
|
|
</plist>
|
|
''' % (product,))
|
|
shutil.copy(loader, os.path.join(sysdir,'boot.efi'))
|
|
shutil.copy(config, os.path.join(sysdir,'boot.conf'))
|
|
# 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, "r+b") 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.'''
|
|
# pjones sez: "17408 is the size of the GPT tables parted creates"
|
|
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 = argparse.ArgumentParser(description="Make an EFI boot image from the given directory.")
|
|
parser.add_argument("-d", "--disk", action="store_true",
|
|
help="make a full EFI disk image (including partition table)")
|
|
parser.add_argument("-a", "--apple", action="store_const", const="apple",
|
|
dest="imgtype", default="default",
|
|
help="make an Apple EFI image (use hfs+, bless bootloader)")
|
|
parser.add_argument("-l", "--label", default="EFI",
|
|
help="filesystem label to use (default: %(default)s)")
|
|
parser.add_argument("-i", "--icon", metavar="ICONFILE",
|
|
help="icon file to include (for Apple EFI image)")
|
|
parser.add_argument("-n", "--diskname", metavar="DISKNAME",
|
|
help="disk name image to include (for Apple EFI image)")
|
|
parser.add_argument("-p", "--product", metavar="PRODUCT",
|
|
help="product name to use (for Apple EFI image)")
|
|
parser.add_argument("bootdir", metavar="EFIBOOTDIR",
|
|
help="input directory (will become /EFI/BOOT in the image)")
|
|
parser.add_argument("outfile", metavar="OUTPUTFILE",
|
|
help="output file to write")
|
|
opt = parser.parse_args()
|
|
# sanity checks
|
|
if not os.path.isdir(opt.bootdir):
|
|
parser.error("%s is not a directory" % opt.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"
|
|
if opt.diskname and not opt.imgtype == "apple":
|
|
print "Warning: --diskname is only useful for Apple EFI images"
|
|
# do the thing!
|
|
if opt.imgtype == "apple":
|
|
mkmacboot(opt.bootdir, opt.outfile, opt.label, opt.icon, opt.product,
|
|
opt.diskname)
|
|
else:
|
|
mkefiboot(opt.bootdir, opt.outfile, opt.label)
|
|
if opt.disk:
|
|
efiboot = tempfile.NamedTemporaryFile(prefix="mkefiboot.").name
|
|
shutil.move(opt.outfile, efiboot)
|
|
mkefidisk(efiboot, opt.outfile)
|