Do not execute mkfs.fat without prompting; update docs
[grml2usb.git] / grml2usb.py
index 5583ba5..c2de631 100755 (executable)
@@ -4,7 +4,7 @@
 grml2usb
 ~~~~~~~~
 
 grml2usb
 ~~~~~~~~
 
-This script installs a grml system to a USB device
+This script installs a grml system (either a running system or 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,62 +13,74 @@ This script installs a grml system to a USB device
 TODO
 ----
 
 TODO
 ----
 
-* implement missing options (--kernel, --initrd, --uninstall,...)
-* improve error handling :)
-* get rid of "if not dry_run" inside code/functions
-* implement mount handling
-* log wrapper (-> logging module)
-* 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 :)
-* graphical version
+* install memtest, dos, grub,... to /boot/addons/
+* copy grml files to /grml/
+* implement missing options (--grub, --kernel, --initrd, --squashfs, --uninstall)
+* code improvements:
+  - improve error handling wherever possible :)
+  - get rid of all TODOs in code
+  - use 'with open("...", "w") as f: ... f.write("...")'
+  - simplify functions/code as much as possible (move stuff to further functions) -> audit
+* validate partition schema/layout: is the partition schema ok and the bootable flag set? (--validate?)
+* implement logic for storing information about copied files -> register every file in a set()
+* the last line in bootsplash (boot.msg) should mention all installed grml flavours
+* graphical version? any volunteers? :)
 """
 
 """
 
+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
+import datetime, time
 
 
+# global variables
 PROG_VERSION = "0.0.1"
 PROG_VERSION = "0.0.1"
+skip_mbr = True  # hm, can we get rid of that? :)
+mounted = set()  # register mountpoints
+tmpfiles = set() # register tmpfiles
+datestamp= time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
 
 # cmdline parsing
 
 # cmdline parsing
-# TODO
-# * --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."
 
 parser = OptionParser(usage=usage)
 parser.add_option("--bootoptions", dest="bootoptions",
                   action="store", type="string",
 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
 and root access."
 
 parser = OptionParser(usage=usage)
 parser.add_option("--bootoptions", dest="bootoptions",
                   action="store", type="string",
-                  help="use specified bootoptions as defaut")
+                  help="use specified bootoptions as default")
+parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
+                  help="do not copy files but just install a bootloader")
 parser.add_option("--copy-only", dest="copyonly", action="store_true",
 parser.add_option("--copy-only", dest="copyonly", action="store_true",
-                  help="copy files only and do not install bootloader")
+                  help="copy files only but do not install bootloader")
 parser.add_option("--dry-run", dest="dryrun", action="store_true",
 parser.add_option("--dry-run", dest="dryrun", action="store_true",
-                  help="do not actually execute any commands")
+                  help="avoid executing commands")
 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",
 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 syslinux")
+                  help="install grub bootloader instead of syslinux [TODO]")
 parser.add_option("--initrd", dest="initrd", action="store", type="string",
 parser.add_option("--initrd", dest="initrd", action="store", type="string",
-                  help="install specified initrd instead of the default")
+                  help="install specified initrd instead of the default [TODO]")
 parser.add_option("--kernel", dest="kernel", action="store", type="string",
 parser.add_option("--kernel", dest="kernel", action="store", type="string",
-                  help="install specified kernel instead of the default")
+                  help="install specified kernel instead of the default [TODO]")
+parser.add_option("--lilo", dest="lilo",  action="store", type="string",
+                  help="lilo executable to be used for installing MBR")
 parser.add_option("--mbr", dest="mbr", action="store_true",
                   help="install master boot record (MBR) on the device")
 parser.add_option("--mbr", dest="mbr", action="store_true",
                   help="install master boot record (MBR) on the device")
+parser.add_option("--quiet", dest="quiet", action="store_true",
+                  help="do not output anything but just errors on console")
 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
