Provide top-level critical error message.
[grml2usb.git] / grml2usb
index 77e72ad..0c9ffc0 100755 (executable)
--- a/grml2usb
+++ b/grml2usb
@@ -41,6 +41,9 @@ GRML_DEFAULT = None
 UUID = None
 SYSLINUX_LIBS = "/usr/lib/syslinux/"
 
+RE_PARTITION = re.compile(r'([a-z/]*?)(\d+)$')
+RE_P_PARTITION = re.compile(r'(.*?\d+)p(\d+)$')
+RE_LOOP_DEVICE = re.compile(r'/dev/loop\d+$')
 
 def syslinux_warning(option, opt, value, opt_parser):
     """A helper function for printing a warning about deprecated option
@@ -60,10 +63,10 @@ def grub_option(option, opt, value, opt_parser):
     setattr(opt_parser.values, 'syslinux', False)
 
 # cmdline parsing
-USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/sdX#>\n\
+USAGE = "Usage: %prog [options] <[ISO[s] | /lib/live/mount/medium]> </dev/sdX#>\n\
 \n\
-%prog installs grml ISO[s] to an USB device to be able to boot from it.\n\
-Make sure you have at least one grml ISO or a running grml system (/live/image),\n\
+%prog installs Grml ISO[s] to an USB device to be able to boot from it.\n\
+Make sure you have at least one Grml ISO or a running Grml system (/lib/live/mount/medium),\n\
 grub or syslinux and root access.\n\
 \n\
 Run %prog --help for usage hints, further information via: man grml2usb"
@@ -128,6 +131,12 @@ class CriticalException(Exception):
     @Exception: message"""
     pass
 
+class VerifyException(Exception):
+    """Throw critical exception if there is an fatal error when verifying something.
+
+    @Exception: message"""
+    pass
+
 
 # The following two functions help to operate on strings as
 # array (list) of bytes (octets). In Python 3000, the bytes
@@ -305,6 +314,20 @@ def check_uid_root():
         sys.exit("Error: please run this script with uid 0 (root).")
 
 
