Make syslinux the default bootloader (drop --syslinux, add --grub instead)
[grml2usb.git] / grml2usb
index 86f0567..7af8f44 100755 (executable)
--- a/grml2usb
+++ b/grml2usb
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.6
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 """
 grml2usb
@@ -18,7 +18,7 @@ from inspect import isroutine, isclass
 import datetime, logging, os, re, subprocess, sys, tempfile, time, os.path
 
 # global variables
-PROG_VERSION = "0.9.11"
+PROG_VERSION = "0.9.13-pre1"
 MOUNTED = set()  # register mountpoints
 TMPFILES = set() # register tmpfiles
 DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
@@ -49,6 +49,8 @@ parser.add_option("--fat16", dest="fat16", action="store_true",
                   help="format specified partition with FAT16")
 parser.add_option("--force", dest="force", action="store_true",
                   help="force any actions requiring manual interaction")
+parser.add_option("--grub", dest="grub", action="store_true",
+                  help="install grub bootloader instead of (default) syslinux")
 parser.add_option("--grub-mbr", dest="grubmbr", action="store_true",
                   help="install grub into MBR instead of (default) PBR")
 parser.add_option("--lilo-binary", dest="lilobin",  action="store", type="string",
@@ -66,7 +68,7 @@ parser.add_option("--skip-mbr", dest="skipmbr", action="store_true",
 parser.add_option("--skip-syslinux-config", dest="skipsyslinuxconfig", action="store_true",
                   help="skip generation of syslinux configuration files")
 parser.add_option("--syslinux", dest="syslinux", action="store_true",
-                  help="install syslinux bootloader instead of grub")
+                  help="install syslinux bootloader (deprecated as it's the default)")
 parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true",
                   help="install syslinux master boot record (MBR) instead of default")
 parser.add_option("--verbose", dest="verbose", action="store_true",
@@ -88,6 +90,21 @@ class CriticalException(Exception):
     pass
 
 
+# The following two functions help to operate on strings as
+# array (list) of bytes (octets). In Python 3000, the bytes
+# datatype will need to be used. This is intended for using
+# with manipulation of files on the octet level, like shell
+# arrays, e.g. in MBR creation.
+
+def array2string(a):
+    """Convert a list of integers [0;255] to a string."""
+    return struct.pack("%sB" % len(a), *a)
+
+def string2array(s):
+    """Convert a (bytes) string into a list of integers."""
+    return struct.unpack("%sB" % len(s), s)
+
+
 def cleanup():
     """Cleanup function to make sure there aren't any mounted devices left behind.
     """
@@ -490,125 +507,51 @@ Some information and boot options available via keys F2 - F10. http://grml.org/
 """ % {'grml_name': grml_name} )
 
 
-def generate_main_syslinux_config(grml_flavour, bootoptions):
+def generate_main_syslinux_config(*args):
     """Generate main configuration for use in syslinux.cfg
 
-    @grml_flavour: name of grml flavour the configuration should be generated for
-    @bootoptions: additional bootoptions that should be used by default"""
+    @*args: just for backward compatibility"""
 
     local_datestamp = DATESTAMP
 
     return("""\
-## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
-# use this to control the bootup via a serial port
-# SERIAL 0 9600
-DEFAULT grml
-TIMEOUT 300
-PROMPT 1
-DISPLAY /boot/syslinux/boot.msg
-F1 /boot/syslinux/boot.msg
-F2 /boot/syslinux/f2
-F3 /boot/syslinux/f3
-F4 /boot/syslinux/f4
-F5 /boot/syslinux/f5
-F6 /boot/syslinux/f6
-F7 /boot/syslinux/f7
-F8 /boot/syslinux/f8
-F9 /boot/syslinux/f9
-F10 /boot/syslinux/f10
-## end of main configuration
-
-## global configuration
-# the default option (using %(grml_flavour)s)
-LABEL  grml
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
-
-# memtest
-LABEL  memtest
-KERNEL /boot/addons/memtest
-
-# grub
-LABEL grub
-MENU LABEL grub
-KERNEL /boot/addons/memdisk
-APPEND initrd=/boot/addons/allinone.img
-
-# dos
-LABEL dos
-MENU LABEL dos
-KERNEL /boot/addons/memdisk
-APPEND initrd=/boot/addons/balder10.imz
-
-# bsd
-LABEL bsd
-MENU LABEL bsd
-KERNEL /boot/addons/bsd4grml/ldbsd.com
-
-# hardware detection tool
-MENU LABEL hdt
-KERNEL /boot/addons/hdt.c32
-APPEND pciids=/boot/addons/pci.ids
-
-## end of global configuration
-""" % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
-
-
-def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
+label -
+menu label Default boot modes:
+menu disable
+include defaults.cfg
+
+menu end
+menu separator
+
+# flavours:
+label -
+menu label Additional boot entries for:
+menu disable
+include additional.cfg
+
+include options.cfg
+include addons.cfg
+""")
+
+
+def generate_flavour_specific_syslinux_config(grml_flavour):
     """Generate flavour specific configuration for use in syslinux.cfg
 
-    @grml_flavour: name of grml flavour the configuration should be generated for
-    @bootoptions: bootoptions that should be used as a default"""
+    @grml_flavour: name of grml flavour the configuration should be generated for"""
 
-    local_datestamp = DATESTAMP
 
     return("""\
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-persistent
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet persistent live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s2ram
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-debug
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ debug boot=live initcall_debug%(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-x
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-nofb
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=ofonly %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-failsafe
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal boot=live noautoconfig atapicd noapic noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia nosmp maxcpus=0 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm noresume selinux=0 edd=off pci=nomsi %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-forensic
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ nofstab noraid nolvm noautoconfig noswap raid=noautodetect forensic readonly %(bootoptions)s
-
-# flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
-LABEL  %(grml_flavour)s-serial
-KERNEL /boot/release/%(grml_flavour)s/linux26
-APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=vesafb:off console=tty1 console=ttyS0,9600n8 %(bootoptions)s
-""" % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
+menu begin grml %(grml_flavour)s
+    menu title %(grml_flavour)s
+    label mainmenu
+    menu label ^Back to main menu...
+    menu exit
+    menu separator
+    # include config for boot parameters from disk
+    include %(grml_flavour)s-grml.cfg
+    menu hide
+menu end
+""" % {'grml_flavour': grml_flavour } )
 
 
 def install_grub(device):