-                  help="install specified squashfs file instead of the default")
+                  help="install specified squashfs file instead of the default [TODO]")
 parser.add_option("--uninstall", dest="uninstall", action="store_true",
 parser.add_option("--uninstall", dest="uninstall", action="store_true",
-                  help="remove grml ISO files")
+                  help="remove grml ISO files from specified device [TODO]")
 parser.add_option("--verbose", dest="verbose", action="store_true",
                   help="enable verbose mode")
 parser.add_option("-v", "--version", dest="version", action="store_true",
 parser.add_option("--verbose", dest="verbose", action="store_true",
                   help="enable verbose mode")
 parser.add_option("-v", "--version", dest="version", action="store_true",
@@ -76,13 +88,36 @@ parser.add_option("-v", "--version", dest="version", action="store_true",
 (options, args) = parser.parse_args()
 
 
 (options, args) = parser.parse_args()
 
 
+def cleanup():
+    """Cleanup function to make sure there aren't any mounted devices left behind.
+    """
+
+    logging.info("Cleaning up before exiting...")
+    proc = subprocess.Popen(["sync"])
+    proc.wait()
+
+    try:
+        for device in mounted:
+            unmount(device, "")
+    # ignore: RuntimeError: Set changed size during iteration
+    except:
+        pass
+
+
+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."""
 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"]))
+    # usage: execute(subprocess.Popen, (["ls", "-la"]))
+    # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
     if options.dryrun:
     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)
 
@@ -115,7 +150,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)):
@@ -133,25 +168,46 @@ 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, dry_run=False):
-    # TODO
-    """Install syslinux on specified device."""
-    print("debug: syslinux %s [TODO]") % device
+def mkfs_fat16(device):
+    """Format specified device with VFAT/FAT16 filesystem.
+
+    @device: partition that should be formated"""
+
+    # syslinux -d boot/isolinux /dev/sdb1
+    logging.info("Formating partition with fat16 filesystem")
+    logging.debug("mkfs.vfat -F 16 %s" % device)
+    proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
+    proc.wait()
+    if proc.returncode != 0:
+        raise Exception, "error executing mkfs.vfat"
+
+
+def install_syslinux(device):
+    """Install syslinux on specified device.
+
+    @device: partition where syslinux should be installed to"""
+
+    if options.dryrun:
+        logging.info("Would install syslinux as bootloader on %s", device)
+        return 0
 
 
-    # syslinux -d boot/isolinux /dev/usb-sdb1
+    # syslinux -d boot/isolinux /dev/sdb1
+    logging.info("Installing syslinux as bootloader")
+    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):
 
 
 def generate_grub_config(grml_flavour):
-    """Generate grub configuration for use via menu,lst"""
+    """Generate grub configuration for use via menu.lst
 
 
+    @grml_flavour: name of grml flavour the configuration should be generated for"""
     # TODO
     # TODO
-    # * install main part of configuration just *once* and append
-    #   flavour specific configuration only
     # * what about systems using grub2 without having grub1 available?
     # * support grub2?
 
     # * what about systems using grub2 without having grub1 available?
     # * support grub2?
 
-    grml_name = grml_flavour
-
     return("""\
 # misc options:
 timeout 30
     return("""\
 # misc options:
 timeout 30
@@ -161,88 +217,171 @@ 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())
 
 
 def generate_isolinux_splash(grml_flavour):
 
 # TODO: extend configuration :)
 """ % locals())
 
 
 def generate_isolinux_splash(grml_flavour):
-    """Generate bootsplash for isolinux/syslinux"""
+    """Generate bootsplash for isolinux/syslinux
 
 
-    # TODO
-    # * adjust last bootsplash line
+    @grml_flavour: name of grml flavour the configuration should be generated for"""
+
+    # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
 
     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"""
 
 
-    # TODO
-    # * install main part of configuration just *once* and append
-    #   flavour specific configuration only
-    # * unify isolinux and syslinux setup ("INCLUDE /boot/...")
-    #   as far as possible
+def generate_main_syslinux_config(grml_flavour, bootoptions):
+    """Generate main configuration for use in syslinux.cfg
 
 
-    grml_name = grml_flavour
+    @grml_flavour: name of grml flavour the configuration should be generated for
+    @bootoptions: bootoptions that should be used as a default"""
+
+    local_datestamp = datestamp
 
     return("""\
 
     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
 # 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
+
+## 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 quiet module=%(grml_flavour)s %(bootoptions)s
+
+# memtest
+LABEL  memtest
+KERNEL /boot/addons/memtest
+APPEND BOOT_IMAGE=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
+
+## end of global configuration
+""" % locals())
 
 
-# TODO: extend configuration :)
+
+def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
+    """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"""
+
+    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 quiet module=%(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 quiet module=%(grml_flavour)s toram=%(grml_flavour)s %(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 quiet module=%(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 quiet module=%(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 module=%(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 module=%(grml_flavour)s vga=normal lang=us boot=live noautoconfig atapicd noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia maxcpus=1 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm %(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 quiet module=%(grml_flavour)s nofstab noraid nolvm noautoconfig noswap raid=noautodetect %(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 module=%(grml_flavour)s vga=normal video=vesafb:off  console=tty1 console=ttyS0,9600n8 %(bootoptions)s
 """ % locals())
 
 
 """ % locals())
 
 
-def install_grub(device, dry_run=False):
-    """Install grub on specified device."""
-    print("grub-install %s") % device
+def install_grub(device):
+    """Install grub on specified device.
 
 
+    @device: partition where grub should be installed to"""
 
 
-def install_bootloader(partition, dry_run=False):
-    """Install bootloader on device."""
-    # 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)
+    if options.dryrun:
+        logging.info("Would execute grub-install %s now.", device)
     else:
     else:
-        device = partition
+        logging.critical("TODO: sorry - grub-install %s not implemented yet"  % device)
+
+
+def install_bootloader(device):
+    """Install bootloader on specified device.
+
+    @device: partition where bootloader should be installed to"""
+
+    # 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
 
     if options.grub:
 
     if options.grub:
-        install_grub(device, dry_run)
+        install_grub(device)
     else:
     else:
-        install_syslinux(device, dry_run)
+        install_syslinux(device)
 
 
 def is_writeable(device):
 
 
 def is_writeable(device):
-    """Check if the device is writeable for the current user"""
+    """Check if the device is writeable for the current user
+
+    @device: partition where bootloader should be installed to"""
 
     if not device:
         return False
 
     if not device:
         return False
@@ -253,7 +392,8 @@ 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, dry_run=False):
+
+def install_mbr(device):
     """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"""
@@ -261,36 +401,77 @@ def install_mbr(device, dry_run=False):
     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
+    if options.lilo:
+        lilo = options.lilo
+    else:
+        lilo = '/usr/share/grml2usb/lilo/lilo.static'
 
     if not is_exe(lilo):
 
     if not is_exe(lilo):
-        raise Exception, "lilo executable not available."
+        raise Exception, "lilo executable can not be execute"
+
+    if options.dryrun:
+        logging.info("Would install MBR running lilo and using syslinux.")
+        return 0
 
     # to support -A for extended partitions:
 
     # 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
-    if not dry_run:
-        proc = subprocess.Popen(["./lilo/lilo.static", "-S", "/dev/null", "-A", device, "1"])
+    logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
+    if not options.dryrun:
+        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:
         proc.wait()
         if proc.returncode != 0:
             raise Exception, "error executing lilo"
 
     # lilo's mbr is broken, use the one from syslinux instead:
-    print("debug: cat /usr/lib/syslinux/mbr.bin > %s") % device
-    if not dry_run:
+    if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
+        raise Exception, "/usr/lib/syslinux/mbr.bin can not be read"
+
+    logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
+    if not options.dryrun:
         try:
         try:
-            # TODO use Popen instead?
+            # TODO -> use Popen instead?
             retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
             if retcode < 0:
             retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
             if retcode < 0:
-                print >> sys.stderr, "Error copying MBR to device", -retcode
+                logging.critical("Error copying MBR to device (%s)" % retcode)
         except OSError, error:
         except OSError, error:
-            print >> sys.stderr, "Execution failed:", error
+            logging.critical("Execution failed:", error)
+
+
+def register_tmpfile(path):
+    """TODO
+    """
+
+    tmpfiles.add(path)
+
+
+def unregister_tmpfile(path):
+    """TODO
+    """
+
+    if path in tmpfiles:
+        tmpfiles.remove(path)
+
+
+def register_mountpoint(target):
+    """TODO
+    """
+
+    mounted.add(target)
+
+
+def unregister_mountpoint(target):
+    """TODO
+    """
+
+    if target in mounted:
+        mounted.remove(target)
 
 
 def mount(source, target, options):
 
 
 def mount(source, target, options):
@@ -300,40 +481,78 @@ def mount(source, target, options):
     @target: directory where the ISO should be mounted to
     @options: mount specific options"""
 
     @target: directory where the ISO should be mounted to
     @options: mount specific options"""
 
-    print("debug: mount %s %s %s [TODO]") % (options, source, target)
+    # notice: options.dryrun does not work here, as we have to
+    # locate files and identify the grml 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"
+    else:
+        logging.debug("register_mountpoint(%s)" % target)
+        register_mountpoint(target)
+
+
+def unmount(target, options):
+    """Unmount specified target
+
+    @target: target where something is mounted on and which should be unmounted
+    @options: options for umount command"""
+
+    # make sure we unmount only already mounted targets
+    target_unmount = False
+    mounts = open('/proc/mounts').readlines()
+    mountstring = re.compile(".*%s.*" % re.escape(target))
+    for line in mounts:
+        if re.match(mountstring, line):
+            target_unmount = True
+
+    if not target_unmount:
+        logging.debug("%s not mounted anymore" % target)
+    else:
+        logging.debug("umount %s %s" % (list(options), target))
+        proc = subprocess.Popen(["umount"] + list(options) + [target])
+        proc.wait()
+        if proc.returncode != 0:
+            raise Exception, "Error executing umount"
+        else:
+            logging.debug("unregister_mountpoint(%s)" % target)
+            unregister_mountpoint(target)
 
 
 
 
-def unmount(directory):
-    """Unmount specified directory
+def check_for_usbdevice(device):
+    """Check whether the specified device is a removable USB device
 
 
-    @directory: directory where something is mounted on and which should be unmounted"""
+    @device: device name, like /dev/sda1 or /dev/sda
+    """
 
 
-    print("debug: umount %s [TODO]") % directory
+    usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
+    usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
+    if os.path.isfile(usbdevice):
+        is_usb = open(usbdevice).readline()
+        if is_usb == "1":
+            return 0
+        else:
+            return 1
 
 
 
 
-def check_for_vat(partition):
+def check_for_fat(partition):
     """Check whether specified partition is a valid VFAT/FAT16 filesystem
 
     @partition: device name of partition"""
 
     try:
     """Check whether specified partition is a valid VFAT/FAT16 filesystem
 
     @partition: device name of partition"""
 
     try:
-        udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t",
-            partition],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         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
-            return 1
+            raise Exception, "Failed to read device %s - wrong UID / permissions?" % partition
 
         if filesystem != "vfat":
 
         if filesystem != "vfat":
-            return(1)
-
-        # TODO
-        # * check for ID_FS_VERSION=FAT16 as well?
+            raise Exception, "Device %s does not contain a FAT16 partition." % partition
 
     except OSError:
 
     except OSError:
-        print("Sorry, /lib/udev/vol_id not available.")
-        return 1
+        raise Exception, "Sorry, /lib/udev/vol_id not available."
 
 
 def mkdir(directory):
 
 
 def mkdir(directory):
@@ -347,88 +566,140 @@ def mkdir(directory):
             pass
 
 
             pass
 
 
-def copy_grml_files(grml_flavour, iso_mount, target, dry_run=False):
+def copy_grml_files(grml_flavour, iso_mount, target):
     """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 and get rid of spaghetti ;)
+    # * abstract copy logic to make the code shorter and get rid of spaghettis ;)
 
 
-    print("Copying files. This might take a while....")
+    if options.dryrun:
+        logging.info("Would copy files to %s", iso_mount)
+    elif not options.bootloaderonly:
+        logging.info("Copying files. This might take a while....")
 
 
-    squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
-    squashfs_target = target + '/live/'
-    execute(mkdir, squashfs_target)
+        squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
+        squashfs_target = target + '/live/'
+        execute(mkdir, squashfs_target)
 
 
-    # use install(1) for now to make sure we can write the files afterwards as normal user as well
-    print("cp %s %s") % (squashfs, target + '/live/' + grml_flavour + '.squashfs')
-    execute(subprocess.Popen, ["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
+        # use install(1) for now to make sure we can write the files afterwards as normal user as well
+        logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
+        proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
+        proc.wait()
 
 
-    filesystem_module = search_file('filesystem.module', iso_mount)
-    print("cp %s %s") % (filesystem_module, squashfs_target + grml_flavour + '.module')
-    execute(subprocess.Popen, ["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
+        filesystem_module = search_file('filesystem.module', iso_mount)
+        logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
+        proc = subprocess.Popen(["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
+        proc.wait()
 
 
-    release_target = target + '/boot/release/' + grml_flavour
-    execute(mkdir, release_target)
+        release_target = target + '/boot/release/' + grml_flavour
+        execute(mkdir, release_target)
 
 
-    kernel = search_file('linux26', iso_mount)
-    print("cp linux26 %s") % release_target + '/linux26'
-    execute(subprocess.Popen, ["install", "--mode=664", kernel, release_target + '/linux26'])
+        kernel = search_file('linux26', iso_mount)
+        logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
+        proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
+        proc.wait()
 
 
-    initrd = search_file('initrd.gz', iso_mount)
-    print("debug: copy initrd to %s") % release_target + '/initrd.gz'
-    execute(subprocess.Popen, ["install", "--mode=664", initrd, release_target + '/initrd.gz'])
+        initrd = search_file('initrd.gz', iso_mount)
+        logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
+        proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
+        proc.wait()
 
     if not options.copyonly:
 
     if not options.copyonly:
-        isolinux_target = target + '/boot/isolinux/'
-        execute(mkdir, isolinux_target)
+        syslinux_target = target + '/boot/syslinux/'
+        execute(mkdir, syslinux_target)
 
         logo = search_file('logo.16', iso_mount)
 
         logo = search_file('logo.16', iso_mount)
-        print("debug: copy logo.16 to %s") % isolinux_target + 'logo.16'
-        execute(subprocess.Popen, ["install", "--mode=664", logo, isolinux_target + 'logo.16'])
+        logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
+        proc = 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)
 
         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)
-            execute(subprocess.Popen, ["install", "--mode=664", bootsplash, isolinux_target + ffile])
+            logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
+            proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile])
+            proc.wait()
 
         grub_target = target + '/boot/grub/'
         execute(mkdir, grub_target)
 
 
         grub_target = target + '/boot/grub/'
         execute(mkdir, grub_target)
 
-        print("debug: copy grub/splash.xpm.gz to %s") % grub_target + 'splash.xpm.gz'
-        execute(subprocess.Popen, ["install", "--mode=664", 'grub/splash.xpm.gz', grub_target + 'splash.xpm.gz'])
-
-        print("debug: copy grub/stage2_eltorito to %s") % grub_target + 'stage2_eltorito'
-        execute(subprocess.Popen, ["install", "--mode=664", 'grub/stage2_eltorito', grub_target + 'stage2_eltorito'])
+        if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
+            logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
+            raise
+        else:
+            logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
+            proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz'])
+            proc.wait()
 
 
-        print("debug: generating grub configuration %s") % grub_target + 'menu.lst'
-        if not dry_run:
+        if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"):
+            logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.")
+            raise
+        else:
+            logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito')
+            proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito'])
+            proc.wait()
+
+        if not options.dryrun:
+            logging.debug("Generating grub configuration")
+            #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 = open(grub_target + 'menu.lst', 'w')
             grub_config_file.write(generate_grub_config(grml_flavour))
-            grub_config_file.close( )
-
-        syslinux_target = target + '/boot/isolinux/'
-        execute(mkdir, syslinux_target)
-
-        print("debug: generating syslinux configuration %s") % syslinux_target + 'syslinux.cfg'
-        if not dry_run:
-            syslinux_config_file = open(syslinux_target + 'syslinux.cfg', 'w')
-            syslinux_config_file.write(generate_syslinux_config(grml_flavour))
-            syslinux_config_file.close( )
-
-        print("debug: generating isolinux/syslinux splash %s") % syslinux_target + 'boot.msg'
-        if not dry_run:
+            grub_config_file.close()
+
+            logging.info("Generating syslinux configuration")
+            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()
+                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')
+                    logging.info("Notice: grml flavour %s is being installed as the default booting system." % grml_flavour)
+                    syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
+                    syslinux_config_file.close()
+            else:
+                    syslinux_config_file = open(syslinux_cfg, 'w')
+                    syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
+                    syslinux_config_file.close()
+
+            # install flavour specific configuration only *once* as well
+            # kind of ugly - I'm pretty sure this could be smoother...
+            flavour_config = True
+            if os.path.isfile(syslinux_cfg):
+                string = open(syslinux_cfg).readlines()
+                logging.info("Notice: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
+                flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(datestamp))))
+                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, options.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( )
 
 
             isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
             isolinux_splash.write(generate_isolinux_splash(grml_flavour))
             isolinux_splash.close( )
 
 
+    # make sure we sync filesystems before returning
+    proc = subprocess.Popen(["sync"])
+    proc.wait()
+
+
 def uninstall_files(device):
 def uninstall_files(device):
-    """Get rid of all grml files on specified device"""
+    """Get rid of all grml files on specified device
+
+    @device: partition where grml2usb files should be removed from"""
 
     # TODO
 
     # TODO
-    print("TODO: %s") % device
+    logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
 
 
 def identify_grml_flavour(mountpath):
 
 
 def identify_grml_flavour(mountpath):
@@ -440,7 +711,8 @@ def identify_grml_flavour(mountpath):
     version_file = search_file('grml-version', mountpath)
 
     if version_file == "":
     version_file = search_file('grml-version', mountpath)
 
     if version_file == "":
-        print("gucku")
+        logging.critical("Error: could not find grml-version file.")
+        raise
 
     try:
         tmpfile = open(version_file, 'r')
 
     try:
         tmpfile = open(version_file, 'r')
@@ -449,101 +721,197 @@ def identify_grml_flavour(mountpath):
     except TypeError:
         raise
     except:
     except TypeError:
         raise
     except:
-        print "Unexpected error:", sys.exc_info()[0]
+        logging.critical("Unexpected error:", sys.exc_info()[0])
         raise
 
     return grml_flavour
 
 
         raise
 
     return grml_flavour
 
 
+def handle_iso(iso, device):
+    """Main logic for mounting ISOs and copying files.
+
+    @iso: full path to the ISO that should be installed to the specified device
+    @device: partition where the specified ISO should be installed to"""
+
+    logging.info("Using ISO %s" % iso)
+
+    if os.path.isdir(iso):
+        logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
+        sys.exit(1)
+    else:
+        iso_mountpoint = tempfile.mkdtemp()
+        register_tmpfile(iso_mountpoint)
+        remove_iso_mountpoint = True
+
+        if not os.path.isfile(iso):
+            logging.critical("Fatal: specified ISO %s could not be read" % iso)
+            cleanup()
+            sys.exit(1)
+
+        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
+
+        else:
+            device_mountpoint = tempfile.mkdtemp()
+            register_tmpfile(device_mountpoint)
+            remove_device_mountpoint = True
+            try:
+                mount(device, device_mountpoint, "")
+            except Exception, error:
+                logging.critical("Fatal: %s" % error)
+                cleanup()
+
+        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)
+        except TypeError:
+            logging.critical("Fatal: a critical error happend during execution, giving up")
+            sys.exit(1)
+        finally:
+            if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
+                unmount(iso_mountpoint, "")
+
+                os.rmdir(iso_mountpoint)
+                unregister_tmpfile(iso_mountpoint)
+
+            if remove_device_mountpoint:
+                unmount(device_mountpoint, "")
+
+                if os.path.isdir(device_mountpoint):
+                    os.rmdir(device_mountpoint)
+                    unregister_tmpfile(device_mountpoint)
+
+        # 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")
 
