#!/usr/bin/env python # -*- coding: utf-8 -*- """ grml2usb ~~~~~~~~ This script installs a grml system (either a running system or ISO[s]) to a USB device :copyright: (c) 2009 by Michael Prokop :license: GPL v2 or any later version :bugreports: http://grml.org/bugs/ """ from optparse import OptionParser from inspect import isroutine, isclass import datetime, logging, os, re, subprocess, sys, tempfile, time, os.path import fileinput import glob import uuid # global variables PROG_VERSION = "0.9.23" 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 def syslinux_warning(option, opt, value, parser): sys.stderr.write("Note: the --syslinux option is deprecated as syslinux " + "is grml2usb's default. Continuing anyway.\n") setattr(parser.values, option.dest, True) # if grub option is set, unset syslinux option def grub_option(option, opt, value, parser): setattr(parser.values, option.dest, True) setattr(parser.values, 'syslinux', False) # cmdline parsing USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> \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\ grub or syslinux and root access.\n\ \n\ Run %prog --help for usage hints, further information via: man grml2usb" # pylint: disable-msg=C0103 parser = OptionParser(usage=USAGE) parser.add_option("--bootoptions", dest="bootoptions", action="store", type="string", 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", help="copy files only but do not install bootloader") parser.add_option("--dry-run", dest="dryrun", action="store_true", 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="callback", callback=grub_option, help="install grub bootloader instead of (default) syslinux") parser.add_option("--grub-mbr", dest="grubmbr", action="store_true", help="install grub into MBR instead of (default) PBR") parser.add_option("--lilo-binary", dest="lilobin", action="store", type="string", help="lilo executable to be used for installing MBR") parser.add_option("--mbr-menu", dest="mbrmenu", action="store_true", help="enable interactive boot menu in MBR") parser.add_option("--quiet", dest="quiet", action="store_true", help="do not output anything but just errors on console") parser.add_option("--remove-bootoption", dest="removeoption", action="append", help="regex for removing existing bootoptions") parser.add_option("--skip-addons", dest="skipaddons", action="store_true", help="do not install /boot/addons/ files") parser.add_option("--skip-grub-config", dest="skipgrubconfig", action="store_true", help="skip generation of grub configuration files") parser.add_option("--skip-mbr", dest="skipmbr", action="store_true", help="do not install a master boot record (MBR) on the device") parser.add_option("--skip-syslinux-config", dest="skipsyslinuxconfig", action="store_true", help="skip generation of syslinux configuration files") parser.add_option("--syslinux", dest="syslinux", action="callback", default=True, callback=syslinux_warning, help="install syslinux bootloader (deprecated as it's the default)") parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true", help="install syslinux master boot record (MBR) instead of default") parser.add_option("--verbose", dest="verbose", action="store_true", help="enable verbose mode") parser.add_option("-v", "--version", dest="version", action="store_true", help="display version and exit") (options, args) = parser.parse_args() GRML2USB_BASE = '/usr/share/grml2usb' if not os.path.isdir(GRML2USB_BASE): GRML2USB_BASE = os.path.dirname(os.path.realpath(__file__)) class CriticalException(Exception): """Throw critical exception if the exact error is not known but fatal." @Exception: message""" pass # The following two functions help to operate on strings as # array (list) of bytes (octets). In Python 3000, the bytes # datatype will need to be used. This is intended for using # with manipulation of files on the octet level, like shell # arrays, e.g. in MBR creation. def array2string(a): """Convert a list of integers [0;255] to a string.""" return struct.pack("%sB" % len(a), *a) def string2array(s): """Convert a (bytes) string into a list of integers.""" return struct.unpack("%sB" % len(s), s) def cleanup(): """Cleanup function to make sure there aren't any mounted devices left behind. """ 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 RuntimeError: logging.debug('caught expection RuntimeError, ignoring') def register_tmpfile(path): """ TODO - not implemented yet """ TMPFILES.add(path) def unregister_tmpfile(path): """ TODO - not implemented yet """ if path in TMPFILES: TMPFILES.remove(path) def register_mountpoint(target): """register specified target in a set() for handling clean exiting @target: destination target of mountpoint """ MOUNTED.add(target) def unregister_mountpoint(target): """unregister specified target in a set() for handling clean exiting @target: destination target of mountpoint """ if target in MOUNTED: MOUNTED.remove(target) def get_function_name(obj): """Helper function for use in execute() to retrive name of a function @obj: the function object """ if not (isroutine(obj) or isclass(obj)): obj = type(obj) return obj.__module__ + '.' + obj.__name__ def execute(f, *exec_arguments): """Wrapper for executing a command. Either really executes the command (default) or when using --dry-run commandline option just displays what would be executed.""" # usage: execute(subprocess.Popen, (["ls", "-la"])) # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas? if options.dryrun: # pylint: disable-msg=W0141 logging.debug('dry-run only: %s(%s)', get_function_name(f), ', '.join(map(repr, exec_arguments))) else: # pylint: disable-msg=W0142 return f(*exec_arguments) def is_exe(fpath): """Check whether a given file can be executed @fpath: full path to file @return:""" return os.path.exists(fpath) and os.access(fpath, os.X_OK) def which(program): """Check whether a given program is available in PATH @program: name of executable""" fpath = os.path.split(program)[0] if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def get_defaults_file(iso_mount, flavour, name): """get the default file for syslinux """ bootloader_dirs = ['/boot/isolinux/', '/boot/syslinux/'] for dir in bootloader_dirs: for name in name, \ "%s_%s" % (flavour_filename(flavour), name): if os.path.isfile(iso_mount + dir + name): return (dir, name) return ('','') def search_file(filename, search_path='/bin' + os.pathsep + '/usr/bin'): """Given a search path, find file @filename: name of file to search for @search_path: path where searching for the specified filename""" file_found = 0 paths = search_path.split(os.pathsep) current_dir = '' # make pylint happy :) def match_file(cwd): return os.path.exists(os.path.join(cwd, filename)) for path in paths: current_dir = path if match_file(current_dir): file_found = 1 break # pylint: disable-msg=W0612 for current_dir, directories, files in os.walk(path): if match_file(current_dir): file_found = 1 break if file_found: return os.path.abspath(os.path.join(current_dir, filename)) else: return None def check_uid_root(): """Check for root permissions""" if not os.geteuid()==0: sys.exit("Error: please run this script with uid 0 (root).") def mkfs_fat16(device): """Format specified device with VFAT/FAT16 filesystem. @device: partition that should be formated""" if options.dryrun: logging.info("Would execute mkfs.vfat -F 16 %s now.", device) return 0 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 CriticalException("error executing mkfs.vfat") def generate_main_grub2_config(grml_flavour, bootoptions): """Generate grub2 configuration for use via grub.cfg @grml_flavour: name of grml flavour the configuration should be generated for @bootoptions: additional bootoptions that should be used by default""" local_datestamp = DATESTAMP return("""\ ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s] set default=0 set timeout=10 insmod fat if loadfont /boot/grub/ascii.pf2 ; then insmod png set gfxmode=640x480 insmod gfxterm insmod vbe if terminal_output.gfxterm ; then true ; else # For backward compatibility with versions of terminal.mod that don't # understand terminal_output terminal gfxterm fi fi if background_image /boot/grub/grml.png ; then set color_normal=black/black set color_highlight=red/black else set menu_color_normal=white/black set menu_color_highlight=black/yellow fi menuentry "%(grml_flavour)s (default)" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off quiet boot=live nomce live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } menuentry "Memory test (memtest86+)" { linux16 /boot/addons/memtest } menuentry "Boot Grub (all in one image)" { linux /boot/addons/memdisk initrd /boot/addons/allinone.img } menuentry "Boot FreeDOS" { linux /boot/addons/memdisk initrd /boot/addons/balder10.imz } menuentry "Boot MirOS bsd4grml" { multiboot /boot/addons/bsd4grml/ldbsd.com module /boot/addons/bsd4grml/bsd.rd module /boot/addons/bsd4grml/boot.1 module /boot/addons/bsd4grml/boot.2 module /boot/addons/bsd4grml/boot.3 module /boot/addons/bsd4grml/boot.4 module /boot/addons/bsd4grml/boot.5 module /boot/addons/bsd4grml/boot.6 module /boot/addons/bsd4grml/boot.cfg } menuentry "Boot OS of first partition on first disk" { set root=(hd0,1) chainloader +1 } """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'flavour_filename': grml_flavour.replace('-', ''), 'uid': UUID, 'bootoptions': bootoptions } ) def generate_flavour_specific_grub2_config(grml_flavour, bootoptions): """Generate grub2 configuration for use via grub.cfg @grml_flavour: name of grml flavour the configuration should be generated for @bootoptions: additional bootoptions that should be used by default""" local_datestamp = DATESTAMP return("""\ ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s - boot in default mode" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-persistent - enable persistency feature" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet persistent live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s2ram - copy compressed grml file to RAM" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-debug - enable debugging options" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ debug bootid=%(uid)s initcall_debug %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-x - start X Window System" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-nofb - disable framebuffer" { linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=ofonly bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-failsafe - disable hardware detection" { linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal noautoconfig atapicd noapic noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia nosmp maxcpus=0 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm noresume selinux=0 edd=off pci=nomsi bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-forensic - do not touch harddisks during hw recognition" { set gfxpayload=1024x768x16,1024x768 linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ nofstab noraid nolvm noautoconfig noswap raid=noautodetect forensic readonly bootid=%(uid)s %(bootoptions)s initrd /boot/release/%(flavour_filename)s/initrd.gz } """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'flavour_filename': grml_flavour.replace('-', ''), 'uid': UUID, 'bootoptions': bootoptions } ) def generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootoptions): """Generate grub1 configuration for use via menu.lst @grml_flavour: name of grml flavour the configuration should be generated for @install_partition: partition number for use in (hd0,X) @bootoptions: additional bootoptions that should be used by default""" local_datestamp = DATESTAMP return("""\ ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-persistent kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet persistent live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s2ram kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-debug kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ debug initcall_debug bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-x kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-nofb kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=ofonly bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-failsafe kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal noautoconfig atapicd noapic noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia nosmp maxcpus=0 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm noresume selinux=0 edd=off pci=nomsi bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-forensic kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ nofstab noraid nolvm noautoconfig noswap raid=noautodetect forensic readonly bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] title %(grml_flavour)s-serial kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=vesafb:off console=tty1 console=ttyS0,9600n8 bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'flavour_filename': grml_flavour.replace('-', ''), 'uid': UUID, 'bootoptions': bootoptions, 'install_partition': install_partition } ) def generate_main_grub1_config(grml_flavour, install_partition, bootoptions): """Generate grub1 configuration for use via menu.lst @grml_flavour: name of grml flavour the configuration should be generated for""" local_datestamp = DATESTAMP return("""\ ## main grub1 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s] # misc options: timeout 30 # color red/blue green/black splashimage=(hd0,%(install_partition)s)/boot/grub/splash.xpm.gz foreground = 000000 background = FFCC33 # define entries: title %(grml_flavour)s - Default boot (using 1024x768 framebuffer) kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off vga=791 quiet boot=live nomce live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz title Memory test (memtest86+) kernel (hd0,%(install_partition)s)/boot/addons/memtest title Grub - all in one image kernel (hd0,%(install_partition)s)/boot/addons/memdisk initrd (hd0,%(install_partition)s)/boot/addons/allinone.img title FreeDOS kernel (hd0,%(install_partition)s)/boot/addons/memdisk initrd (hd0,%(install_partition)s)/boot/addons/balder10.imz title MirOS BSD kernel (hd0,%(install_partition)s)/boot/addons/bsd4grml/ldbsd.com """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'flavour_filename': grml_flavour.replace('-', ''), 'uid': UUID, 'bootoptions': bootoptions, 'install_partition': install_partition } ) def generate_isolinux_splash(grml_flavour): """Generate bootsplash for isolinux/syslinux @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("""\ 17 /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(*args): """Generate main configuration for use in syslinux.cfg @*args: just for backward compatibility""" local_datestamp = DATESTAMP return("""\ label - menu label Default boot modes: menu disable include defaults.cfg menu end menu separator # flavours: label - menu label Additional boot entries for: menu disable include additional.cfg menu separator include options.cfg include addons.cfg label help include promptname.cfg config prompt.cfg text help Jump to old style isolinux prompt featuring further information regarding available boot options. endtext include hiddens.cfg """) def generate_flavour_specific_syslinux_config(grml_flavour): """Generate flavour specific configuration for use in syslinux.cfg @grml_flavour: name of grml flavour the configuration should be generated for""" return("""\ menu begin grml %(grml_flavour)s menu title %(display_name)s label mainmenu menu label ^Back to main menu... menu exit menu separator # include config for boot parameters from disk include %(grml_flavour)s_grml.cfg menu hide menu end """ % {'grml_flavour': grml_flavour, 'display_name' : grml_flavour.replace('_', '-') } ) def install_grub(device): """Install grub on specified device. @mntpoint: mountpoint of device where grub should install its files to @device: partition where grub should be installed to""" if options.dryrun: logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device) else: device_mountpoint = tempfile.mkdtemp(prefix="grml2usb") register_tmpfile(device_mountpoint) try: try: mount(device, device_mountpoint, "") # If using --grub-mbr then make sure we install grub in MBR instead of PBR # Thanks to grub2. NOT. 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 else: grub_device = device logging.info("Installing grub as bootloader") logging.debug("grub-install --recheck --no-floppy --root-directory=%s %s", device_mountpoint, grub_device) proc = subprocess.Popen(["grub-install", "--recheck", "--no-floppy", "--root-directory=%s" % device_mountpoint, grub_device], stdout=file(os.devnull, "r+")) proc.wait() if proc.returncode != 0: # raise Exception("error executing grub-install") logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ or drop the --grub option)") logging.critical("Note: if using grub2 consider using the --grub-mbr option because grub2's PBR feature is broken.") cleanup() sys.exit(1) except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() sys.exit(1) finally: unmount(device_mountpoint, "") os.rmdir(device_mountpoint) unregister_tmpfile(device_mountpoint) 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/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 CriticalException("Error executing syslinux (either try --fat16 or use grub?)") def install_bootloader(device): """Install bootloader on specified device. @device: partition where bootloader should be installed to""" # by default we use grub, so install syslinux only on request if options.grub: if not which("grub-install"): logging.critical("Fatal: grub-install not available (please install the grub package or use the --syslinux option)") cleanup() sys.exit(1) else: try: install_grub(device) except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() sys.exit(1) else: try: install_syslinux(device) except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() sys.exit(1) def execute_lilo(lilo, device): """execute lilo for activating the partitions in the MBR @lilo: path of lilo executable @device: device where lilo should be executed on""" # to support -A for extended partitions: logging.info("Activating partitions in MBR via lilo") 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: logging.debug("%s -S /dev/null -A %s 1", lilo, device) proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"]) proc.wait() if proc.returncode != 0: raise Exception("error executing lilo") def install_syslinux_mbr(device): """install syslinux's master boot record (MBR) on the specified device @device: device where MBR of syslinux should be installed to""" # make sure we have syslinux available if not which("syslinux") and not options.copyonly: raise Exception("syslinux not available (either install it or consider using the --grub option)") # lilo's mbr is broken, use the one from syslinux instead: if not os.path.isfile("/usr/lib/syslinux/mbr.bin"): raise Exception("/usr/lib/syslinux/mbr.bin can not be read") logging.info("Installing syslinux MBR") logging.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: logging.critical("Error copying MBR to device (%s)", retcode) except OSError, error: logging.critical("Execution failed: %s", error) def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True): """install 'mbr' master boot record (MBR) on a device Retrieve the partition table from "device", install an MBR from the "mbrtemplate" file, set the "partition" (0..3) active, and install the result back to "device". @mbrtemplate: default MBR file @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 active flags to 0 and set the active flag of the partition to 80h. Note: behaviour of mirbsdmbr: if flag = 0/1/2/3 then use it, otherwise search for the active flag.""" 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?)") if (partition < 0) or (partition > 3): raise ValueError("partition must be between 0 and 3") if ismirbsdmbr: nmbrbytes = 439 else: nmbrbytes = 440 tmpf = tempfile.NamedTemporaryFile() logging.debug("executing: dd if='%s' of='%s' bs=512 count=1", device, tmpf.name) proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"], stderr=file(os.devnull, "r+")) proc.wait() if proc.returncode != 0: raise Exception("error executing dd (first run)") logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc", mbrtemplate, tmpf.name, nmbrbytes) proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes, "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+")) proc.wait() if proc.returncode != 0: raise Exception("error executing dd (second run)") mbrcode = tmpf.file.read(512) if len(mbrcode) < 512: raise EOFError("MBR size (%d) < 512" % len(mbrcode)) if ismirbsdmbr: mbrcode = mbrcode[0:439] + chr(partition) + \ mbrcode[440:510] + "\x55\xAA" else: actives = ["\x00", "\x00", "\x00", "\x00"] actives[partition] = "\x80" mbrcode = mbrcode[0:446] + actives[0] + \ mbrcode[447:462] + actives[1] + \ mbrcode[463:478] + actives[2] + \ mbrcode[479:494] + actives[3] + \ mbrcode[495:510] + "\x55\xAA" tmpf.file.seek(0) tmpf.file.truncate() tmpf.file.write(mbrcode) tmpf.file.close() logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc", tmpf.name, device) proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+")) proc.wait() if proc.returncode != 0: raise Exception("error executing dd (third run)") del tmpf def handle_syslinux_mbr(device): """Install syslinux master boot record on given device @device: device where MBR should be installed to""" if not is_writeable(device): raise IOError("device not writeable for user") # try to use system's lilo if which("lilo"): lilo = which("lilo") else: # otherwise fall back to our static version from platform import architecture if architecture()[0] == '64bit': lilo = GRML2USB_BASE + '/lilo/lilo.static.amd64' else: lilo = GRML2USB_BASE + '/lilo/lilo.static.i386' # finally prefer a specified lilo executable if options.lilobin: lilo = options.lilobin if not is_exe(lilo): raise Exception("lilo executable can not be execute") if options.dryrun: logging.info("Would install MBR running lilo and using syslinux.") return 0 execute_lilo(lilo, device) install_syslinux_mbr(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 #raise Exception("no device for checking write permissions") 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 @source: name of device/ISO that should be mounted @target: directory where the ISO should be mounted to @options: mount specific options""" # note: options.dryrun does not work here, as we have to # locate files and identify the grml flavour for x in file('/proc/mounts').readlines(): if x.startswith(source): raise CriticalException("Error executing mount: %s already mounted - please unmount before invoking grml2usb" % source) if os.path.isdir(source): logging.debug("Source %s is not a device, therefore not mounting.", source) return 0 logging.debug("mount %s %s %s", mount_options, source, target) proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target]) proc.wait() if proc.returncode != 0: raise CriticalException("Error executing mount (no filesystem on the partition?)") else: logging.debug("register_mountpoint(%s)", target) register_mountpoint(target) def unmount(target, unmount_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(os.path.realpath(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(unmount_options), target) proc = subprocess.Popen(["umount"] + list(unmount_options) + [target]) proc.wait() if proc.returncode != 0: raise Exception("Error executing umount") else: logging.debug("unregister_mountpoint(%s)", target) unregister_mountpoint(target) 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) # newer systems: usbdev = os.path.realpath('/sys/class/block/' + usbdevice + '/removable') if not os.path.isfile(usbdev): # Ubuntu with kernel 2.6.24 for example: usbdev = os.path.realpath('/sys/block/' + usbdevice + '/removable') if os.path.isfile(usbdev): is_usb = open(usbdev).readline() if is_usb.find("1"): return 0 return 1 def check_for_fat(partition): """Check whether specified partition is a valid VFAT/FAT16 filesystem @partition: device name of partition""" try: udev_info = subprocess.Popen(["/sbin/blkid", "-s", "TYPE", "-o", "value", partition], stdout=subprocess.PIPE, stderr=subprocess.PIPE) filesystem = udev_info.communicate()[0].rstrip() if udev_info.returncode == 2: raise CriticalException("Failed to read device %s" " (wrong UID/permissions or device/directory not present?)" % partition) if filesystem != "vfat": raise CriticalException("Partition %s does not contain a FAT16 filesystem. (Use --fat16 or run mkfs.vfat %s)" % (partition, partition)) except OSError: raise CriticalException("Sorry, /sbin/blkid not available (install e2fsprogs?)") def mkdir(directory): """Simple wrapper around os.makedirs to get shell mkdir -p behaviour""" # just silently pass as it's just fine it the directory exists if not os.path.isdir(directory): try: os.makedirs(directory) # pylint: disable-msg=W0704 except OSError: pass def exec_rsync(source, target): """Simple wrapper around rsync to install files @source: source file/directory @target: target file/directory""" logging.debug("Source: %s / Target: %s", source, target) proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", source, target]) proc.wait() if proc.returncode == 12: logging.critical("Fatal: No space left on device") cleanup() sys.exit(1) if proc.returncode != 0: logging.critical("Fatal: could not install %s", source) cleanup() sys.exit(1) def write_uuid(target_file): file = open(target_file, 'w') uid = str(uuid.uuid4()) file.write(uid) file.close() return uid def get_uuid(target): conf_target = target + "/conf/" uuid_file_name = conf_target + "/bootid.txt" if os.path.isdir(conf_target): if os.path.isfile(uuid_file_name): uuid_file = open(uuid_file_name, 'r') uid = uuid_file.readline().strip() uuid_file.close() return uid else: return write_uuid(uuid_file_name) else: execute(mkdir, conf_target) return write_uuid(uuid_file_name) def copy_system_files(grml_flavour, iso_mount, target): """copy grml's main files (like squashfs, kernel and initrd) to a given target @grml_flavour: name of grml flavour the configuration should be generated for @iso_mount: path where a grml ISO is mounted on @target: path where grml's main files should be copied to""" squashfs = search_file(grml_flavour + '.squashfs', iso_mount) if squashfs is None: logging.critical("Fatal: squashfs file not found") raise CriticalException("error locating squashfs file") else: squashfs_target = target + '/live/' + grml_flavour + '/' execute(mkdir, squashfs_target) exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs') for prefix in grml_flavour + "/", "": filesystem_module = search_file(prefix + 'filesystem.module', iso_mount) if filesystem_module: break if filesystem_module is None: logging.critical("Fatal: filesystem.module not found") raise CriticalException("error locating filesystem.module file") else: exec_rsync(filesystem_module, squashfs_target + 'filesystem.module') release_path = 'boot/release/' + grml_flavour.replace('-', '') release_target = target + "/" + release_path execute(mkdir, release_target) prefix = "" if os.path.isdir(iso_mount + '/boot/release'): prefix = release_path + '/' kernel = search_file(prefix + 'linux26', iso_mount) if kernel is None: logging.critical("Fatal kernel not found") raise CriticalException("error locating kernel file") else: exec_rsync(kernel, release_target + '/linux26') initrd = search_file(prefix + 'initrd.gz', iso_mount) if initrd is None: logging.critical("Fatal: initrd not found") raise CriticalException("error locating initrd file") else: exec_rsync(initrd, release_target + '/initrd.gz') def copy_grml_files(iso_mount, target): """copy some minor grml files to a given target @iso_mount: path where a grml ISO is mounted on @target: path where grml's main files should be copied to""" grml_target = target + '/grml/' execute(mkdir, grml_target) copy_files = [ 'grml-cheatcodes.txt', 'LICENSE.txt', 'md5sums', 'README.txt' ] # handle grml-version new_grml_version = search_file('grml-version', grml_target) if new_grml_version: orig_grml_version = search_file('grml-version', iso_mount) if not orig_grml_version: logging.warn("Warning: %s could not be found - can not install it", orig_grml_version) else: try: new_file = open(new_grml_version, 'a+') new_flavours = [ get_flavour(l) for l in new_file.readlines() ] old_file = open(orig_grml_version, 'r') old_lines = old_file.readlines() for line in old_lines: if not get_flavour(line) in new_flavours: new_file.write(line) except IOError, e: logging.warn("Warning: Could not write file") finally: new_file.close() old_file.close() else: copy_files.append('grml-version') for myfile in copy_files: grml_file = search_file(myfile, iso_mount) if grml_file is None: logging.warn("Warning: myfile %s could not be found - can not install it", myfile) else: exec_rsync(grml_file, grml_target + myfile) grml_web_target = grml_target + '/web/' execute(mkdir, grml_web_target) for myfile in 'index.html', 'style.css': grml_file = search_file(myfile, iso_mount) if grml_file is None: logging.warn("Warning: myfile %s could not be found - can not install it") else: exec_rsync(grml_file, grml_web_target + myfile) grml_webimg_target = grml_web_target + '/images/' execute(mkdir, grml_webimg_target) for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png': grml_file = search_file(myfile, iso_mount) if grml_file is None: logging.warn("Warning: myfile %s could not be found - can not install it") else: exec_rsync(grml_file, grml_webimg_target + myfile) def copy_addons(iso_mount, target): """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target @iso_mount: path where a grml ISO is mounted on @target: path where grml's main files should be copied to""" addons = target + '/boot/addons/' execute(mkdir, addons) # grub all-in-one image allinoneimg = search_file('allinone.img', iso_mount) if allinoneimg is None: logging.warn("Warning: allinone.img not found (that's fine if you don't need it)") else: exec_rsync(allinoneimg, addons + 'allinone.img') # bsd imag bsdimg = search_file('bsd4grml', iso_mount) if bsdimg is None: logging.warn("Warning: bsd4grml not found (that's fine if you don't need it)") else: exec_rsync(bsdimg, addons + '/') # freedos image balderimg = search_file('balder10.imz', iso_mount) if balderimg is None: logging.warn("Warning: balder10.imz not found (that's fine if you don't need it)") else: exec_rsync(balderimg, addons + 'balder10.imz') # install hdt and pci.ids only when using syslinux (grub doesn't support it) if options.syslinux: # hdt (hardware detection tool) image hdtimg = search_file('hdt.c32', iso_mount) if hdtimg: exec_rsync(hdtimg, addons + '/hdt.c32') # pci.ids file picids = search_file('pci.ids', iso_mount) if picids: exec_rsync(picids, addons + '/pci.ids') # memdisk image memdiskimg = search_file('memdisk', iso_mount) if memdiskimg is None: logging.warn("Warning: memdisk not found (that's fine if you don't need it)") else: exec_rsync(memdiskimg, addons + 'memdisk') # memtest86+ image memtestimg = search_file('memtest', iso_mount) if memtestimg is None: logging.warn("Warning: memtest not found (that's fine if you don't need it)") else: exec_rsync(memtestimg, addons + 'memtest') # gpxe.lkrn gpxeimg = search_file('gpxe.lkrn', iso_mount) if gpxeimg is None: logging.warn("Warning: gpxe.lkrn not found (that's fine if you don't need it)") else: exec_rsync(gpxeimg, addons + 'gpxe.lkrn') def copy_bootloader_files(iso_mount, target): """copy grml's bootloader files to a given target @iso_mount: path where a grml ISO is mounted on @target: path where grml's main files should be copied to""" syslinux_target = target + '/boot/syslinux/' execute(mkdir, syslinux_target) logo = search_file('logo.16', iso_mount) exec_rsync(logo, syslinux_target + 'logo.16') for ffile in ['f%d' % number for number in range(1,11) ]: bootsplash = search_file(ffile, iso_mount) if not bootsplash: continue exec_rsync(bootsplash, syslinux_target + ffile) loopback_cfg = search_file("loopback.cfg", iso_mount) if loopback_cfg: directory = os.path.dirname(loopback_cfg) directory = directory.replace(iso_mount, "") if not os.path.isdir(target + "/" + directory): os.mkdir(target + os.path.sep + directory) exec_rsync(loopback_cfg, target + os.path.sep + directory) # avoid the "file is read only, overwrite anyway (y/n) ?" question # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file") if os.path.isfile(syslinux_target + 'ldlinux.sys'): os.unlink(syslinux_target + 'ldlinux.sys') source_dir, name = get_defaults_file(iso_mount, GRML_DEFAULT, "default.cfg") (source_dir,options) = get_defaults_file(iso_mount, GRML_DEFAULT, "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.") logging.critical(" Please visit http://grml.org/grml2usb/#grml2usb-compat for further information.") raise for expr in name, 'distri.cfg', \ options, 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \ 'isoprompt.cfg', 'options.cfg', \ 'prompt.cfg', 'vesamenu.c32', 'vesamenu.cfg', 'grml.png', '*.c32': files = glob.glob(iso_mount + source_dir + expr) for path in files: filename = os.path.basename(path) exec_rsync(path, syslinux_target + filename) # copy the addons_*.cfg file to the new syslinux directory for filename in glob.glob(iso_mount + source_dir + 'addon*.cfg'): exec_rsync(filename, syslinux_target) path = search_file('hidden.cfg', iso_mount + source_dir) if path: exec_rsync(path, syslinux_target + "new_" + 'hidden.cfg') grub_target = target + '/boot/grub/' execute(mkdir, grub_target) if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"): logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE) logging.critical("Please make sure you've installed the grml2usb (Debian) package!") raise else: exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz') # grml splash in grub if os.path.isfile(GRML2USB_BASE + "/grub/grml.png"): exec_rsync(GRML2USB_BASE + '/grub/grml.png', grub_target + 'grml.png') # font file for graphical bootsplash in grub if os.path.isfile("/usr/share/grub/ascii.pf2"): exec_rsync('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2') # always copy grub content as it might be useful for file in glob.glob(iso_mount + '/boot/grub/*.mod'): exec_rsync(file, grub_target) for file in glob.glob(iso_mount + '/boot/grub/*.img'): exec_rsync(file, grub_target) for file in glob.glob(iso_mount + '/boot/grub/stage*'): exec_rsync(file, grub_target) def install_iso_files(grml_flavour, iso_mount, device, target): """Copy files from ISO to given target @grml_flavour: name of grml flavour the configuration should be generated for @iso_mount: path where a grml ISO is mounted on @device: device/partition where bootloader should be installed to @target: path where grml's main files should be copied to""" # TODO => several improvements: # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception # * provide alternative search_file() if file information is stored in a config.ini file? # * catch "install: .. No space left on device" & CO global GRML_DEFAULT GRML_DEFAULT = grml_flavour if options.dryrun: return 0 elif not options.bootloaderonly: logging.info("Copying files. This might take a while....") try: copy_system_files(grml_flavour, iso_mount, target) copy_grml_files(iso_mount, target) except CriticalException, error: logging.critical("Execution failed: %s", error) sys.exit(1) if not options.skipaddons: if not search_file('addons', iso_mount): logging.info("Could not find addons, therefore not installing.") else: copy_addons(iso_mount, target) if not options.copyonly: copy_bootloader_files(iso_mount, target) if not options.dryrun: handle_bootloader_config(grml_flavour, device, target) # make sure we sync filesystems before returning proc = subprocess.Popen(["sync"]) proc.wait() def uninstall_files(device): """Get rid of all grml files on specified device @device: partition where grml2usb files should be removed from""" # TODO - not implemented yet logging.critical("TODO: uninstalling files from %s not yet implement, sorry.", device) def get_flavour(str): """Returns the flavour of a grml version string """ return re.match(r'[\w-]*', str).group() def identify_grml_flavour(mountpath): """Get name of grml flavour @mountpath: path where the grml ISO is mounted to @return: name of grml-flavour""" version_file = search_file('grml-version', mountpath) if version_file == "": logging.critical("Error: could not find grml-version file.") raise flavours = [] tmpfile = None try: tmpfile = open(version_file, 'r') for line in tmpfile.readlines(): flavours.append(get_flavour(line)) except TypeError, e: raise except Exception, e: logging.critical("Unexpected error: %s", e) raise finally: if tmpfile: tmpfile.close() return flavours def modify_grub_config(filename): if options.removeoption: regexe = [] for regex in options.removeoption: regexe.append(re.compile(r'%s' % regex)) option_re = re.compile(r'(.*/boot/release/.*linux26.*)') for line in fileinput.input(filename, inplace=1): if regexe and option_re.search(line): for regex in regexe: line = regex.sub(' ', line) sys.stdout.write(line) fileinput.close() def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt): """Main handler for generating grub1 configuration @grml_flavour: name of grml flavour the configuration should be generated for @install_partition: partition number for use in (hd0,X) @grub_target: path of grub's configuration files @bootoptions: additional bootoptions that should be used by default""" # grub1 config grub1_cfg = grub_target + 'menu.lst' logging.debug("Creating grub1 configuration file (menu.lst)") # install main configuration only *once*, no matter how many ISOs we have: if os.path.isfile(grub1_cfg): string = open(grub1_cfg).readline() main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP))) if not re.match(main_identifier, string): grub1_config_file = open(grub1_cfg, 'w') grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt)) grub1_config_file.close() else: grub1_config_file = open(grub1_cfg, 'w') grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt)) grub1_config_file.close() grub_flavour_config = True if os.path.isfile(grub1_cfg): string = open(grub1_cfg).readlines() flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP)))) for line in string: if flavour.match(line): grub_flavour_config = False if grub_flavour_config: grub1_config_file = open(grub1_cfg, 'a') grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt)) grub1_config_file.close() modify_grub_config(grub1_cfg) # make sure grub.conf isn't a symlink but a plain file instead, # otherwise it will break on FAT16 filesystems # this works around grub-install of (at least) Fedora 10 if os.path.isfile(grub1_cfg): grubconf = grub_target + 'grub.conf' if not os.path.islink(grubconf): import shutil shutil.copyfile(grub1_cfg, grub_target + 'grub.conf') def handle_grub2_config(grml_flavour, grub_target, bootopt): """Main handler for generating grub2 configuration @grml_flavour: name of grml flavour the configuration should be generated for @grub_target: path of grub's configuration files @bootoptions: additional bootoptions that should be used by default""" # grub2 config grub2_cfg = grub_target + 'grub.cfg' logging.debug("Creating grub2 configuration file (grub.lst)") global GRML_DEFAULT # install main configuration only *once*, no matter how many ISOs we have: grub_flavour_is_default = False if os.path.isfile(grub2_cfg): string = open(grub2_cfg).readline() main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP))) if not re.match(main_identifier, string): grub2_config_file = open(grub2_cfg, 'w') GRML_DEFAULT = grml_flavour grub_flavour_is_default = True grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt)) grub2_config_file.close() else: grub2_config_file = open(grub2_cfg, 'w') GRML_DEFAULT = grml_flavour grub_flavour_is_default = True grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt)) grub2_config_file.close() # install flavour specific configuration only *once* as well grub_flavour_config = True if os.path.isfile(grub2_cfg): string = open(grub2_cfg).readlines() flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP)))) for line in string: if flavour.match(line): grub_flavour_config = False if grub_flavour_config: grub2_config_file = open(grub2_cfg, 'a') # display only if the grml flavour isn't the default if not grub_flavour_is_default: GRML_FLAVOURS.add(grml_flavour) grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt)) grub2_config_file.close() modify_grub_config(grub2_cfg) def handle_grub_config(grml_flavour, device, target): """Main handler for generating grub (v1 and v2) configuration @grml_flavour: name of grml flavour the configuration should be generated for @device: device/partition where grub should be installed to @target: path of grub's configuration files""" logging.debug("Generating grub configuration") grub_target = target + '/boot/grub/' execute(mkdir, grub_target) if os.path.isdir(device): install_grub1_partition = None else: if device[-1:].isdigit(): install_grub1_partition = int(device[-1:]) - 1 else: raise CriticalException("error validating partition schema (raw device?)") # do NOT write "None" in kernel cmdline if options.bootoptions is None: bootopt = "" else: bootopt = options.bootoptions # write menu.lst handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt) # write grub.cfg handle_grub2_config(grml_flavour, grub_target, bootopt) def initial_syslinux_config(target): """Generates intial syslinux configuration @target path of syslinux's configuration files""" target = target + "/" filename = target + "grmlmain.cfg" if os.path.isfile(target + "grmlmain.cfg"): return data = open(filename, "w") data.write(generate_main_syslinux_config()) data.close filename = target + "hiddens.cfg" data = open(filename, "w") data.write("include hidden.cfg\n") data.close() def add_entry_if_not_present(filename, entry): data = open(filename, "a+") for line in data: if line == entry: break else: data.write(entry) data.close() def flavour_filename(flavour): return flavour.replace('-', '_') def adjust_syslinux_bootoptions(src, flavour): append_re = re.compile("^(\s*append.*/boot/release.*)$", re.I) boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)") # flavour_re = re.compile("(label.*)(grml\w+)") default_re = re.compile("(default.cfg)") bootid_re = re.compile("bootid=[\w_-]+") live_media_path_re = re.compile("live-media-path=[\w_/-]+") # do NOT write "None" in kernel cmdline if options.bootoptions is None: bootopt = "" else: bootopt = options.bootoptions regexe = [] option_re = None if options.removeoption: option_re = re.compile(r'/boot/release/.*/initrd.gz') for regex in options.removeoption: regexe.append(re.compile(r'%s' % regex)) global UUID for line in fileinput.input(src, inplace=1): line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour.replace('-', ''), line) # line = flavour_re.sub(r'\1 %s-\2' % flavour, line) line = default_re.sub(r'%s-\1' % flavour, line) line = bootid_re.sub('', line) line = live_media_path_re.sub('', line) line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line) line = append_re.sub(r'\1 boot=live %s ' % bootopt, line) line = append_re.sub(r'\1 %s=%s ' % ("bootid", UUID), line) if option_re and option_re.search(line): for regex in regexe: line = regex.sub(' ', line) sys.stdout.write(line) fileinput.close() def adjust_labels(src, replacement): label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I) for line in fileinput.input(src, inplace=1): line = label_re.sub(replacement, line) sys.stdout.write(line) fileinput.close() def add_syslinux_entry(filename, grml_flavour): entry_filename = "option_%s.cfg" % grml_flavour entry = "include %s\n" % entry_filename add_entry_if_not_present(filename, entry) path = os.path.dirname(filename) data = open(path + "/" + entry_filename, "w") data.write(generate_flavour_specific_syslinux_config(grml_flavour)) data.close() def modify_filenames(grml_flavour, target, filenames): grml_filename = grml_flavour.replace('-', '_') for filename in filenames: old_filename = "%s/%s" % (target, filename) new_filename = "%s/%s_%s" % (target, grml_filename, filename) os.rename(old_filename, new_filename) def remove_default_entry(filename): default_re = re.compile("^(\s*menu\s*default\s*)$", re.I) for line in fileinput.input(filename, inplace=1): if default_re.match(line): continue sys.stdout.write(line) fileinput.close() def handle_syslinux_config(grml_flavour, target): """Main handler for generating syslinux configuration @grml_flavour: name of grml flavour the configuration should be generated for @target: path of syslinux's configuration files""" # do NOT write "None" in kernel cmdline if options.bootoptions is None: bootopt = "" else: bootopt = options.bootoptions logging.debug("Generating syslinux configuration") syslinux_target = target + '/boot/syslinux/' # should be present via copy_bootloader_files(), but make sure it exits: execute(mkdir, syslinux_target) syslinux_cfg = syslinux_target + 'syslinux.cfg' global GRML_DEFAULT # install main configuration only *once*, no matter how many ISOs we have: syslinux_flavour_is_default = False syslinux_config_file = open(syslinux_cfg, 'w') syslinux_config_file.write("TIMEOUT 300\n") syslinux_config_file.write("include vesamenu.cfg\n") syslinux_config_file.close() prompt_name = open(syslinux_target + 'promptname.cfg', 'w') prompt_name.write('menu label S^yslinux prompt\n') prompt_name.close() initial_syslinux_config(syslinux_target) flavour_filename = grml_flavour.replace('-', '_') if search_file('default.cfg', syslinux_target): modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg']) filename = search_file("new_hidden.cfg", syslinux_target) # process hidden file if not search_file("hidden.cfg", syslinux_target): new_hidden = syslinux_target + "hidden.cfg" os.rename(filename, new_hidden) adjust_syslinux_bootoptions(new_hidden, grml_flavour) else: new_hidden = "%s_hidden.cfg" % (flavour_filename) new_hidden_file = "%s/%s" % (syslinux_target, new_hidden) os.rename(filename, new_hidden_file) adjust_labels(new_hidden_file, r'\1 %s-\2' % grml_flavour) adjust_syslinux_bootoptions(new_hidden_file, grml_flavour) entry = 'include %s\n' % new_hidden add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry) new_default = "%s_default.cfg" % (flavour_filename) entry = 'include %s\n' % new_default defaults_file = '%s/defaults.cfg' % syslinux_target new_default_with_path = "%s/%s" % (syslinux_target, new_default) 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) # adjust all labels for additional isos adjust_labels(new_default_with_path, r'\1 %s' % grml_flavour) adjust_labels(new_grml_cfg, r'\1 %s-\2' % grml_flavour) # always adjust bootoptions adjust_syslinux_bootoptions(new_default_with_path, grml_flavour) adjust_syslinux_bootoptions(new_grml_cfg, grml_flavour) add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry) add_syslinux_entry("%s/additional.cfg" % syslinux_target, flavour_filename) def handle_bootloader_config(grml_flavour, device, target): """Main handler for generating bootloader's configuration @grml_flavour: name of grml flavour the configuration should be generated for @device: device/partition where bootloader should be installed to @target: path of bootloader's configuration files""" global UUID UUID = get_uuid(target) if options.skipsyslinuxconfig: logging.info("Skipping generation of syslinux configuration as requested.") else: try: handle_syslinux_config(grml_flavour, target) except CriticalException, error: logging.critical("Fatal: %s", error) sys.exit(1) if options.skipgrubconfig: logging.info("Skipping generation of grub configuration as requested.") else: try: handle_grub_config(grml_flavour, device, target) except CriticalException, error: logging.critical("Fatal: %s", error) sys.exit(1) def handle_dir(live_image, device): """Main logic for copying files of the currently running grml system. @live_image: directory where currently running live system resides (usually /live/image) @device: partition where the specified ISO should be installed to""" logging.info("Using %s as install base", live_image) if os.path.isdir(device): logging.info("Specified target is a directory, therefore not mounting.") device_mountpoint = device remove_device_mountpoint = False else: device_mountpoint = tempfile.mkdtemp(prefix="grml2usb") register_tmpfile(device_mountpoint) remove_device_mountpoint = True try: mount(device, device_mountpoint, "") except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() sys.exit(1) try: try: grml_flavours = identify_grml_flavour(live_image) worked_flavours = [] for flavour in grml_flavours: if flavour in worked_flavours: continue else: worked_flavours.append(flavour) logging.info("Identified grml flavour \"%s\".", flavour) install_iso_files(flavour, live_image, device, device_mountpoint) except TypeError: logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up") sys.exit(1) finally: if remove_device_mountpoint: try: unmount(device_mountpoint, "") if os.path.isdir(device_mountpoint): os.rmdir(device_mountpoint) unregister_tmpfile(device_mountpoint) except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() 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) iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb") 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) try: mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"]) except CriticalException, error: logging.critical("Fatal: %s", error) sys.exit(1) if os.path.isdir(device): logging.info("Specified target is a directory, therefore not mounting.") device_mountpoint = device remove_device_mountpoint = False # skip_mbr = True else: device_mountpoint = tempfile.mkdtemp(prefix="grml2usb") register_tmpfile(device_mountpoint) remove_device_mountpoint = True try: check_for_fat(device) mount(device, device_mountpoint, ['-o', 'utf8,iocharset=iso8859-1']) except CriticalException, error: try: mount(device, device_mountpoint, "") except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() sys.exit(1) try: try: grml_flavours = identify_grml_flavour(iso_mountpoint) worked_flavours = [] for flavour in grml_flavours: if flavour in worked_flavours: continue else: worked_flavours.append(flavour) logging.info("Identified grml flavour \"%s\".", flavour) install_iso_files(flavour, iso_mountpoint, device, device_mountpoint) except TypeError: logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), 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: try: unmount(device_mountpoint, "") if os.path.isdir(device_mountpoint): os.rmdir(device_mountpoint) unregister_tmpfile(device_mountpoint) except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() def handle_mbr(device): """Main handler for installing master boot record (MBR) @device: device where the MBR should be installed to""" if options.dryrun: 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 skip_install_mir_mbr = False # 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 if mbr_device == "/dev/loop": mbr_device = device logging.info("Detected loop device - using %s as MBR device therefore", mbr_device) skip_install_mir_mbr = True try: if options.syslinuxmbr: handle_syslinux_mbr(mbr_device) elif not skip_install_mir_mbr: if options.mbrmenu: install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True) else: install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False) except IOError, error: logging.critical("Execution failed: %s", error) sys.exit(1) except Exception, error: logging.critical("Execution failed: %s", error) sys.exit(1) def handle_vfat(device): """Check for FAT specific settings and options @device: device that should checked / formated""" # make sure we have mkfs.vfat available if options.fat16: 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) exec_mkfs = False if options.force: print "Forcing mkfs.fat16 on %s as requested via option --force." % device exec_mkfs = True else: # make sure the user is aware of what he is doing f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ") if f == "y" or f == "Y": logging.info("Note: you can skip this question using the option --force") exec_mkfs = True if exec_mkfs: try: mkfs_fat16(device) except CriticalException, error: logging.critical("Execution failed: %s", error) sys.exit(1) else: sys.exit(1) # check for vfat filesystem if device is not None and not os.path.isdir(device): try: if options.syslinux: check_for_fat(device) except CriticalException, error: logging.critical("Execution failed: %s", error) sys.exit(1) if not os.path.isdir(device) and not check_for_usbdevice(device) and not option.force: 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) def handle_compat_warning(device): """Backwards compatible checks @device: device that should be checked""" # 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) def handle_logging(): """Log handling and configuration""" if options.verbose and options.quiet: parser.error("please use either verbose (--verbose) or quiet (--quiet) option") if options.verbose: FORMAT = "Debug: %(asctime)-15s %(message)s" logging.basicConfig(level=logging.DEBUG, format=FORMAT) elif options.quiet: FORMAT = "Critical: %(message)s" logging.basicConfig(level=logging.CRITICAL, format=FORMAT) else: FORMAT = "%(message)s" logging.basicConfig(level=logging.INFO, format=FORMAT) def handle_bootloader(device): """wrapper for installing bootloader @device: device where bootloader should be installed to""" # Install bootloader only if not using the --copy-only option if options.copyonly: logging.info("Not installing bootloader and its files as requested via option copyonly.") elif os.path.isdir(device): logging.info("Not installing bootloader as %s is a directory.", device) else: install_bootloader(device) def main(): """Main function [make pylint happy :)]""" global GRML_FLAVOURS global GRML_DEFAULT global PROG_VERSION if options.version: print os.path.basename(sys.argv[0]) + " " + PROG_VERSION sys.exit(0) if len(args) < 2: parser.error("invalid usage") # log handling handle_logging() # make sure we have the appropriate permissions check_uid_root() 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.grubmbr and not options.grub: logging.critical("Error: --grub-mbr requires --grub option.") sys.exit(1) # specified arguments device = 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) else: if os.path.exists(device): logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)") sys.exit(1) if not which("rsync"): logging.critical("Fatal: rsync not available, can not continue - sorry.") sys.exit(1) # provide upgrade path handle_compat_warning(device) # check for vfat partition handle_vfat(device) # main operation (like installing files) for iso in isos: if os.path.isdir(iso): handle_dir(iso, device) else: handle_iso(iso, device) # install mbr if not os.path.isdir(device): if not options.skipmbr: handle_mbr(device) handle_bootloader(device) 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) # finally be politely :) logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION) if __name__ == "__main__": try: main() except KeyboardInterrupt: logging.info("Received KeyboardInterrupt") cleanup() ## END OF FILE ################################################################# # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8