@@ -644,7 +587,7 @@ def install_grub(device):
                 proc.wait()
                 if proc.returncode != 0:
                     # raise Exception("error executing grub-install")
-                    logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ or use --syslinux)")
+                    logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ or drop the --grub option)")
                     logging.critical("Note:  if using grub2 consider using the --grub-mbr option because grub2's PBR feature is broken.")
                     cleanup()
                     sys.exit(1)
@@ -684,13 +627,9 @@ def install_bootloader(device):
 
     # by default we use grub, so install syslinux only on request
     if options.syslinux:
-        try:
-            install_syslinux(device)
-        except CriticalException, error:
-            logging.critical("Fatal: %s", error)
-            cleanup()
-            sys.exit(1)
-    else:
+        logging.info("Note: the --syslinux option is deprecated as syslinux is grml2usb's default. Continuing anyway.")
+
+    if options.grub:
         if not which("grub-install"):
             logging.critical("Fatal: grub-install not available (please install the grub package or use the --syslinux option)")
             cleanup()
@@ -702,6 +641,13 @@ def install_bootloader(device):
                 logging.critical("Fatal: %s", error)
                 cleanup()
                 sys.exit(1)
+    else:
+        try:
+            install_syslinux(device)
+        except CriticalException, error:
+            logging.critical("Fatal: %s", error)
+            cleanup()
+            sys.exit(1)
 
 
 def execute_lilo(lilo, device):
@@ -733,7 +679,7 @@ def install_syslinux_mbr(device):
 
     # make sure we have syslinux available
     if not which("syslinux") and not options.copyonly:
-        raise Exception("syslinux not available (either install it or consider dropping the --syslinux option)")
+        raise Exception("syslinux not available (either install it or consider using the --grub option)")
 
     # lilo's mbr is broken, use the one from syslinux instead:
     if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
@@ -1029,6 +975,7 @@ def copy_system_files(grml_flavour, iso_mount, target):
     filesystem_module = search_file('filesystem.module', iso_mount)
     if filesystem_module is None:
         logging.critical("Fatal: filesystem.module not found")
+        raise CriticalException("error locating filesystem.module file")
     else:
         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
 
@@ -1038,12 +985,14 @@ def copy_system_files(grml_flavour, iso_mount, target):
     kernel = search_file('linux26', iso_mount)
     if kernel is None:
         logging.critical("Fatal kernel not found")
+        raise CriticalException("error locating kernel file")
     else:
         exec_rsync(kernel, release_target + '/linux26')
 
     initrd = search_file('initrd.gz', iso_mount)
     if initrd is None:
         logging.critical("Fatal: initrd not found")
+        raise CriticalException("error locating initrd file")
     else:
         exec_rsync(initrd, release_target + '/initrd.gz')
 
@@ -1158,12 +1107,24 @@ def copy_bootloader_files(iso_mount, target):
         bootsplash = search_file(ffile, iso_mount)
         exec_rsync(bootsplash, syslinux_target + ffile)
 
+    if not search_file('default.cfg', iso_mount + '/boot/isolinux/'):
+        logging.critical("Fatal: file default.cfg could not be found.")
+        logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
+        logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
+        raise
+
+    for filename in 'addons.cfg', 'default.cfg', 'distri.cfg', 'hidden.cfg', \
+                    'grml.cfg', 'grml.png', 'hd.cfg', 'isoprompt.cfg', 'options.cfg', \
+                    'vesamenu.c32', 'vesamenu.cfg', 'grml.png':
+        path = search_file(filename, iso_mount + '/boot/isolinux/')
+        exec_rsync(path, syslinux_target + filename)
+
     grub_target = target + '/boot/grub/'
     execute(mkdir, grub_target)
 
     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
