Initial working version
[grml2usb.git] / grml2usb.py
index 7411e48..f4e45e9 100755 (executable)
@@ -4,7 +4,7 @@
 grml2usb
 ~~~~~~~~
 
 grml2usb
 ~~~~~~~~
 
-This script installs a grml system to a USB device
+This script installs a grml system (running system / ISO[s]) to a USB device
 
 :copyright: (c) 2009 by Michael Prokop <mika@grml.org>
 :license: GPL v2 or any later version
 
 :copyright: (c) 2009 by Michael Prokop <mika@grml.org>
 :license: GPL v2 or any later version
@@ -13,34 +13,35 @@ This script installs a grml system to a USB device
 TODO
 ----
 
 TODO
 ----
 
+* verify that the specified device is really an USB device (/sys/devices/*/removable_)
+* validate partition schema/layout:
+  -> bootable flag?
+  -> fat16 partition if using syslinux
+* implement missing options (--kernel, --initrd, --uninstall,...)
 * improve error handling :)
 * improve error handling :)
-* implement mount handling
-* log wrapper (-> logging module)
 * implement logic for storing information about copied files
   -> register every single file?
 * implement logic for storing information about copied files
   -> register every single file?
-* trap handling (like unmount devices when interrupting?)
 * get rid of all TODOs in code :)
 * get rid of all TODOs in code :)
-* graphical version
+* graphical version? :)
 """
 
 """
 
+from __future__ import with_statement
 import os, re, subprocess, sys, tempfile
 from optparse import OptionParser
 from os.path import exists, join, abspath
 from os import pathsep
 import os, re, subprocess, sys, tempfile
 from optparse import OptionParser
 from os.path import exists, join, abspath
 from os import pathsep
-# TODO string.split() is deprecated - replace?
-#      -> http://docs.python.org/library/string.html#deprecated-string-functions
-from string import split
+from inspect import isroutine, isclass
+import logging
 
 PROG_VERSION = "0.0.1"
 
 
 PROG_VERSION = "0.0.1"
 
+skip_mbr = False # By default we don't want to skip it; TODO - can we get rid of that?
+
 # cmdline parsing
 # cmdline parsing
-# TODO
-# * --copy-only?
-# * --bootloader-only?
-usage = "Usage: %prog [options] <ISO[s]> <partition>\n\
+usage = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/ice>\n\
 \n\
 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
 \n\
 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
-Make sure you have at least a grml ISO or a running grml system,\n\
+Make sure you have at least a grml ISO or a running grml system (/live/image),\n\
 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
 and root access."
 
 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
 and root access."
 
@@ -48,6 +49,10 @@ parser = OptionParser(usage=usage)
 parser.add_option("--bootoptions", dest="bootoptions",
                   action="store", type="string",
                   help="use specified bootoptions as defaut")
 parser.add_option("--bootoptions", dest="bootoptions",
                   action="store", type="string",
                   help="use specified bootoptions as defaut")
+parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
+                  help="do not copy files only but just install a bootloader")
+parser.add_option("--copy-only", dest="copyonly", action="store_true",
+                  help="copy files only and do not install bootloader")
 parser.add_option("--dry-run", dest="dryrun", action="store_true",
                   help="do not actually execute any commands")
 parser.add_option("--fat16", dest="fat16", action="store_true",
 parser.add_option("--dry-run", dest="dryrun", action="store_true",
                   help="do not actually execute any commands")
 parser.add_option("--fat16", dest="fat16", action="store_true",
@@ -62,6 +67,10 @@ parser.add_option("--kernel", dest="kernel", action="store", type="string",
                   help="install specified kernel instead of the default")
 parser.add_option("--mbr", dest="mbr", action="store_true",
                   help="install master boot record (MBR) on the device")
                   help="install specified kernel instead of the default")
 parser.add_option("--mbr", dest="mbr", action="store_true",
                   help="install master boot record (MBR) on the device")
+parser.add_option("--mountpath", dest="mountpath", action="store_true",
+                  help="install system to specified mount path")
+parser.add_option("--quiet", dest="quiet", action="store_true",
+                  help="do not output anything than errors on console")
 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
                   help="install specified squashfs file instead of the default")
 parser.add_option("--uninstall", dest="uninstall", action="store_true",
 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
                   help="install specified squashfs file instead of the default")
 parser.add_option("--uninstall", dest="uninstall", action="store_true",
@@ -73,13 +82,19 @@ parser.add_option("-v", "--version", dest="version", action="store_true",
 (options, args) = parser.parse_args()
 
 
 (options, args) = parser.parse_args()
 
 
+def get_function_name(obj):
+    if not (isroutine(obj) or isclass(obj)):
+        obj = type(obj)
+    return obj.__module__ + '.' + obj.__name__
+
+
 def execute(f, *args):
     """Wrapper for executing a command. Either really executes
     the command (default) or when using --dry-run commandline option
     just displays what would be executed."""
     # demo: execute(subprocess.Popen, (["ls", "-la"]))
     if options.dryrun:
 def execute(f, *args):
     """Wrapper for executing a command. Either really executes
     the command (default) or when using --dry-run commandline option
     just displays what would be executed."""
     # demo: execute(subprocess.Popen, (["ls", "-la"]))
     if options.dryrun:
-        print "would execute %s(%s) now" % (f, args)
+        logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, args))))
     else:
         return f(*args)
 
     else:
         return f(*args)
 
@@ -112,7 +127,7 @@ def which(program):
 def search_file(filename, search_path='/bin' + pathsep + '/usr/bin'):
     """Given a search path, find file"""
     file_found = 0
 def search_file(filename, search_path='/bin' + pathsep + '/usr/bin'):
     """Given a search path, find file"""
     file_found = 0
-    paths = split(search_path, pathsep)
+    paths = search_path.split(pathsep)
     for path in paths:
         for current_dir, directories, files in os.walk(path):
             if exists(join(current_dir, filename)):
     for path in paths:
         for current_dir, directories, files in os.walk(path):
             if exists(join(current_dir, filename)):
@@ -130,22 +145,27 @@ def check_uid_root():
         sys.exit("Error: please run this script with uid 0 (root).")
 
 
         sys.exit("Error: please run this script with uid 0 (root).")
 
 
-def install_syslinux(device):
+def install_syslinux(device, dry_run=False):
     # TODO
     """Install syslinux on specified device."""
     # TODO
     """Install syslinux on specified device."""
-    print("debug: syslinux %s [TODO]") % device
 
 
-    # syslinux -d boot/isolinux /dev/usb-sdb1
+    # syslinux -d boot/isolinux /dev/sdb1
+    logging.info("Installing syslinux")
+    logging.debug("syslinux -d boot/syslinux %s" % device)
+    proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
+    proc.wait()
+    if proc.returncode != 0:
+        raise Exception, "error executing syslinux"
 
 
 def generate_grub_config(grml_flavour):
     """Generate grub configuration for use via menu,lst"""
 
     # TODO
 
 
 def generate_grub_config(grml_flavour):
     """Generate grub configuration for use via menu,lst"""
 
     # TODO
-    # * what about system using grub2 without having grub available?
-    # * support grub2
-
-    grml_name = grml_flavour
+    # * install main part of configuration just *once* and append
+    #   flavour specific configuration only
+    # * what about systems using grub2 without having grub1 available?
+    # * support grub2?
 
     return("""\
 # misc options:
 
     return("""\
 # misc options:
@@ -156,9 +176,9 @@ foreground  = 000000
 background  = FFCC33
 
 # define entries:
 background  = FFCC33
 
 # define entries:
-title %(grml_name)s  - Default boot (using 1024x768 framebuffer)
-kernel /boot/release/%(grml_name)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_name)s
-initrd /boot/release/%(grml_name)s/initrd.gz
+title %(grml_flavour)s  - Default boot (using 1024x768 framebuffer)
+kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s
+initrd /boot/release/%(grml_flavour)s/initrd.gz
 
 # TODO: extend configuration :)
 """ % locals())
 
 # TODO: extend configuration :)
 """ % locals())
@@ -173,64 +193,79 @@ def generate_isolinux_splash(grml_flavour):
     grml_name = grml_flavour
 
     return("""\
     grml_name = grml_flavour
 
     return("""\
-\ f17\f\18/boot/isolinux/logo.16
+\ f17\f\18/boot/syslinux/logo.16
 
 Some information and boot options available via keys F2 - F10. http://grml.org/
 %(grml_name)s
 """ % locals())
 
 
 Some information and boot options available via keys F2 - F10. http://grml.org/
 %(grml_name)s
 """ % locals())
 
-def generate_syslinux_config(grml_flavour):
-    """Generate configuration for use in syslinux.cfg"""
+def generate_main_syslinux_config(grml_flavour, grml_bootoptions):
+    """Generate main configuration for use in syslinux.cfg"""
 
     # TODO
 
     # TODO
+    # * install main part of configuration just *once* and append
+    #   flavour specific configuration only
     # * unify isolinux and syslinux setup ("INCLUDE /boot/...")
     # * unify isolinux and syslinux setup ("INCLUDE /boot/...")
-
-    grml_name = grml_flavour
+    #   as far as possible
 
     return("""\
 
     return("""\
+## main syslinux configuration - generated by grml2usb
 # use this to control the bootup via a serial port
 # SERIAL 0 9600
 DEFAULT grml
 TIMEOUT 300
 PROMPT 1
 # use this to control the bootup via a serial port
 # SERIAL 0 9600
 DEFAULT grml
 TIMEOUT 300
 PROMPT 1
-DISPLAY /boot/isolinux/boot.msg
-F1 /boot/isolinux/boot.msg
-F2 /boot/isolinux/f2
-F3 /boot/isolinux/f3
-F4 /boot/isolinux/f4
-F5 /boot/isolinux/f5
-F6 /boot/isolinux/f6
-F7 /boot/isolinux/f7
-F8 /boot/isolinux/f8
-F9 /boot/isolinux/f9
-F10 /boot/isolinux/f10
-
-LABEL grml
-KERNEL /boot/release/%(grml_name)s/linux26
-APPEND initrd=/boot/release/%(grml_name)s/initrd.gz apm=power-off lang=us boot=live nomce module=%(grml_name)s
+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
+
+# flavour specific configuration for grml
+LABEL  grml
+KERNEL /boot/release/%(grml_flavour)s/linux26
+APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(grml_bootoptions)s
 
 
-# TODO: extend configuration :)
 """ % locals())
 
 """ % locals())
 
+def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
+    """Generate flavour specific configuration for use in syslinux.cfg"""
+
+    return("""\
+
+# flavour specific configuration for %(grml_flavour)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 quiet module=%(grml_flavour)s %(bootoptions)s
+""" % locals())
 
 
-def install_grub(device):
+
+def install_grub(device, dry_run=False):
     """Install grub on specified device."""
     """Install grub on specified device."""
-    print("grub-install %s") % device
+    logging.critical("TODO: grub-install %s"  % device)
 
 
 
 
-def install_bootloader(partition):
+def install_bootloader(partition, dry_run=False):
     """Install bootloader on device."""
     """Install bootloader on device."""
+
     # Install bootloader on the device (/dev/sda),
     # Install bootloader on the device (/dev/sda),
-    # not on the partition itself (/dev/sda1)
-    if partition[-1:].isdigit():
-        device = re.match(r'(.*?)\d*$', partition).group(1)
-    else:
-        device = partition
+    # not on the partition itself (/dev/sda1)?
+#    if partition[-1:].isdigit():
+#        device = re.match(r'(.*?)\d*$', partition).group(1)
+#    else:
+#        device = partition
 
     if options.grub:
 
     if options.grub:
-        install_grub(device)
+        install_grub(partition, dry_run)
     else:
     else:
-        install_syslinux(device)
+        install_syslinux(partition, dry_run)
 
 
 def is_writeable(device):
 
 
 def is_writeable(device):
@@ -245,7 +280,7 @@ def is_writeable(device):
 
     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
 
 
     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
 
-def install_mbr(device):
+def install_mbr(device, dry_run=False):
     """Install a default master boot record on given device
 
     @device: device where MBR should be installed to"""
     """Install a default master boot record on given device
 
     @device: device where MBR should be installed to"""
@@ -253,43 +288,63 @@ def install_mbr(device):
     if not is_writeable(device):
         raise IOError, "device not writeable for user"
 
     if not is_writeable(device):
         raise IOError, "device not writeable for user"
 
-    lilo = './lilo/lilo.static' # FIXME
+    lilo = '/grml/git/grml2usb/lilo/lilo.static' # FIXME
 
     if not is_exe(lilo):
         raise Exception, "lilo executable not available."
 
     # to support -A for extended partitions:
 
     if not is_exe(lilo):
         raise Exception, "lilo executable not available."
 
     # to support -A for extended partitions:
-    print("debug: ./lilo/lilo.static -S /dev/null -M %s ext") % device
-    proc = subprocess.Popen(["./lilo/lilo.static", "-S", "/dev/null", "-M", device, "ext"])
+    logging.info("Installing MBR")
+    logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
+    proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
     proc.wait()
     if proc.returncode != 0:
         raise Exception, "error executing lilo"
 
     # activate partition:
     proc.wait()
     if proc.returncode != 0:
         raise Exception, "error executing lilo"
 
     # activate partition:
-    print("debug: ./lilo/lilo.static -S /dev/null -A %s 1") % device
-    proc = subprocess.Popen(["./lilo/lilo.static", "-S", "/dev/null", "-A", device, "1"])
-    proc.wait()
-    if proc.returncode != 0:
-        raise Exception, "error executing lilo"
+    logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
+    if not dry_run:
+        proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
+        proc.wait()
+        if proc.returncode != 0:
+            raise Exception, "error executing lilo"
 
     # lilo's mbr is broken, use the one from syslinux instead:
 
     # lilo's mbr is broken, use the one from syslinux instead:
-    print("debug: cat /usr/lib/syslinux/mbr.bin > %s") % device
-    try:
-        # TODO use Popen instead?
-        retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
-        if retcode < 0:
-            print >> sys.stderr, "Error copying MBR to device", -retcode
-    except OSError, error:
-        print >> sys.stderr, "Execution failed:", error
+    logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
+    if not dry_run:
+        try:
+            # TODO use Popen instead?
+            retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
+            if retcode < 0:
+                logging.critical("Error copying MBR to device (%s)" % retcode)
+        except OSError, error:
+            logging.critical("Execution failed:", error)
+
 
 
+def mount(source, target, options):
+    """Mount specified source on given target
+
+    @source: name of device/ISO that should be mounted
+    @target: directory where the ISO should be mounted to
+    @options: mount specific options"""
+
+#   notice: dry_run does not work here, as we have to locate files, identify flavour,...
+    logging.debug("mount %s %s %s" % (options, source, target))
+    proc = subprocess.Popen(["mount"] + list(options) + [source, target])
+    proc.wait()
+    if proc.returncode != 0:
+        raise Exception, "Error executing mount"
 
 
-def loopback_mount(iso, target):
-    """Loopback mount specified ISO on given target
+def unmount(directory):
+    """Unmount specified directory
 
 
-    @iso: name of ISO that should be mounted
-    @target: directory where the ISO should be mounted to"""
+    @directory: directory where something is mounted on and which should be unmounted"""
 
 
-    print("mount -o loop %s %s") % (iso, target)
+    logging.debug("umount %s" % directory)
+    proc = subprocess.Popen(["umount"] + [directory])
+    proc.wait()
+    if proc.returncode != 0:
+        raise Exception, "Error executing umount"
 
 
 def check_for_vat(partition):
 
 
 def check_for_vat(partition):
@@ -303,19 +358,20 @@ def check_for_vat(partition):
         filesystem = udev_info.communicate()[0].rstrip()
 
         if udev_info.returncode == 2:
         filesystem = udev_info.communicate()[0].rstrip()
 
         if udev_info.returncode == 2:
-            print("failed to read device %s - wrong UID / permissions?") % partition
+            logging.critical("failed to read device %s - wrong UID / permissions?" % partition)
             return 1
 
         if filesystem != "vfat":
             return 1
 
         if filesystem != "vfat":
-            return(1)
+            return 1
 
         # TODO
         # * check for ID_FS_VERSION=FAT16 as well?
 
     except OSError:
 
         # TODO
         # * check for ID_FS_VERSION=FAT16 as well?
 
     except OSError:
-        print("Sorry, /lib/udev/vol_id not available.")
+        logging.critical("Sorry, /lib/udev/vol_id not available.")
         return 1
 
         return 1
 
+
 def mkdir(directory):
     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
 
 def mkdir(directory):
     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
 
@@ -327,171 +383,274 @@ def mkdir(directory):
             pass
 
 
             pass
 
 
-def copy_grml_files(grml_flavour, iso_mount, target):
+def copy_grml_files(grml_flavour, iso_mount, target, dry_run=False):
     """Copy files from ISO on given target"""
 
     # TODO
     # * provide alternative search_file() if file information is stored in a config.ini file?
     # * catch "install: .. No space left on device" & CO
     """Copy files from ISO on given target"""
 
     # TODO
     # * provide alternative search_file() if file information is stored in a config.ini file?
     # * catch "install: .. No space left on device" & CO
-    # * abstract copy logic to make the code shorter?
+    # * abstract copy logic to make the code shorter and get rid of spaghetti ;)
+
+    logging.info("Copying files. This might take a while....")
 
     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
     squashfs_target = target + '/live/'
 
     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
     squashfs_target = target + '/live/'
-    mkdir(squashfs_target)
+    execute(mkdir, squashfs_target)
 
     # use install(1) for now to make sure we can write the files afterwards as normal user as well
 
     # use install(1) for now to make sure we can write the files afterwards as normal user as well
-    print("debug: copy squashfs to %s") % target + '/live/' + grml_flavour + '.squashfs'
-    subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
+    logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
+    proc = execute(subprocess.Popen, ["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
+    proc.wait()
 
     filesystem_module = search_file('filesystem.module', iso_mount)
 
     filesystem_module = search_file('filesystem.module', iso_mount)
-    print("debug: copy filesystem.module to %s") % squashfs_target + grml_flavour + '.module'
-    subprocess.Popen(["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
+    logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
+    proc = execute(subprocess.Popen, ["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
+    proc.wait()
 
     release_target = target + '/boot/release/' + grml_flavour
 
     release_target = target + '/boot/release/' + grml_flavour
-    mkdir(release_target)
+    execute(mkdir, release_target)
 
     kernel = search_file('linux26', iso_mount)
 
     kernel = search_file('linux26', iso_mount)
-    print("debug: copy kernel to %s") % release_target + '/linux26'
-    subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
+    logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
+    proc = execute(subprocess.Popen, ["install", "--mode=664", kernel, release_target + '/linux26'])
+    proc.wait()
 
     initrd = search_file('initrd.gz', iso_mount)
 
     initrd = search_file('initrd.gz', iso_mount)
-    print("debug: copy initrd to %s") % release_target + '/initrd.gz'
-    subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
+    logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
+    proc = execute(subprocess.Popen, ["install", "--mode=664", initrd, release_target + '/initrd.gz'])
+    proc.wait()
 
 
-    isolinux_target = target + '/boot/isolinux/'
-    mkdir(isolinux_target)
+    if not options.copyonly:
+        syslinux_target = target + '/boot/syslinux/'
+        execute(mkdir, syslinux_target)
+
+        logo = search_file('logo.16', iso_mount)
+        logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
+        proc = execute(subprocess.Popen, ["install", "--mode=664", logo, syslinux_target + 'logo.16'])
+        proc.wait()
+
+        for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
+            bootsplash = search_file(ffile, iso_mount)
+            logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
+            proc = execute(subprocess.Popen, ["install", "--mode=664", bootsplash, syslinux_target + ffile])
+            proc.wait()
+
+        grub_target = target + '/boot/grub/'
+        execute(mkdir, grub_target)
+
+        logging.debug("cp /grml/git/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz') # FIXME - path of grub
+        proc = execute(subprocess.Popen, ["install", "--mode=664", '/grml/git/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz']) # FIXME
+        proc.wait()
+
+        logging.debug("cp /grml/git/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito') # FIXME - path of grub
+        proc = execute(subprocess.Popen, ["install", "--mode=664", '/grml/git/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito']) # FIXME
+        proc.wait()
+
+        if not dry_run:
+            logging.debug("Generating grub configuration") # % grub_target + 'menu.lst')
+            #with open("...", "w") as f:
+            #f.write("bla bla bal")
+            grub_config_file = open(grub_target + 'menu.lst', 'w')
+            grub_config_file.write(generate_grub_config(grml_flavour))
+            grub_config_file.close()
+
+            logging.info("Generating syslinux configuration") # % syslinux_target + 'syslinux.cfg')
+            syslinux_cfg = syslinux_target + 'syslinux.cfg'
+
+            # install main configuration only *once*, no matter how many ISOs we have:
+            if os.path.isfile(syslinux_cfg):
+                string = open(syslinux_cfg).readline()
+                if not re.match("## main syslinux configuration", string):
+                    syslinux_config_file = open(syslinux_cfg, 'w')
+                    syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, "")) # FIXME - bootoptions
+                    syslinux_config_file.close()
+            else:
+                    syslinux_config_file = open(syslinux_cfg, 'w')
+                    syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, "")) # FIXME - bootoptions
+                    syslinux_config_file.close()
+
+            # install flavour specific configuration only *once* as well
+            # ugly - I'm pretty sure this could be smoother...
+            flavour_config = True
+            if os.path.isfile(syslinux_cfg):
+                string = open(syslinux_cfg).readlines()
+                flavour = re.compile("^# flavour specific configuration for %s"  % re.escape(grml_flavour))
+                for line in string:
+                    if flavour.match(line):
+                        flavour_config = False
+
+            if flavour_config:
+                syslinux_config_file = open(syslinux_cfg, 'a')
+                syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, "")) # FIXME - bootoptions
+                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( )
+
+
+    # make sure we are sync before continuing
+    proc = subprocess.Popen(["sync"])
+    proc.wait()
 
 
-    logo = search_file('logo.16', iso_mount)
-    print("debug: copy logo.16 to %s") % isolinux_target + 'logo.16'
-    subprocess.Popen(["install", "--mode=664", logo, isolinux_target + 'logo.16'])
+def uninstall_files(device):
+    """Get rid of all grml files on specified device"""
 
 
-    for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
-        bootsplash = search_file(ffile, iso_mount)
-        print("debug: copy %s to %s") % (bootsplash, isolinux_target + ffile)
-        subprocess.Popen(["install", "--mode=664", bootsplash, isolinux_target + ffile])
+    # TODO
+    logging.critical("TODO: %s" % device)
 
 
-    grub_target = target + '/boot/grub/'
-    mkdir(grub_target)
 
 
-    print("debug: copy grub/splash.xpm.gz to %s") % grub_target + 'splash.xpm.gz'
-    subprocess.Popen(["install", "--mode=664", 'grub/splash.xpm.gz', grub_target + 'splash.xpm.gz'])
+def identify_grml_flavour(mountpath):
+    """Get name of grml flavour
 
 
-    print("debug: copy grub/stage2_eltorito to %s") % grub_target + 'stage2_eltorito'
-    subprocess.Popen(["install", "--mode=664", 'grub/stage2_eltorito', grub_target + 'stage2_eltorito'])
+    @mountpath: path where the grml ISO is mounted to
+    @return: name of grml-flavour"""
 
 
-    print("debug: generating grub configuration %s") % grub_target + 'menu.lst'
-    grub_config_file = open(grub_target + 'menu.lst', 'w')
-    grub_config_file.write(generate_grub_config(grml_flavour))
-    grub_config_file.close( )
+    version_file = search_file('grml-version', mountpath)
 
 
-    syslinux_target = target + '/boot/isolinux/'
-    mkdir(syslinux_target)
+    if version_file == "":
+        logging.critical("Error: could not find grml-version file.")
+        raise
 
 
-    print("debug: generating syslinux configuration %s") % syslinux_target + 'syslinux.cfg'
-    syslinux_config_file = open(syslinux_target + 'syslinux.cfg', 'w')
-    syslinux_config_file.write(generate_syslinux_config(grml_flavour))
-    syslinux_config_file.close( )
+    try:
+        tmpfile = open(version_file, 'r')
+        grml_info = tmpfile.readline()
+        grml_flavour = re.match(r'[\w-]*', grml_info).group()
+    except TypeError:
+        raise
+    except:
+        logging.critical("Unexpected error:", sys.exc_info()[0])
+        raise
 
 
-    print("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( )
+    return grml_flavour
 
 
+def handle_iso(iso, device):
+    """TODO
+    """
 
 
-def uninstall_files(device):
-    """Get rid of all grml files on specified device"""
+    logging.info("Using ISO %s" % iso)
 
 
-    # TODO
-    print("TODO: %s") % device
+    if os.path.isdir(iso):
+        logging.critical("TODO: /live/image handling not yet implemented") # TODO
+    else:
+        iso_mountpoint = tempfile.mkdtemp()
+        remove_iso_mountpoint = True
+        mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
 
 
+        if os.path.isdir(device):
+            logging.info("Specified target is a directory, not mounting therefore.")
+            device_mountpoint = device
+            remove_device_mountpoint = False
+            skip_mbr = True
 
 
-def identify_grml_flavour(mountpath):
-    """Get name of grml flavour
+        else:
+            device_mountpoint = tempfile.mkdtemp()
+            remove_device_mountpoint = True
+            mount(device, device_mountpoint, "")
 
 
-    @mountpath: path where the grml ISO is mounted to
-    @return: name of grml-flavour"""
+        try:
+            grml_flavour = identify_grml_flavour(iso_mountpoint)
+            logging.info("Identified grml flavour \"%s\"." % grml_flavour)
+            copy_grml_files(grml_flavour, iso_mountpoint, device_mountpoint, dry_run=options.dryrun)
+        except TypeError:
+            logging.critical("Fatal: something happend - TODO")
+            sys.exit(1)
+        finally:
+            if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
+                unmount(iso_mountpoint)
+                os.rmdir(iso_mountpoint)
 
 
-    version_file = search_file('grml-version', mountpath)
+            if remove_device_mountpoint:
+                unmount(device_mountpoint)
 
 
-    tmpfile = open(version_file, 'r')
-    grml_info = tmpfile.readline()
-    #tmpfile.close # pylint: Statement seems to have no effect
+                if os.path.isdir(device_mountpoint):
+                    os.rmdir(device_mountpoint)
 
 
-    grml_flavour = re.match(r'[\w-]*', grml_info).group()
-    return grml_flavour
+        # grml_flavour_short = grml_flavour.replace('-','')
+        # logging.debug("grml_flavour_short = %s" % grml_flavour_short)
 
 
 def main():
     """Main function [make pylint happy :)]"""
 
     if options.version:
 
 
 def main():
     """Main function [make pylint happy :)]"""
 
     if options.version:
-        print("%s %s")% (os.path.basename(sys.argv[0]), PROG_VERSION)
+        print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
         sys.exit(0)
 
     if len(args) < 2:
         parser.error("invalid usage")
 
         sys.exit(0)
 
     if len(args) < 2:
         parser.error("invalid usage")
 
-    # check_uid_root()
+    if options.verbose:
+        FORMAT = "%(asctime)-15s %(message)s"
+        logging.basicConfig(level=logging.DEBUG, format=FORMAT)
+    elif options.quiet:
+        FORMAT = "Critial: %(message)s"
+        logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
+    else:
+        FORMAT = "Info: %(message)s"
+        logging.basicConfig(level=logging.INFO, format=FORMAT)
+
+    if options.dryrun:
+        logging.info("Running in simulate mode as requested via option dry-run.")
+    else:
+        check_uid_root()
 
     device = args[len(args) - 1]
     isos = args[0:len(args) - 1]
 
 
     device = args[len(args) - 1]
     isos = args[0:len(args) - 1]
 
+    # make sure we can replace old grml2usb script and warn user when using old way of life:
+    if device.startswith("/mnt/external") or device.startswith("/mnt/usb"):
+        print "Warning: the semantics of grml2usb has changed."
+        print "Instead of using grml2usb /path/to/iso %s you might" % device
+        print "want to use grml2usb /path/to/iso /dev/... instead."
+        print "Please check out the grml2usb manpage for details."
+        f = raw_input("Do you really want to continue? y/N ")
+        if f == "y" or f == "Y":
+           pass
+        else:
+            sys.exit(1)
+
     if not which("syslinux"):
     if not which("syslinux"):
-        print >> sys.stderr, 'Sorry, syslinux not available. Exiting.'
-        print >> sys.stderr, 'Please install syslinux or consider using the --grub option.'
+        logging.critical('Sorry, syslinux not available. Exiting.')
+        logging.critical('Please install syslinux or consider using the --grub option.')
         sys.exit(1)
 
     # TODO
     # * check for valid blockdevice, vfat and mount functions
     # if device is not None:
         # check_for_vat(device)
         sys.exit(1)
 
     # TODO
     # * check for valid blockdevice, vfat and mount functions
     # if device is not None:
         # check_for_vat(device)
-        # mount_target(partition)
 
 
-    # TODO
-    # target = '/mnt/usb-sdb1'
-    target = tempfile.mkdtemp()
-    print("debug: target = %s") % target
-
-    # TODO
-    # * it doesn't need to be a ISO, could be /live/image as well
     for iso in isos:
     for iso in isos:
-        print("debug: iso = %s") % iso
-        # loopback_mount(iso)
-        iso_mount = '/mnt/test' # FIXME
-        loopback_mount(iso, target) # FIXME s/target/iso_mount/
-        # copy_grml_files(iso, target)
-        # loopback_unmount(iso)
+        handle_iso(iso, device)
 
 
-        grml_flavour = identify_grml_flavour(iso_mount)
-        print("debug: grml_flavour = %s") % grml_flavour
+    if options.mbr and not skip_mbr:
 
 
-        grml_flavour_short = grml_flavour.replace('-','')
-        print("debug: grml_flavour_short = %s") % grml_flavour_short
-
-        # TODO - re-enable :)
-        # copy_grml_files(grml_flavour, iso_mount, target)
-
-    if options.mbr:
         # make sure we install MBR on /dev/sdX and not /dev/sdX#
         if device[-1:].isdigit():
         # make sure we install MBR on /dev/sdX and not /dev/sdX#
         if device[-1:].isdigit():
-            device = re.match(r'(.*?)\d*$', device).group(1)
+            mbr_device = re.match(r'(.*?)\d*$', device).group(1)
 
         try:
 
         try:
-            install_mbr(device)
+            install_mbr(mbr_device, dry_run=options.dryrun)
         except IOError, error:
         except IOError, error:
-            print >> sys.stderr, "Execution failed:", error
+            logging.critical("Execution failed:", error)
             sys.exit(1)
         except Exception, error:
             sys.exit(1)
         except Exception, error:
-            print >> sys.stderr, "Execution failed:", error
+            logging.critical("Execution failed:", error)
             sys.exit(1)
 
             sys.exit(1)
 
-    install_bootloader(device)
+    if options.copyonly:
+        logging.info("Not installing bootloader and its files as requested via option copyonly.")
+    else:
+        install_bootloader(device, dry_run=options.dryrun)
 
 
-    if os.path.isdir(target):
-        os.rmdir(target)
+    logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
 
 if __name__ == "__main__":
 
 if __name__ == "__main__":
-    main()
+    try:
+        main()
+    except KeyboardInterrupt:
+        print "TODO / FIXME: handle me! :)"
 
 ## END OF FILE #################################################################
 # vim:foldmethod=marker expandtab ai ft=python tw=120 fileencoding=utf-8
 
 ## END OF FILE #################################################################
 # vim:foldmethod=marker expandtab ai ft=python tw=120 fileencoding=utf-8