From db96a6fa59211aee47669e6153b3f0a53bf7c6f1 Mon Sep 17 00:00:00 2001 From: Tom Callaway Date: Jan 14 2013 22:30:14 +0000 Subject: add efi support for the live media cases [BETA] --- diff --git a/README b/README index 8dcdc0d..ead98c8 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ *** Multiboot Media Creator *** -*** Copyright 2011 Tom Callaway *** +*** Copyright 2011, 2012 Tom Callaway *** == What is it? == * It is a tool that generates a Multiboot ISO image from a set of ISO images, @@ -13,6 +13,7 @@ appropriate item. * It supports both Live and Non-Live (Installation ISO) Images. * It was generated for Fedora, and has hardcoded filename assumptions. (Non Fedora Non-Live support will need some code changes.) +* It has some support for EFI boot == How do I use it ? == Here is a sample invocation: diff --git a/multiboot-media-creator.py b/multiboot-media-creator.py index 8235c7d..4e579f3 100755 --- a/multiboot-media-creator.py +++ b/multiboot-media-creator.py @@ -24,12 +24,27 @@ import re import shutil import subprocess import sys -import time name = 'Multiboot Media Creator' version = 0.6 my_description = '{0} {1}'.format(name, version) +def makeuefidirs(imagedir, verbose): + uefi_dirs = ['EFI/BOOT', 'EFI/BOOT/fonts'] + for dir in uefi_dirs: + if os.path.isdir(os.path.join(imagedir, dir)): + # The directory exists, do nothing. + if verbose: + print '{0} exists, skipping directory creation.'.format(os.path.join(imagedir, dir)) + else: + if verbose: + print 'Creating directory: {0}'.format(os.path.join(imagedir, dir)) + try: + os.makedirs(os.path.join(imagedir, dir)) + except: + error = sys.exc_info()[1] + sys.exit('I was trying to make a UEFI directory as {0}, but I failed with error {1}. Exiting.'.format(os.makedirs(os.path.join(imagedir, dir)), error)) + def makehelperdirs(imagedir, iso_basename, type, verbose): if type == "grub": dirs = ['boot', 'images', 'CHECKSUM'] @@ -49,7 +64,7 @@ def makehelperdirs(imagedir, iso_basename, type, verbose): error = sys.exc_info()[1] sys.exit('I was trying to make an image directory as {0}, but I failed with error {1}. Exiting.'.format(os.makedirs(os.path.join(imagedir, iso_basename, dir)), error)) -def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targetiso, targetname, isolinuxsplash, isodir, nomultiarch, verbose): +def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targetiso, targetname, isolinuxsplash, isodir, nomultiarch, efi, bootdefaultnum, verbose): # Second Sanity Check # Memtest86+ test memtest_list = glob.glob('/boot/memtest86+-*') @@ -58,6 +73,46 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ else: sys.exit('Could not find memtest86+ binary in /boot ? Perhaps memtest86+ is not installed?') + if efi: + uefidir = os.path.join(imagedir, 'EFI/BOOT') + ueficonf = os.path.join(uefidir, 'grub.cfg') + if verbose: + print 'Making the EFI directories for grub2 in IMAGEDIR: {0}'.format(uefidir) + makeuefidirs(imagedir, verbose) + if verbose: + print 'Copying the needed EFI helper files in IMAGEDIR: {0}'.format(uefidir) + shutil.copy2('/boot/efi/EFI/fedora/shim.efi', os.path.join(uefidir, 'BOOTX64.efi')) + shutil.copy2('/boot/efi/EFI/fedora/MokManager.efi', uefidir) + shutil.copy2('/boot/efi/EFI/fedora/gcdx64.efi', os.path.join(uefidir, 'grubx64.efi')) + shutil.copy2('/boot/efi/EFI/fedora/fonts/unicode.pf2', os.path.join(uefidir, 'fonts')) + + # Open our master GRUB2 config file. + masterueficonf = open(ueficonf, 'w') + + # Write our header into the master GRUB2 config file + # There is almost certainly a more appropriate way to do this. + masterueficonf.write('\n') + masterueficonf.write('set default="{0}"\n'.format(bootdefaultnum)) + masterueficonf.write('\n') + masterueficonf.write('function load_video {\n') + masterueficonf.write(' insmod efi_gop\n') + masterueficonf.write(' insmod efi_uga\n') + masterueficonf.write(' insmod video_bochs\n') + masterueficonf.write(' insmod video_cirrus\n') + masterueficonf.write(' insmod all_video\n') + masterueficonf.write('}\n') + masterueficonf.write('\n') + masterueficonf.write('load_video\n') + masterueficonf.write('set gfxpayload=keep\n') + masterueficonf.write('insmod gzio\n') + masterueficonf.write('insmod part_gpt\n') + masterueficonf.write('insmod ext2\n') + masterueficonf.write('\n') + masterueficonf.write('set timeout=10\n') + masterueficonf.write('### END /etc/grub.d/00_header ###\n') + masterueficonf.write('\n') + masterueficonf.write('## BEGIN /etc/grub.d/10_linux ###\n') + # If the nomultiarch flag is set to true, disable multiarch. Otherwise, enable multiarch. if nomultiarch: multiarch = False @@ -207,25 +262,38 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ result = os.system(mount_command) if result: sys.exit('I tried to run {0}, but it failed. Exiting.'.format(mount_command)) - # We used to check for "live" in the isolinux/isolinux.cfg, but all Fedora ISOs now - # pass that check, including the install media! Now, we just look for .discinfo. If it - # is present, we know we're not on a live ISO. - discinfo_file = os.path.join(mountdir, '.discinfo') - if os.path.isfile(discinfo_file): - if verbose: - print '{0} found. Must be an InstallISO.'.format(discinfo_file) - iso_type_live = False - else: - if verbose: - print '{0} _NOT_ found. Must be a LiveISO.'.format(discinfo_file) - iso_type_live = True - - if iso_type_live: + isolinux_config = open(os.path.join(mountdir, 'isolinux/isolinux.cfg'), 'r') + isolinux_config_text = isolinux_config.read() + isolinux_config.close() + search_string = 'live' + index = isolinux_config_text.find(search_string) + # If index is -1, then the search_string is not in the text. + if index >= 0: # Okay, this is a Live ISO. if verbose: print '{0} is a Live ISO. Copying files to {1}.'.format(iso, os.path.join(imagedir, iso_basename)) shutil.copytree(mountdir, os.path.join(imagedir, iso_basename)) + # HACK HACK HACK + # We're copying the first efiboot.img and macboot.img we find. + if efi: + if os.path.isfile(os.path.join(imagedir, 'isolinux/efiboot.img')): + if verbose: + print '{0} already present, nothing to do here.'.format(os.path.join(imagedir, 'isolinux/efiboot.img')) + else: + if os.path.isfile(os.path.join(mountdir, 'isolinux/efiboot.img')): + if verbose: + print 'Copying {0} to {1}.'.format(os.path.join(mountdir, 'isolinux/efiboot.img'), os.path.join(imagedir, 'isolinux/efiboot.img')) + shutil.copy2(os.path.join(mountdir, 'isolinux/efiboot.img'), os.path.join(imagedir, 'isolinux/efiboot.img')) + if os.path.isfile(os.path.join(imagedir, 'isolinux/macboot.img')): + if verbose: + print '{0} already present, nothing to do here.'.format(os.path.join(imagedir, 'isolinux/macboot.img')) + else: + if os.path.isfile(os.path.join(mountdir, 'isolinux/macboot.img')): + if verbose: + print 'Copying {0} to {1}.'.format(os.path.join(mountdir, 'isolinux/macboot.img'), os.path.join(imagedir, 'isolinux/macboot.img')) + shutil.copy2(os.path.join(mountdir, 'isolinux/macboot.img'), os.path.join(imagedir, 'isolinux/macboot.img')) + if pairfound: x86_64_basename = os.path.basename(x86_64_iso) x86_64_iso_basename = os.path.splitext(x86_64_basename)[0] @@ -242,9 +310,9 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ mat.write(' append {0} -- {1}\n'.format(x86_64_iso_basename, iso_basename)) mat.write('\n') - # Write out non-multiboot items - if verbose: - print 'Writing ISO specific entry for {0} and {1} into isolinux configs.'.format(iso_basename, x86_64_iso_basename) + # Write out non-multiboot items + if verbose: + print 'Writing ISO specific entry for {0} and {1} into isolinux configs.'.format(iso_basename, x86_64_iso_basename) # If multiarch is enabled if multiarch: @@ -260,9 +328,6 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ if verbose: print 'Unmounting {0}, and mounting {1} to copy second half of pair to {2}.'.format(iso, x86_64_iso, os.path.join(imagedir, x86_64_iso_basename)) unmount_command = 'umount "{0}"'.format(mountdir) - if verbose: - print 'Sleeping for 3 seconds here, otherwise we might try to umount while GNOME is still looking at it.' - time.sleep(3) result = os.system(unmount_command) if result: sys.exit('I tried to run {0}, but it failed. Exiting.'.format(unmount_command)) @@ -272,6 +337,26 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ sys.exit('I tried to run {0}, but it failed. Exiting.'.format(mount_command)) shutil.copytree(mountdir, os.path.join(imagedir, x86_64_iso_basename)) + # HACK HACK HACK + # We're copying the first efiboot.img and macboot.img we find. + if efi: + if os.path.isfile(os.path.join(imagedir, 'isolinux/efiboot.img')): + if verbose: + print '{0} already present, nothing to do here.'.format(os.path.join(imagedir, 'isolinux/efiboot.img')) + else: + if os.path.isfile(os.path.join(mountdir, 'isolinux/efiboot.img')): + if verbose: + print 'Copying {0} to {1}.'.format(os.path.join(mountdir, 'isolinux/efiboot.img'), os.path.join(imagedir, 'isolinux/efiboot.img')) + shutil.copy2(os.path.join(mountdir, 'isolinux/efiboot.img'), os.path.join(imagedir, 'isolinux/efiboot.img')) + if os.path.isfile(os.path.join(imagedir, 'isolinux/macboot.img')): + if verbose: + print '{0} already present, nothing to do here.'.format(os.path.join(imagedir, 'isolinux/macboot.img')) + else: + if os.path.isfile(os.path.join(mountdir, 'isolinux/macboot.img')): + if verbose: + print 'Copying {0} to {1}.'.format(os.path.join(mountdir, 'isolinux/macboot.img'), os.path.join(imagedir, 'isolinux/macboot.img')) + shutil.copy2(os.path.join(mountdir, 'isolinux/macboot.img'), os.path.join(imagedir, 'isolinux/macboot.img')) + pretty_x86_64_iso_basename = re.sub(r'-', ' ', x86_64_iso_basename) # isolinux can't read directories or files longer than 31 characters. # Truncate if we need to. (Yes, this could cause issues. :P) @@ -289,6 +374,14 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ f64.write(' kernel /{0}/isolinux/vmlinuz0\n'.format(small_x86_64_iso_basename)) f64.write(' append initrd=/{0}/isolinux/initrd0.img root=live:CDLABEL={1} live_dir=/{2}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb rd_NO_LUKS rd_NO_MD rd_NO_DM\n'.format(small_x86_64_iso_basename, targetname, x86_64_iso_basename)) f64.write('\n') + + # Only write out x86_64 targets to the GRUB2 EFI config (no support for 32bit EFI) + if efi: + masterueficonf.write('menuentry \'{0}\' --class fedora --class gnu-linux --class gnu --class os {{\n'.format(pretty_x86_64_iso_basename)) + masterueficonf.write('\tlinuxefi /{0}/isolinux/vmlinuz0 root=live:LABEL={1} ro rd.live.image quiet rhgb live_dir=/{2}/LiveOS/\n'.format(small_x86_64_iso_basename, targetname, x86_64_iso_basename)) + masterueficonf.write('\tinitrdefi /{0}/isolinux/initrd0.img\n'.format(small_x86_64_iso_basename)) + masterueficonf.write('}\n') + # Now, pull x86_64_iso out of 'isolist' try: isolist.remove(x86_64_iso) @@ -307,6 +400,12 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ fnopair.write(' append initrd=/{0}/isolinux/initrd0.img root=live:CDLABEL={1} live_dir=/{2}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb rd_NO_LUKS rd_NO_MD rd_NO_DM\n'.format(small_iso_basename, targetname, iso_basename)) fnopair.write('\n') + if efi: + masterueficonf.write('menuentry \'{0}\' --class fedora --class gnu-linux --class gnu --class os {{\n'.format(pretty_iso_basename)) + masterueficonf.write('\tlinuxefi /{0}/isolinux/vmlinuz0 root=live:LABEL={1} ro rd.live.image quiet rhgb live_dir=/{2}/LiveOS/\n'.format(small_iso_basename, targetname, iso_basename)) + masterueficonf.write('\tinitrdefi /{0}/isolinux/initrd0.img\n'.format(small_iso_basename)) + masterueficonf.write('}\n') + # Multiarch disabled else: f.write('label {0}\n'.format(iso_basename)) @@ -318,6 +417,12 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ f.write(' append initrd=/{0}/isolinux/initrd0.img root=live:CDLABEL={1} live_dir=/{2}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb rd_NO_LUKS rd_NO_MD rd_NO_DM\n'.format(small_iso_basename, targetname, iso_basename)) f.write('\n') + if efi: + masterueficonf.write('menuentry \'{0}\' --class fedora --class gnu-linux --class gnu --class os {{\n'.format(pretty_iso_basename)) + masterueficonf.write('\tlinuxefi /{0}/isolinux/vmlinuz0 root=live:LABEL={1} ro rd.live.image quiet rhgb live_dir=/{2}/LiveOS/\n'.format(small_iso_basename, targetname, iso_basename)) + masterueficonf.write('\tinitrdefi /{0}/isolinux/initrd0.img\n'.format(small_iso_basename)) + masterueficonf.write('}\n') + # Now, we write out the basic video entry bvt.write('label {0}_basicvideo\n'.format(iso_basename)) bvt.write(' menu label {0} (Basic Video)\n'.format(pretty_iso_basename)) @@ -381,9 +486,6 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ if verbose: print 'Copying second half of iso pair to {1}.'.format(x86_64_iso, os.path.join(imagedir, x86_64_iso_basename)) unmount_command = 'umount "{0}"'.format(mountdir) - if verbose: - print 'Sleeping for 3 seconds here, otherwise we might try to umount while GNOME is still looking at it.' - time.sleep(3) result = os.system(unmount_command) if result: sys.exit('I tried to run {0}, but it failed. Exiting.'.format(unmount_command)) @@ -592,6 +694,8 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ # We're done writing to the master isolinux.cfg file! masterconf.close() + if efi: + masterueficonf.close() if verbose: print 'Copying /usr/share/syslinux/isolinux.bin to {0}'.format(isolinuxdir) shutil.copy2('/usr/share/syslinux/isolinux.bin', isolinuxdir) @@ -603,12 +707,22 @@ def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultiso, targ if verbose: print 'Copying /usr/share/syslinux/ifcpu64.c32 to {0}'.format(isolinuxdir) shutil.copy2('/usr/share/syslinux/ifcpu64.c32', isolinuxdir) - mkisofs_command = '/usr/bin/mkisofs -allow-leading-dots -allow-multidot -l -relaxed-filenames -no-iso-translate -R -v -V {0} -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -allow-limited-size -o {1} {2}'.format(targetname, targetiso, imagedir) + if efi: + mkisofs_command = '/usr/bin/mkisofs -allow-leading-dots -allow-multidot -l -relaxed-filenames -no-iso-translate -R -v -V {0} -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e isolinux/efiboot.img -no-emul-boot -eltorito-alt-boot -e isolinux/macboot.img -no-emul-boot -allow-limited-size -o {1} {2}'.format(targetname, targetiso, imagedir) + else: + mkisofs_command = '/usr/bin/mkisofs -allow-leading-dots -allow-multidot -l -relaxed-filenames -no-iso-translate -R -v -V {0} -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -allow-limited-size -o {1} {2}'.format(targetname, targetiso, imagedir) if verbose: print 'Running mkisofs to make {0}:'.format(targetiso) print mkisofs_command os.system(mkisofs_command) # subprocess.Popen(mkisofs_command) + if verbose: + print 'Running isohybrid on the ISO' + if efi: + isohybrid_command = '/usr/bin/isohybrid --uefi --mac {0}'.format(targetiso) + else: + isohybrid_command = '/usr/bin/isohybrid {0}'.format(targetiso) + os.system(isohybrid_command) def makegrubimage(isolist, imagedir, mountdir, timeout, bootdefaultnum, grubarch, targetiso, targetname, isodir, verbose): # Make the grub directory @@ -637,20 +751,13 @@ def makegrubimage(isolist, imagedir, mountdir, timeout, bootdefaultnum, grubarch result = os.system(mount_command) if result: sys.exit('I tried to run {0}, but it failed. Exiting.'.format(mount_command)) - # We used to check for "live" in the isolinux/isolinux.cfg, but all Fedora ISOs now - # pass that check, including the install media! Now, we just look for .discinfo. If it - # is present, we know we're not on a live ISO. - discinfo_file = os.path.join(mountdir, '.discinfo') - if os.path.isfile(discinfo_file): - if verbose: - print '{0} found. Must be an InstallISO.'.format(discinfo_file) - iso_type_live = False - else: - if verbose: - print '{0} _NOT_ found. Must be a LiveISO.'.format(discinfo_file) - iso_type_live = True - - if iso_type_live: + isolinux_config = open(os.path.join(mountdir, 'isolinux/isolinux.cfg'), 'r') + isolinux_config_text = isolinux_config.read() + isolinux_config.close() + search_string = 'live' + index = isolinux_config_text.find(search_string) + # If index is -1, then the search_string is not in the text. + if index >= 0: # Okay, this is a Live ISO. if verbose: print '{0} is a Live ISO. Copying files to {1}.'.format(iso, os.path.join(imagedir, iso_basename)) @@ -771,6 +878,9 @@ if __name__ == '__main__': parser.add_argument('--clean', action = 'store_true', help = 'Clean out and remove any files found in IMAGEDIR before starting.') + parser.add_argument('--efi', action = 'store_true', + help = 'Enable EFI support.') + parser.add_argument('--grub', action = 'store_true', help = 'Use grub to handle the boot menu. The default is to use isolinux.') @@ -917,6 +1027,25 @@ if __name__ == '__main__': print item print 'The default is {0} in position {1}'.format(isolist[bootdefaultnum], bootdefaultnum) + if args.efi: + # Sanity check for shim + if os.path.isfile('/boot/efi/EFI/fedora/shim.efi'): + if args.verbose: + print 'Found /boot/efi/EFI/fedora/shim.efi. Assuming shim is installed properly' + else: + sys.exit('Could not find /boot/efi/EFI/fedora/shim.efi ? Perhaps the shim package is not installed?') + # Sanity check for shim-unsigned + if os.path.isfile('/boot/efi/EFI/fedora/MokManager.efi'): + if args.verbose: + print 'Found /boot/efi/EFI/fedora/MokManager.efi. Assuming shim-unsigned is installed properly' + else: + sys.exit('Could not find /boot/efi/EFI/fedora/MokManager.efi ? Perhaps the shim-unsigned package is not installed?') + # Sanity check for grub2 EFI files + if os.path.isfile('/boot/efi/EFI/fedora/gcdx64.efi'): + if args.verbose: + print 'Found /boot/efi/EFI/fedora/gcdx64.efi. Assuming grub2-efi is installed properly' + else: + sys.exit('Could not find /boot/efi/EFI/fedora/gcdx64.efi ? Perhaps the grub2-efi package is not installed?') if args.grub: # Generating multiboot grub config @@ -935,4 +1064,4 @@ if __name__ == '__main__': print 'Found /usr/share/syslinux/isolinux.bin. Assuming syslinux is installed properly' else: sys.exit('Could not find /usr/share/syslinux/isolinux.bin ? Perhaps syslinux is not installed?') - makeisolinuximage(isolist, args.imagedir, args.mountdir, args.timeout, bootdefaultiso, args.target, args.targetname, args.isolinuxsplash, args.isodir, args.nomultiarch, args.verbose) + makeisolinuximage(isolist, args.imagedir, args.mountdir, args.timeout, bootdefaultiso, args.target, args.targetname, args.isolinuxsplash, args.isodir, args.nomultiarch, args.efi, bootdefaultnum, args.verbose)