+    # log handling
+    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:
     if options.dryrun:
-        print("Running in simulate mode as requested via option dry-run.")
+        logging.info("Running in simulate mode as requested via option dry-run.")
     else:
         check_uid_root()
 
     else:
         check_uid_root()
 
+    # specified arguments
     device = args[len(args) - 1]
     isos = args[0:len(args) - 1]
 
     device = args[len(args) - 1]
     isos = args[0:len(args) - 1]
 
-    if not which("syslinux"):
-        print >> sys.stderr, 'Sorry, syslinux not available. Exiting.'
-        print >> sys.stderr, 'Please install syslinux or consider using the --grub option.'
-        sys.exit(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") and not options.force:
+        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)
 
 
-    # TODO
-    # * check for valid blockdevice, vfat and mount functions
-    # if device is not None:
-        # check_for_vat(device)
-        # mount_target(partition)
+    # make sure we have syslinux available
+    if options.mbr:
+        if not which("syslinux") and not options.copyonly and not options.dryrun:
+            logging.critical('Sorry, syslinux not available. Exiting.')
+            logging.critical('Please install syslinux or consider using the --grub option.')
+            sys.exit(1)
 
 
-    # TODO
-    # * it doesn't need to be a ISO, could be /live/image as well
-    for iso in isos:
-        print("debug: iso = %s") % iso
+    # make sure we have mkfs.vfat available
+    if options.fat16 and not options.force:
+        if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
+            logging.critical('Sorry, mkfs.vfat not available. Exiting.')
+            logging.critical('Please make sure to install dosfstools.')
+            sys.exit(1)
 
 
-        if os.path.isdir(iso):
-            print("TODO: /live/image handling not yet implemented") # TODO
+        # make sure the user is aware of what he is doing
+        f = raw_input("Are you sure you want to format the device with a fat16 filesystem? y/N ")
+        if f == "y" or f == "Y":
+            logging.info("Note: you can skip this question using the option --force")
+            mkfs_fat16(device)
         else:
         else:
