grml2usb docs: drop references to deprecated grml-medium
[grml2usb.git] / grml2usb
index 70d5142..5ac6e76 100755 (executable)
--- a/grml2usb
+++ b/grml2usb
@@ -14,7 +14,6 @@ This script installs a Grml system (either a running system or ISO[s]) to a USB
 """
 
 
-import datetime
 import fileinput
 import glob
 import logging
@@ -22,11 +21,9 @@ import os
 import os.path
 import re
 import shutil
-import struct
 import subprocess
 import sys
 import tempfile
-import time
 import uuid
 from inspect import isclass, isroutine
 from optparse import OptionParser
@@ -53,9 +50,6 @@ except Exception:
 # global variables
 MOUNTED = set()  # register mountpoints
 TMPFILES = set()  # register tmpfiles
-DATESTAMP = time.mktime(
-    datetime.datetime.now().timetuple()
-)  # unique identifier for syslinux.cfg
 GRML_FLAVOURS = set()  # which flavours are being installed?
 GRML_DEFAULT = None
 UUID = None
@@ -63,7 +57,6 @@ SYSLINUX_LIBS = [
     "/usr/lib/syslinux/modules/bios/",  # Debian
     "/usr/lib/syslinux/bios/",  # Arch Linux
 ]
-GPT_HEADER = b"\x55\xaa\x45\x46\x49\x20\x50\x41\x52\x54"  # original GPT header
 GRUB_INSTALL = None
 
 RE_PARTITION = re.compile(r"([a-z/]*?)(\d+)$")
@@ -71,9 +64,8 @@ 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
-    """
+def syslinux_warning(option, opt, _value, opt_parser):
+    """A helper function for printing a warning about deprecated option"""
     # pylint: disable-msg=W0613
     sys.stderr.write(
         "Note: the --syslinux option is deprecated as syslinux "
@@ -83,9 +75,8 @@ def syslinux_warning(option, opt, value, opt_parser):
 
 
 # if grub option is set, unset syslinux option
-def grub_option(option, opt, value, opt_parser):
-    """A helper function adjusting other option values
-    """
+def grub_option(option, opt, _value, opt_parser):
+    """A helper function adjusting other option values"""
     # pylint: disable-msg=W0613
     setattr(opt_parser.values, option.dest, True)
     setattr(opt_parser.values, "syslinux", False)
@@ -273,29 +264,11 @@ class VerifyException(Exception):
     @Exception: message"""
 
 
-# The following two functions help to operate on strings as
-# array (list) of bytes (octets). In Python 3000, the bytes
-# datatype will need to be used. This is intended for using
-# with manipulation of files on the octet level, like shell
-# arrays, e.g. in MBR creation.
-
-
-def array2string(*a):
-    """Convert a list of integers [0;255] to a string."""
-    return struct.pack("%sB" % len(a), *a)
-
-
-def string2array(s):
-    """Convert a (bytes) string into a list of integers."""
-    return struct.unpack("%sB" % len(s), s)
-
-
 def cleanup():
-    """Cleanup function to make sure there aren't any mounted devices left behind.
-    """
+    """Cleanup function to make sure there aren't any mounted devices left behind."""
 
-    def del_failed(fn, path, exc):
-        logging.warn("Deletion of %s failed in temporary folder" % path)
+    def del_failed(_fn, path, _exc):
+        logging.warning("Deletion of %s failed in temporary folder", path)
 
     logging.info("Cleaning up before exiting...")
     proc = subprocess.Popen(["sync"])
@@ -427,8 +400,7 @@ def which(program):
 
 
 def get_defaults_file(iso_mount, flavour, name):
-    """get the default file for syslinux
-    """
+    """get the default file for syslinux"""
     bootloader_dirs = ["/boot/isolinux/", "/boot/syslinux/"]
     for directory in bootloader_dirs:
         for name in name, "%s_%s" % (get_flavour_filename(flavour), name):
@@ -465,8 +437,7 @@ def search_file(
             retval.append(os.path.abspath(os.path.join(current_dir, filename)))
             if not lst_return:
                 break
-        # pylint: disable-msg=W0612
-        for current_dir, directories, files in os.walk(path):
+        for current_dir, _directories, _files in os.walk(path):
             if match_file(current_dir):
                 retval.append(os.path.abspath(os.path.join(current_dir, filename)))
                 if not lst_return:
@@ -503,6 +474,12 @@ def get_partition_for_path(path):
 
 
 def check_boot_flag(device):
+    if os.path.isdir(device):
+        logging.debug(
+            "Device %s is a directory, skipping check for boot flag." % device
+        )
+        return
+
     boot_dev, x = get_device_from_partition(device)
 
     logging.info("Checking for boot flag")
@@ -539,35 +516,14 @@ def mkfs_fat16(device):
 
     logging.info("Formating partition with fat16 filesystem")
     logging.debug("mkfs.vfat -F 16 %s", device)
-    proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
+    proc = subprocess.Popen(["mkfs.vfat", "-n", "GRML", "-F", "16", device])
     proc.wait()
     if proc.returncode != 0:
         raise CriticalException("error executing mkfs.vfat")
 
 
-def generate_isolinux_splash(grml_flavour):
-    """Generate bootsplash for isolinux/syslinux
-
-    @grml_flavour: name of grml flavour the configuration should be generated for"""
-
-    grml_name = grml_flavour
-
-    return """\
-\ f17\f\18/boot/syslinux/logo.16
-
-Some information and boot options available via keys F2 - F10. http://grml.org/
-%(grml_name)s
-""" % {
-        "grml_name": grml_name
-    }
-
-
-def generate_main_syslinux_config(*arg):
-    """Generate main configuration for use in syslinux.cfg
-
-    @*arg: just for backward compatibility"""
-    # pylint: disable-msg=W0613
-    # remove warning about unused arg
+def generate_main_syslinux_config():
+    """Generate main configuration for use in syslinux.cfg"""
 
     return """\
 label -
@@ -748,16 +704,14 @@ def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
     "mbrtemplate" file, set the "partition" (0..3) active, and install the
     result back to "device".
 
-    @mbrtemplate: default MBR file
+    @mbrtemplate: default MBR file (must be a valid MBR file of at least 440
+    (or 439 if ismirbsdmbr) bytes)
 
     @device: name of a file assumed to be a hard disc (or USB stick) image, or
     something like "/dev/sdb"
 
     @partition: must be a number between 0 and 3, inclusive
 
-    @mbrtemplate: must be a valid MBR file of at least 440 (or 439 if
-    ismirbsdmbr) bytes.
-
     @ismirbsdmbr: if true then ignore the active flag, set the mirbsdmbr
     specific flag to 0/1/2/3 and set the MBR's default value accordingly. If
     false then leave the mirbsdmbr specific flag set to FFh, set all
@@ -776,7 +730,7 @@ def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
         raise CriticalException("%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.warning("Cannot activate partition %d", partition)
         partition = None
 
     if ismirbsdmbr:
@@ -852,7 +806,9 @@ def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
     set_rw(device)
 
     logging.debug(
-        "executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc", tmpf.name, device
+        "executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc,fsync",
+        tmpf.name,
+        device,
     )
     proc = subprocess.Popen(
         [
@@ -861,7 +817,7 @@ def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
             "of=%s" % device,
             "bs=512",
             "count=1",
-            "conv=notrunc",
+            "conv=notrunc,fsync",
         ],
         stderr=open(os.devnull, "r+"),
     )
@@ -870,28 +826,17 @@ def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
         raise Exception("error executing dd (third run)")
     del tmpf
 
-    # make sure we sync filesystems before returning
-    logging.debug("executing: sync")
-    proc = subprocess.Popen(["sync"])
+    logging.debug("Probing device via 'blockdev --rereadpt %s'", device)
+    proc = subprocess.Popen(["blockdev", "--rereadpt", device])
     proc.wait()
+    if proc.returncode != 0:
+        raise Exception(
+            "Couldn't execute blockdev on '%s' (install util-linux?)", device
+        )
 
     set_rw(device)
 
 
-def is_writeable(device):
-    """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 os.path.exists(device):
-        return False
-
-    return os.access(device, os.W_OK) and os.access(device, os.R_OK)
-
-
 def mount(source, target, mount_options):
     """Mount specified source on given target
 
@@ -906,7 +851,7 @@ def mount(source, target, mount_options):
         if x.startswith(source):
             raise CriticalException(
                 (
-                    "Error executing mount: %s already mounted - "
+                    "Error executing mount: {0} already mounted - "
                     "please unmount before invoking grml2usb"
                 ).format(source)
             )
@@ -954,13 +899,21 @@ def unmount(target, unmount_options):
             unregister_mountpoint(target)
 
 
+def extract_device_name(device):
+    """Extract the device name of a given path
+
+    @device: device name, like /dev/sda1 or /dev/sda
+    """
+    return re.match(r"/dev/(.*?)\d*$", device).group(1)
+
+
 def check_for_usbdevice(device):
     """Check whether the specified device is a removable USB device
 
     @device: device name, like /dev/sda1 or /dev/sda
     """
 
-    usbdevice = re.match(r"/dev/(.*?)\d*$", device).group(1)
+    usbdevice = extract_device_name(device)
     # newer systems:
     usbdev = os.path.realpath("/sys/class/block/" + usbdevice + "/removable")
     if not os.path.isfile(usbdev):
@@ -1126,54 +1079,6 @@ def copy_system_files(grml_flavour, iso_mount, target):
         exec_rsync(source, dest)
 
 
-def update_grml_versions(iso_mount, target):
-    """Update the grml version file on a cd
-    Returns true if version was updated successfully,
-    False if grml-version does not exist yet on the mountpoint
-
-    @iso_mount: string of the iso mount point
-    @target: path of the target mount point
-    """
-    grml_target = target + "/grml/"
-    target_grml_version_file = search_file("grml-version", grml_target)
-    if target_grml_version_file:
-        iso_grml_version_file = search_file("grml-version", iso_mount)
-        if not iso_grml_version_file:
-            logging.warn(
-                "Warning: %s could not be found - can not install it",
-                iso_grml_version_file,
-            )
-            return False
-        try:
-            # read the flavours from the iso image
-            iso_versions = {}
-            iso_file = open(iso_grml_version_file, "r")
-            for line in iso_file:
-                iso_versions[get_flavour(line)] = line.strip()
-
-            # update the existing flavours on the target
-            for line in fileinput.input([target_grml_version_file], inplace=1):
-                flavour = get_flavour(line)
-                if flavour in list(iso_versions.keys()):
-                    print(iso_versions.pop(flavour))
-                else:
-                    print(line.strip())
-            fileinput.close()
-
-            target_file = open(target_grml_version_file, "a")
-            # add the new flavours from the current iso
-            for flavour in iso_versions:
-                target_file.write("%s\n" % iso_versions[flavour])
-        except IOError:
-            logging.warn("Warning: Could not write file")
-        finally:
-            iso_file.close()
-            target_file.close()
-        return True
-    else:
-        return False
-
-
 def copy_grml_files(grml_flavour, iso_mount, target):
     """copy some minor grml files to a given target
 
@@ -1191,7 +1096,9 @@ def copy_grml_files(grml_flavour, iso_mount, target):
             exec_rsync(filename, grml_target)
             break
     else:
-        logging.warn("Warning: could not find flavour directory for %s ", grml_flavour)
+        logging.warning(
+            "Warning: could not find flavour directory for %s ", grml_flavour
+        )
 
 
 def copy_addons(iso_mount, target):
@@ -1302,6 +1209,9 @@ def copy_bootloader_files(iso_mount, target, grml_flavour):
         exec_rsync(efi_img, target + "/boot/efi.img")
         handle_secure_boot(target, efi_img)
 
+    execute(mkdir, target + "/conf/")
+    glob_and_copy(iso_mount + "/conf/bootfile_*", target + "/conf/")
+
     for ffile in ["f%d" % number for number in range(1, 11)]:
         search_and_copy(ffile, iso_mount, syslinux_target + ffile)
 
@@ -1413,8 +1323,7 @@ def get_device_from_partition(partition):
 
 
 def get_flavour(flavour_str):
-    """Returns the flavour of a grml version string
-    """
+    """Returns the flavour of a grml version string"""
     return re.match(r"[\w-]*", flavour_str).group()
 
 
@@ -1481,7 +1390,6 @@ def handle_grub_config(grml_flavour, device, target):
     logging.debug("Updating grub configuration")
 
     grub_target = target + "/boot/grub/"
-    secureboot_target = target + "/EFI/ubuntu/"
 
     bootid_re = re.compile(r"bootid=[\w_-]+")
     live_media_path_re = re.compile(r"live-media-path=[\w_/-]+")
@@ -1496,9 +1404,7 @@ def handle_grub_config(grml_flavour, device, target):
             remove_regexes.append(re.compile(regex))
 
     shortname = get_shortname(grml_flavour)
-    for filename in glob.glob(grub_target + "*.cfg") + glob.glob(
-        secureboot_target + "*.cfg"
-    ):
+    for filename in glob.glob(grub_target + "*.cfg"):
         for line in fileinput.input(filename, inplace=1):
             line = line.rstrip("\r\n")
             if option_re.search(line):
@@ -1708,7 +1614,6 @@ def handle_syslinux_config(grml_flavour, target):
     new_grml_cfg = "%s/%s_grml.cfg" % (syslinux_target, flavour_filename)
 
     if os.path.isfile(defaults_file):
-
         # remove default menu entry in menu
         remove_default_entry(new_default_with_path)
 
@@ -1748,18 +1653,18 @@ def handle_secure_boot(target, efi_img):
         logging.critical("Fatal: %s", error)
         sys.exit(1)
 
-    ubuntu_cfg = search_file("grub.cfg", efi_mountpoint + "/EFI/ubuntu")
-    logging.debug("ubuntu_cfg = %s" % ubuntu_cfg)
-    if not ubuntu_cfg:
+    grub_cfg = search_file("grub.cfg", efi_mountpoint + "/boot/grub/")
+    logging.debug("grub_cfg = %s" % grub_cfg)
+    if not grub_cfg:
         logging.info(
-            "No /EFI/ubuntu/grub.cfg found inside EFI image, looks like Secure Boot support is missing."
+            "No /boot/grub/grub.cfg found inside EFI image, looks like Secure Boot support is missing."
         )
     else:
-        mkdir(target + "/efi/ubuntu")
+        mkdir(target + "/boot/grub/x86_64-efi/")
         logging.debug(
-            "exec_rsync(%s, %s + '/efi/ubuntu/grub.cfg')" % (ubuntu_cfg, target)
+            "exec_rsync(%s, %s + '/boot/grub/x86_64-efi/grub.cfg')" % (grub_cfg, target)
         )
-        exec_rsync(ubuntu_cfg, target + "/efi/ubuntu/grub.cfg")
+        exec_rsync(grub_cfg, target + "/boot/grub/x86_64-efi/grub.cfg")
 
         logging.debug(
             "exec_rsync(%s + '/EFI/BOOT/grubx64.efi', %s + '/efi/boot/grubx64.efi')'"
@@ -1893,8 +1798,7 @@ def install_grml(mountpoint, device):
 
 
 def remove_mountpoint(mountpoint):
-    """remove a registered mountpoint
-    """
+    """remove a registered mountpoint"""
 
     try:
         unmount(mountpoint, "")
@@ -1917,7 +1821,7 @@ def handle_mbr(device):
 
     mbr_device, partition_number = get_device_from_partition(device)
     if partition_number is None:
-        logging.warn("Could not detect partition number, not activating partition")
+        logging.warning("Could not detect partition number, not activating partition")
 
     # 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
@@ -1941,7 +1845,7 @@ def handle_mbr(device):
                 break
 
         if not mbrcode:
-            str_locations = " or ".join(['"%s"' % l for l in mbr_locations])
+            str_locations = " or ".join(['"%s"' % x for x in mbr_locations])
             logging.error("Cannot find syslinux MBR, install it at %s)", str_locations)
             raise CriticalException(
                 "syslinux MBR  can not be found at %s." % str_locations
@@ -2152,7 +2056,7 @@ def main():
         if not os.path.isdir(device):
             if device[-1:].isdigit():
                 if int(device[-1:]) > 4 or device[-2:].isdigit():
-                    logging.warn(
+                    logging.warning(
                         "Warning: installing on partition number >4, booting *might* fail depending on your system."
                     )