+def check_boot_flag(device):
+    boot_dev, x = get_device_from_partition(device)
+
+    with open(boot_dev, 'r') as image:
+        data = image.read(512)
+        bootcode = data[440:]
+        if bootcode[6] == '\x80':
+            logging.debug("bootflag is enabled")
+        else:
+            logging.debug("bootflag is NOT enabled")
+            raise VerifyException("Device %s does not have the bootflag set. "
+                "Please enable it to be able to boot." % boot_dev)
+
+
 def mkfs_fat16(device):
     """Format specified device with VFAT/FAT16 filesystem.
 
@@ -414,10 +437,7 @@ def install_grub(device):
                 # If using --grub-mbr then make sure we install grub in MBR instead of PBR
                 if options.grubmbr:
                     logging.debug("Using option --grub-mbr ...")
-                    if device[-1:].isdigit():
-                        grub_device = re.match(r'(.*?)\d*$', device).group(1)
-                    else:
-                        grub_device = device
+                    grub_device, x = get_device_from_partition(device)
                 else:
                     grub_device = device
 
@@ -518,11 +538,12 @@ def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
     logging.info("Installing default MBR")
 
     if not os.path.isfile(mbrtemplate):
-        logging.critical("Error: %s can not be read.", mbrtemplate)
-        raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file \"%s\"?)" % mbrtemplate)
+        logging.error('Error installing MBR (either try --syslinux-mbr or '
+            'install missing file "%s"?)', mbrtemplate)
+        raise CriticalException("Error: %s can not be read.", mbrtemplate)
 
     if partition is not None and ((partition < 0) or (partition > 3)):
-        logging.warn("Cannot activate partition %d" % partition)
+        logging.warn("Cannot activate partition %d", partition)
         partition = None
 
     if ismirbsdmbr:
@@ -774,9 +795,9 @@ def copy_system_files(grml_flavour, iso_mount, target):
 
     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
     if squashfs is None:
-        logging.critical("Fatal: squashfs file not found"
-        ", please check that your iso is not corrupt")
-        raise CriticalException("error locating squashfs file")
+        logging.error("error locating squashfs file")
+        raise CriticalException("Fatal: squashfs file not found"
+            ", please check that your iso is not corrupt")
     else:
         squashfs_target = target + '/live/' + grml_flavour + '/'
         execute(mkdir, squashfs_target)
@@ -787,8 +808,8 @@ def copy_system_files(grml_flavour, iso_mount, target):
         if filesystem_module:
             break
     if filesystem_module is None:
-        logging.critical("Fatal: filesystem.module not found")
-        raise CriticalException("error locating filesystem.module file")
+        logging.error("error locating filesystem.module file")
+        raise CriticalException("filesystem.module not found")
     else:
         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
 
@@ -802,8 +823,8 @@ 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")
+            logging.error("error locating kernel file")
+            raise CriticalException("Kernel not found")
 
         source = os.path.dirname(kernel) + '/'
         dest = target + '/' + os.path.dirname(kernel).replace(iso_mount, '') + '/'
@@ -1026,10 +1047,10 @@ def copy_bootloader_files(iso_mount, target, grml_flavour):
     (source_dir, defaults_file) = get_defaults_file(iso_mount, grml_flavour, "grml.cfg")
 
     if not source_dir:
-        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
+        raise CriticalException(
+            "Fatal: file default.cfg could not be found." \
+            "Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ..." \
+            "       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
 
     if not os.path.exists(iso_mount + '/boot/grub/footer.cfg'):
         logging.warning("Warning: Grml releases older than 2011.12 support only one flavour in grub.")
@@ -1093,6 +1114,19 @@ def install_iso_files(grml_flavour, iso_mount, device, target):
     proc.wait()
 
 
+def get_device_from_partition(partition):
+    device = partition
+    partition_number = None
+    if partition[-1].isdigit() and not RE_LOOP_DEVICE.match(partition):
+        m = RE_P_PARTITION.match(partition)
+        if not m:
+            m = RE_PARTITION.match(partition)
+        if m:
+            device = m.group(1)
+            partition_number = int(m.group(2)) - 1
+    return (device, partition_number)
+
+
 def get_flavour(flavour_str):
     """Returns the flavour of a grml version string
     """
@@ -1108,8 +1142,16 @@ def identify_grml_flavour(mountpath):
     version_files = search_file('grml-version', mountpath, lst_return=True)
 
     if not version_files:
-        logging.critical("Error: could not find grml-version file.")
-        raise
+        if mountpath.startswith("/lib/live/mount/medium"):
+            logging.critical("Error: could not find grml-version file.")
+            logging.critical("Looks like your system is running from RAM but required files are not available.")
+            logging.critical("Please either boot without toram=... or use boot option toram instead of toram=...")
+            cleanup()
+            sys.exit(1)
+        else:
+            logging.critical("Error: could not find grml-version file.")
+            cleanup()
+            sys.exit(1)
 
     flavours = []
     logging.debug("version_files = %s", version_files)
@@ -1122,7 +1164,6 @@ def identify_grml_flavour(mountpath):
         except TypeError, e:
             raise
         except Exception, e:
-            logging.critical("Unexpected error: %s", e)
             raise
         finally:
             if tmpfile:
@@ -1431,7 +1472,7 @@ def install(image, device):
         logging.info("Using %s as install base", image)
     else:
         logging.info("Using ISO %s", image)
-        iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb", dir=options.tmpdir)
+        iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb", dir=os.path.abspath(options.tmpdir))
         register_tmpfile(iso_mountpoint)
         remove_image_mountpoint = True
         try:
@@ -1454,12 +1495,12 @@ def install(image, device):
 def install_grml(mountpoint, device):
     """Main logic for copying files of the currently running grml system.
 
-    @mountpoin: directory where currently running live system resides (usually /live/image)
+    @mountpoint: directory where currently running live system resides (usually /lib/live/mount/medium)
     @device: partition where the specified ISO should be installed to"""
 
     device_mountpoint = device
     if os.path.isdir(device):
-        logging.info("Specified device is not a directory, therefore not mounting.")
+        logging.info("Specified device is a directory, therefore not mounting.")
         remove_device_mountpoint = False
     else:
         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
@@ -1467,13 +1508,13 @@ def install_grml(mountpoint, device):
         remove_device_mountpoint = True
         try:
             check_for_fat(device)
+            check_boot_flag(device)
             mount(device, device_mountpoint, ['-o', 'utf8,iocharset=iso8859-1'])
+        except VerifyException, error:
+            logging.critical("Fatal: %s", error)
+            raise
         except CriticalException, error:
-            try:
-                mount(device, device_mountpoint, "")
-            except CriticalException, error:
-                logging.critical("Fatal: %s", error)
-                raise
+            mount(device, device_mountpoint, "")
     try:
         grml_flavours = identify_grml_flavour(mountpoint)
         for flavour in set(grml_flavours):
@@ -1488,7 +1529,7 @@ def install_grml(mountpoint, device):
 
 
 def remove_mountpoint(mountpoint):
-    """remove a registred mountpoint
+    """remove a registered mountpoint
     """
 
     try:
@@ -1510,12 +1551,9 @@ def handle_mbr(device):
         logging.info("Would install MBR")
         return 0
 
-    if device[-1:].isdigit():
-        mbr_device = re.match(r'(.*?)\d*$', device).group(1)
-        partition_number = int(device[-1:]) - 1
-    else:
+    mbr_device, partition_number = get_device_from_partition(device)
+    if partition_number is None:
         logging.warn("Could not detect partition number, not activating partition")
-        partition_number = None
 
     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
@@ -1663,6 +1701,11 @@ def check_programs():
 
 def load_loop():
     """Runs modprobe loop and throws away it's output"""
+    if not which("modprobe"):
+        logging.critical("Fatal: modprobe not available, can not continue - sorry.")
+        logging.critical("Hint: is /sbin missing in PATH?")
+        sys.exit(1)
+
     proc = subprocess.Popen(["modprobe", "loop"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     proc.wait()
 
@@ -1670,67 +1713,72 @@ def load_loop():
 def main():
     """Main function [make pylint happy :)]"""
 
-    if options.version:
-        print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
-        sys.exit(0)
+    try:
+        if options.version:
+            print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
+            sys.exit(0)
 
-    if len(args) < 2:
-        parser.error("invalid usage")
+        if len(args) < 2:
+            parser.error("invalid usage")
 
-    # log handling
-    handle_logging()
+        # log handling
+        handle_logging()
 
-    # make sure we have the appropriate permissions
-    check_uid_root()
+        # make sure we have the appropriate permissions
+        check_uid_root()
 
-    check_options(options)
+        check_options(options)
 
-    load_loop()
+        load_loop()
 
-    logging.info("Executing grml2usb version %s", PROG_VERSION)
+        logging.info("Executing grml2usb version %s", PROG_VERSION)
 
-    if options.dryrun:
-        logging.info("Running in simulation mode as requested via option dry-run.")
+        if options.dryrun:
+            logging.info("Running in simulation mode as requested via option dry-run.")
 
-    check_programs()
+        check_programs()
 
-    # specified arguments
-    device = os.path.realpath(args[len(args) - 1])
-    isos = args[0:len(args) - 1]
+        # specified arguments
+        device = os.path.realpath(args[len(args) - 1])
+        isos = args[0:len(args) - 1]
 
-    if not os.path.isdir(device):
-        if device[-1:].isdigit():
-            if int(device[-1:]) > 4 or device[-2:].isdigit():
-                logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
-                sys.exit(1)
+        if not os.path.isdir(device):
+            if device[-1:].isdigit():
+                if int(device[-1:]) > 4 or device[-2:].isdigit():
+                    logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
+                    sys.exit(1)
 
-    # provide upgrade path
-    handle_compat_warning(device)
+        # provide upgrade path
+        handle_compat_warning(device)
 
-    # check for vfat partition
-    handle_vfat(device)
+        # check for vfat partition
+        handle_vfat(device)
 
-    # main operation (like installing files)
-    for iso in isos:
-        install(iso, device)
+        # main operation (like installing files)
+        for iso in isos:
+            install(iso, device)
 
-    # install mbr
-    is_superfloppy = not device[-1:].isdigit()
-    if is_superfloppy:
-        logging.info("Detected superfloppy format - not installing MBR")
+        # install mbr
+        is_superfloppy = not device[-1:].isdigit()
+        if is_superfloppy:
+            logging.info("Detected superfloppy format - not installing MBR")
 
-    if not options.skipmbr and not os.path.isdir(device) and not is_superfloppy:
-        handle_mbr(device)
+        if not options.skipmbr and not os.path.isdir(device) and not is_superfloppy:
+            handle_mbr(device)
 
-    handle_bootloader(device)
+        handle_bootloader(device)
 
-    logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
+        logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
 
-    for flavour in GRML_FLAVOURS:
-        logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
+        for flavour in GRML_FLAVOURS:
+            logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
 
-    # finally be politely :)
-    logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
+        # finally be politely :)
+        logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
+
+    except Exception, error:
+        logging.critical("Fatal: %s", str(error))
+        sys.exit(1)
 
 
 if __name__ == "__main__":