-        logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", (GRML2USB_BASE, ))
-        logging.critical("Please make sure you've the grml2usb Debian package installed!")
+        logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
+        logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
         raise
     else:
         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
@@ -1244,8 +1205,8 @@ def identify_grml_flavour(mountpath):
         grml_flavour = re.match(r'[\w-]*', grml_info).group()
     except TypeError:
         raise
-    except:
-        logging.critical("Unexpected error:", sys.exc_info()[0])
+    except Exception, e:
+        logging.critical("Unexpected error: %s", e)
         raise
 
     return grml_flavour
@@ -1379,6 +1340,56 @@ def handle_grub_config(grml_flavour, device, target):
     handle_grub2_config(grml_flavour, grub_target, bootopt)
 
 
+def initial_syslinux_config(target):
+    """Generates intial syslinux configuration
+
+    @target path of syslinux's configuration files"""
+
+    target = target + "/"
+    filename = target + "grmlmain.cfg"
+    if os.path.isfile(target + "grmlmain.cfg"):
+        return
+    data = open(filename, "w")
+    data.write(generate_main_syslinux_config())
+    data.close
+
+def adjust_syslinux_bootoptions(src_name, dst_name, flavour):
+    append_re = re.compile("^(\s*append.*)$", re.I)
+    boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
+    flavour_re = re.compile("(label.*)(grml\w+)")
+    default_re = re.compile("(default.cfg)")
+    src = open(src_name, "r")
+    dst = open(dst_name, "w")
+
+    for line in src:
+        line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour, line) 
+        line = flavour_re.sub(r'\1 %s-\2' % flavour, line) 
+        line = default_re.sub(r'%s-\1' % flavour, line)
+        line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
+        dst.write(line)
+    src.close()
+    dst.close()
+
+
+def add_syslinux_entry(filename, grml_flavour):
+    data = open(filename, "a+")
+    entry_filename = "option-%s.cfg" % grml_flavour
+    entry = "include %s\n" % entry_filename
+    path = os.path.dirname(filename)
+    for line in data:
+        if line == entry:
+            break
+    else:
+        data.write(entry)
+
+    data.close()
+    data = open(path + "/" + entry_filename, "w")
+    data.write(generate_flavour_specific_syslinux_config(grml_flavour))
+    data.close()
+
+
+
+
 def handle_syslinux_config(grml_flavour, target):
     """Main handler for generating syslinux configuration
 
@@ -1401,43 +1412,32 @@ def handle_syslinux_config(grml_flavour, target):
 
     # install main configuration only *once*, no matter how many ISOs we have:
     syslinux_flavour_is_default = False
-    if os.path.isfile(syslinux_cfg):
-        string = open(syslinux_cfg).readline()
-        main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
-        if not re.match(main_identifier, string):
-            syslinux_config_file = open(syslinux_cfg, 'w')
-            GRML_DEFAULT = grml_flavour
-            syslinux_flavour_is_default = True
-            syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
-            syslinux_config_file.close()
+    syslinux_config_file = open(syslinux_cfg, 'w')
+    syslinux_config_file.write("include vesamenu.cfg")
+    syslinux_config_file.close()
+
+    initial_syslinux_config(syslinux_target)
+    for filename in 'grml.cfg', 'default.cfg', 'hidden.cfg':
+        old_filename = "%s/%s" % (syslinux_target, filename)
+        new_filename = "%s/%s-%s" % (syslinux_target, grml_flavour, filename)
+        adjust_syslinux_bootoptions(old_filename, new_filename, grml_flavour)
+        os.unlink(old_filename)
+
+    new_hidden =  "%s-hidden.cfg" % (grml_flavour)
+    new_default = "%s-default.cfg" % (grml_flavour)
+    default_file = open("%s/defaults.cfg" % syslinux_target, "a+")
+    entry = "include %s\n" % new_default
+    for line in default_file:
+        if line == entry:
+            break
     else:
-        syslinux_config_file = open(syslinux_cfg, 'w')
-        GRML_DEFAULT = grml_flavour
-        syslinux_flavour_is_default = True
-        syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
-        syslinux_config_file.close()
+        default_file.write("include %s\n" % new_default)
+
+    default_file.close()
+    add_syslinux_entry("%s/additional.cfg" % syslinux_target, grml_flavour)
 
-    # install flavour specific configuration only *once* as well
-    syslinux_flavour_config = True
-    if os.path.isfile(syslinux_cfg):
-        string = open(syslinux_cfg).readlines()
-        flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
-        for line in string:
-            if flavour.match(line):
-                syslinux_flavour_config = False
 
-    if syslinux_flavour_config:
-        syslinux_config_file = open(syslinux_cfg, 'a')
-        # display only if the grml flavour isn't the default
-        if not syslinux_flavour_is_default:
-            GRML_FLAVOURS.add(grml_flavour)
-        syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
-        syslinux_config_file.close()
 
-    logging.debug("Generating isolinux/syslinux splash %s", syslinux_target + 'boot.msg')
-    isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
-    isolinux_splash.write(generate_isolinux_splash(grml_flavour))
-    isolinux_splash.close()
 
 
 def handle_bootloader_config(grml_flavour, device, target):