#!/usr/bin/env python # -*- coding: utf-8 -*- # pylint: disable-msg=C0302 """ 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 import struct # global variables PROG_VERSION = "0.9.29" 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 SYSLINUX_LIBS = "/usr/lib/syslinux/" 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 " "is grml2usb's default. Continuing anyway.\n") setattr(opt_parser.values, option.dest, True) # if grub option is set, unset syslinux option 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) # 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 # pylint: disable-msg=W0603 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, "") for tmpfile in TMPFILES: os.unlink(tmpfile) # ignore: RuntimeError: Set changed size during iteration except RuntimeError: logging.debug('caught expection RuntimeError, ignoring') def register_tmpfile(path): """ register tmpfile """ TMPFILES.add(path) def unregister_tmpfile(path): """ remove registered tmpfile """ try: TMPFILES.remove(path) except KeyError: pass 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"])) 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 directory in bootloader_dirs: for name in name, \ "%s_%s" % (get_flavour_filename(flavour), name): if os.path.isfile(iso_mount + directory + name): return (directory, 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): """Helper function ffor testing if specified file exists in cwd @cwd: current working directory """ 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 } if [ ${iso_path} ] ; then # assume loopback.cfg boot if [ -e /boot/addons/bsd4grml/loopback.0 ] ; then # bsd4grml 20100815 and later menuentry "Boot MirOS bsd4grml" { multiboot /boot/addons/bsd4grml/ldbsd.com module /boot/addons/bsd4grml/bsd.rd bsd module /boot/addons/bsd4grml/loopback.0 boot.cfg module /boot/addons/bsd4grml/loopback.1 boot.1 module /boot/addons/bsd4grml/loopback.2 boot.2 module /boot/addons/bsd4grml/loopback.3 boot.3 module /boot/addons/bsd4grml/loopback.4 boot.4 module /boot/addons/bsd4grml/loopback.5 boot.5 module /boot/addons/bsd4grml/loopback.6 boot.6 } else # old bsd4grml menuentry "Boot MirOS bsd4grml" { multiboot /boot/addons/bsd4grml/ldbsd.com module /boot/addons/bsd4grml/bsd.rd bsd.rd module /boot/addons/bsd4grml/boot.cfg boot.cfg module /boot/addons/bsd4grml/boot.1 boot.1 module /boot/addons/bsd4grml/boot.2 boot.2 module /boot/addons/bsd4grml/boot.3 boot.3 module /boot/addons/bsd4grml/boot.4 boot.4 module /boot/addons/bsd4grml/boot.5 boot.5 } fi else # assume grub.cfg boot menuentry "Boot MirOS bsd4grml" { multiboot /boot/addons/bsd4grml/ldbsd.com module /boot/addons/bsd4grml/bsd.rd bsd.rd module /boot/addons/bsd4grml/boot.cfg boot.cfg module /boot/addons/bsd4grml/boot.1 boot.1 module /boot/addons/bsd4grml/boot.2 boot.2 module /boot/addons/bsd4grml/boot.3 boot.3 module /boot/addons/bsd4grml/boot.4 boot.4 module /boot/addons/bsd4grml/boot.5 boot.5 module /boot/addons/bsd4grml/boot.6 boot.6 } fi 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""" 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(*arg): """Generate main configuration for use in syslinux.cfg @*arg: just for backward compatibility""" # pylint: disable-msg=W0613 # remove warning about unused arg 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 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") for opt in ["", "--force" ]: logging.debug("grub-install --recheck %s --no-floppy --root-directory=%s %s", opt, device_mountpoint, grub_device) proc = subprocess.Popen(["grub-install", "--recheck", opt, "--no-floppy", "--root-directory=%s" % device_mountpoint, grub_device], stdout=file(os.devnull, "r+")) proc.wait() if proc.returncode == 0: break 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 as grub considers PBR problematic.") 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: 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: 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 - " % source + "please unmount before invoking grml2usb") 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""" if not os.access(partition, os.R_OK): raise CriticalException("Failed to read device %s" " (wrong UID/permissions or device/directory not present?)" % 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 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): """Generates an returns uuid and write it to the specified file @target_file: filename to write the uuid to """ fileh = open(target_file, 'w') uid = str(uuid.uuid4()) fileh.write(uid) fileh.close() return uid def get_uuid(target): """Get the uuid of the specified target. Will generate an uuid if none exist. @target: directory/mountpoint containing the grml layout """ 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" ", please check that your iso is not corrupt") 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 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 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(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 if not update_grml_versions(iso_mount, target): 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 handle_addon_copy(filename, dst, iso_mount): """handle copy of optional addons @filename: filename of the addon @dst: destination directory @iso_mount: location of the iso mount """ file_location = search_file(filename, iso_mount) if file_location is None: logging.warn("Warning: %s not found (that's fine if you don't need it)", filename) else: exec_rsync(file_location, dst) 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 handle_addon_copy('allinone.img', addons, iso_mount) # bsd imag handle_addon_copy('bsd4grml', addons, iso_mount) handle_addon_copy('balder10.imz', addons, iso_mount) # 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 handle_addon_copy('memdisk', addons, iso_mount) # memtest86+ image handle_addon_copy('memtest', addons, iso_mount) # gpxe.lkrn handle_addon_copy('gpxe.lkrn', addons, iso_mount) def glob_and_copy(filepattern, dst): """Glob on specified filepattern and copy the result to dst @filepattern: globbing pattern @dst: target directory """ for name in glob.glob(filepattern): copy_if_exist(name, dst) def search_and_copy(filename, search_path, dst): """Search for the specified filename at searchpath and copy it to dst @filename: filename to look for @search_path: base search file @dst: destionation to copy the file to """ file_location = search_file(filename, search_path) copy_if_exist(file_location, dst) def copy_if_exist(filename, dst): """Copy filename to dst if filename is set. @filename: a filename @dst: dst file """ if filename and (os.path.isfile(filename) or os.path.isdir(filename)): exec_rsync(filename, dst) def copy_bootloader_files(iso_mount, target, grml_flavour): """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 @grml_flavour: name of the current processed grml_flavour """ syslinux_target = target + '/boot/syslinux/' execute(mkdir, syslinux_target) grub_target = target + '/boot/grub/' execute(mkdir, grub_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) ]: search_and_copy(ffile, iso_mount, 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, "") mkdir(os.path.join(target, 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_flavour, "default.cfg") (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.") logging.critical(" Please visit http://grml.org/grml2usb/#grml2usb-compat for further information.") raise for expr in name, 'distri.cfg', \ defaults_file, 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \ 'isoprompt.cfg', 'options.cfg', \ 'prompt.cfg', 'vesamenu.cfg', 'grml.png', '*.c32': glob_and_copy(iso_mount + source_dir + expr, syslinux_target) for filename in glob.glob1(syslinux_target, "*.c32"): copy_if_exist(os.path.join(SYSLINUX_LIBS, filename), syslinux_target) # copy the addons_*.cfg file to the new syslinux directory glob_and_copy(iso_mount + source_dir + 'addon*.cfg', syslinux_target) search_and_copy('hidden.cfg', iso_mount + source_dir, 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 copy_if_exist(GRML2USB_BASE + "/grub/grml.png", grub_target + 'grml.png') # font file for graphical bootsplash in grub copy_if_exist('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2') # always copy grub content as it might be useful glob_and_copy(iso_mount + '/boot/grub/*.mod', grub_target) glob_and_copy(iso_mount + '/boot/grub/*.img', grub_target) glob_and_copy(iso_mount + '/boot/grub/stage*', 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""" global GRML_DEFAULT GRML_DEFAULT = GRML_DEFAULT or 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, grml_flavour) 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 get_flavour(flavour_str): """Returns the flavour of a grml version string """ return re.match(r'[\w-]*', flavour_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): """Adjust bootoptions for a grub file @filename: filename to modify """ 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.cfg)") 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 get_bootoptions(grml_flavour): """Returns bootoptions for specific flavour @grml_flavour: name of the grml_flavour """ # do NOT write "None" in kernel cmdline if options.bootoptions is None: bootopt = "" else: bootopt = options.bootoptions bootopt = bootopt.replace("%flavour", grml_flavour) return bootopt 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/' 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?)") bootopt = get_bootoptions(grml_flavour) # 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): """Write entry into filename if entry is not already in the file @filanme: name of the file @entry: data to write to the file """ data = open(filename, "a+") for line in data: if line == entry: break else: data.write(entry) data.close() def get_flavour_filename(flavour): """Generate a iso9960 save filename out of the specified flavour @flavour: grml flavour """ return flavour.replace('-', '_') def adjust_syslinux_bootoptions(src, flavour): """Adjust existing bootoptions of specified syslinux config to grml2usb specific ones, e.g. change the location of the kernel... @src: config file to alter @flavour: grml 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_/-]+") bootopt = get_bootoptions(flavour) 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)) 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): """Adjust the specified labels in the syslinux config file src with specified 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): """Add includes for a specific grml_flavour to the specified filename @filename: syslinux config file @grml_flavour: grml flavour to add """ 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): """Replace the standarf filenames with the new ones @grml_flavour: grml-flavour strin @target: directory where the files are located @filenames: list of filenames to alter """ 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): """Remove the default entry from specified syslinux file @filename: syslinux config file """ 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""" 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' # install main configuration only *once*, no matter how many ISOs we have: 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_file = "%s/%s_hidden.cfg" % (syslinux_target, flavour_filename) 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_hidden.cfg\n' % flavour_filename 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 install(image, device): """Install a grml image to the specified device @image: directory or is file @device: partition or directory to install the device """ iso_mountpoint = image remove_image_mountpoint = False if os.path.isdir(image): logging.info("Using %s as install base", image) else: logging.info("Using ISO %s", image) iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb") register_tmpfile(iso_mountpoint) remove_image_mountpoint = True try: mount(image, iso_mountpoint, ["-o", "loop", "-t", "iso9660"]) except CriticalException, error: logging.critical("Fatal: %s", error) sys.exit(1) try: install_grml(iso_mountpoint, device) finally: if remove_image_mountpoint: try: remove_mountpoint(iso_mountpoint) except CriticalException, error: logging.critical("Fatal: %s", error) cleanup() 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) @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.") remove_device_mountpoint = False 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) raise try: grml_flavours = identify_grml_flavour(mountpoint) for flavour in set(grml_flavours): if not flavour: logging.warning("No valid flavour found, please check your iso") logging.info("Identified grml flavour \"%s\".", flavour) install_iso_files(flavour, mountpoint, device, device_mountpoint) GRML_FLAVOURS.add(flavour) finally: if remove_device_mountpoint: remove_mountpoint(device_mountpoint) def remove_mountpoint(mountpoint): """remove a registred mountpoint """ try: unmount(mountpoint, "") if os.path.isdir(mountpoint): os.rmdir(mountpoint) unregister_tmpfile(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) if options.force: print "Forcing mkfs.fat16 on %s as requested via option --force." % device 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") else: sys.exit(1) try: mkfs_fat16(device) except CriticalException, error: logging.critical("Execution failed: %s", error) sys.exit(1) # check for vfat filesystem if device is not None and not os.path.isdir(device) and options.syslinux: try: 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 options.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 check_options(opts): """Check compability of provided user opts @opts option dict from OptionParser """ if opts.grubmbr and not opts.grub: logging.critical("Error: --grub-mbr requires --grub option.") sys.exit(1) def check_programs(): """check if all needed programs are installed""" if options.grub: if not which("grub-install"): logging.critical("Fatal: grub-install not available (please install the " + "grub package or drop the --grub option)") sys.exit(1) if options.syslinux: if not which("syslinux"): logging.critical("Fatal: syslinux not available (please install the " + "syslinux package or use the --grub option)") sys.exit(1) if not which("rsync"): logging.critical("Fatal: rsync not available, can not continue - sorry.") sys.exit(1) def main(): """Main function [make pylint happy :)]""" 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() check_options(options) logging.info("Executing grml2usb version %s", PROG_VERSION) if options.dryrun: logging.info("Running in simulation mode as requested via option dry-run.") check_programs() # 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) elif os.path.exists(device): logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)") 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: install(iso, device) # install mbr if not options.skipmbr and not os.path.isdir(device): 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