-            iso_mountpoint = '/mnt/test'     # FIXME
-            # iso_mount = tempfile.mkdtemp()
-            mount(iso, iso_mountpoint, "-o loop -t iso9660")
-            # device_mountpoint = '/mnt/usb-sdb1'
-            # device_mountpoint = tempfile.mkdtemp()
-            device_mountpoint = '/dev/shm/grml2usb' # FIXME
-            mount(device, device_mountpoint, "")
-
-            try:
-                grml_flavour = identify_grml_flavour(iso_mountpoint)
-                print("Identified grml flavour \"%s\".") % grml_flavour
-            except TypeError:
-                print("Fatal: could not identify grml flavour, sorry.")
-                sys.exit(1)
-
-            # grml_flavour_short = grml_flavour.replace('-','')
-            #p rint("debug: grml_flavour_short = %s") % grml_flavour_short
+            sys.exit(1)
 
 
-            copy_grml_files(grml_flavour, iso_mountpoint, device_mountpoint, dry_run=options.dryrun)
+    # check for vfat filesystem
+    if device is not None and not os.path.isdir(device):
+        try:
+            check_for_fat(device)
+        except Exception, error:
+            logging.critical("Execution failed: %s", error)
+            sys.exit(1)
 
 
-            unmount(device_mountpoint)     # TODO
-            unmount(iso_mountpoint)  # TODO
+    if not check_for_usbdevice(device):
+        print "Warning: the specified device %s does not look like a removable usb device." % device
+        f = raw_input("Do you really want to continue? y/N ")
+        if f == "y" or f == "Y":
+           pass
+        else:
+            sys.exit(1)
 
 
-            #if os.path.isdir(target):
-            #    os.rmdir(target)
-            #if os.path.isdir(iso_mount):
-            #    os.rmdir(iso_mount)
+    # format partition:
+    if options.fat16:
+        mkfs_fat16(device)
 
 
+    # main operation (like installing files)
+    for iso in isos:
+        handle_iso(iso, device)
 
 
-    if options.mbr:
+    # install MBR
+    if not options.mbr or skip_mbr:
+        logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
+    else:
         # 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, dry_run=options.dryrun)
+            install_mbr(mbr_device)
         except IOError, error:
         except IOError, error:
-            print >> sys.stderr, "Execution failed:", error
+            logging.critical("Execution failed: %s", error)
             sys.exit(1)
         except Exception, error:
             sys.exit(1)
         except Exception, error:
-            print >> sys.stderr, "Execution failed:", error
+            logging.critical("Execution failed: %s", error)
             sys.exit(1)
 
             sys.exit(1)
 
+    # Install bootloader only if not using the --copy-only option
     if options.copyonly:
     if options.copyonly:
-        print("Not installing bootloader and its files as requested via option copyonly.")
+        logging.info("Not installing bootloader and its files as requested via option copyonly.")
     else:
     else:
-        install_bootloader(device, dry_run=options.dryrun)
+        install_bootloader(device)
+
+    # finally be politely :)
+    logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
 
 
-    print("Finished execution of grml2usb (%s). Have fun with your grml system.") % PROG_VERSION
 
 if __name__ == "__main__":
 
 if __name__ == "__main__":
-    main()
+    try:
+        main()
+    except KeyboardInterrupt:
+        logging.info("Received KeyboardInterrupt")
+        cleanup()
 
 ## 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