#!/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 __future__ import with_statement from optparse import OptionParser from inspect import isroutine, isclass import datetime, logging, os, re, subprocess, sys, tempfile, time # global variables PROG_VERSION = "0.9.2(pre2)" MOUNTED = set() # register mountpoints TMPFILES = set() # register tmpfiles DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg # cmdline parsing USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> \n\ \n\ %prog installs a grml ISO to an USB device to be able to boot from it.\n\ Make sure you have at least one grml ISO or a running grml system (/live/image),\n\ syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\ and root access. Further information can be found in: 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="store_true", help="install grub bootloader instead of syslinux") parser.add_option("--initrd", dest="initrd", action="store", type="string", help="install specified initrd instead of the default [TODO]") parser.add_option("--kernel", dest="kernel", action="store", type="string", help="install specified kernel instead of the default [TODO]") parser.add_option("--lilo", dest="lilo", action="store", type="string", help="lilo executable to be used for installing MBR") parser.add_option("--mbr-manager", dest="mbrmgr", action="store_true", help="enable boot manager 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("--skip-addons", dest="skipaddons", action="store_true", help="do not install /boot/addons/ 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("--syslinux-mbr", dest="syslinuxmbr", action="store_true", help="install syslinux master boot record (MBR) instead of default") parser.add_option("--squashfs", dest="squashfs", action="store", type="string", help="install specified squashfs file instead of the default [TODO]") parser.add_option("--uninstall", dest="uninstall", action="store_true", help="remove grml ISO files from specified device [TODO]") parser.add_option("--verbose", dest="verbose", action="store_true", help="enable verbose mode") parser.add_option("-v", "--version", dest="version", action="store_true", help="display version and exit") (options, args) = parser.parse_args() class CriticalException(Exception): """Throw critical exception if the exact error is not known but fatal." @Exception: message""" pass 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 """ TMPFILES.add(path) def unregister_tmpfile(path): """TODO """ if path in TMPFILES: TMPFILES.remove(path) def register_mountpoint(target): """TODO """ MOUNTED.add(target) def unregister_mountpoint(target): """TODO """ if target in MOUNTED: MOUNTED.remove(target) def 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 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 :) for path in paths: # pylint: disable-msg=W0612 for current_dir, directories, files in os.walk(path): if os.path.exists(os.path.join(current_dir, filename)): 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""" # syslinux -d boot/isolinux /dev/sdb1 logging.info("Formating partition with fat16 filesystem") logging.debug("mkfs.vfat -F 16 %s" % device) proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device]) proc.wait() if proc.returncode != 0: raise CriticalException("error executing mkfs.vfat") def generate_main_grub2_config(grml_flavour, install_partition, bootoptions): """Generate grub2 configuration for use via grub.cfg TODO @grml_flavour: name of grml flavour the configuration should be generated for""" local_datestamp = DATESTAMP return("""\ ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s] set default=0 set timeout=5 insmod fat if font (hd0,%(install_partition)s)/boot/grub/ascii.pff ; then insmod png set gfxmode=640x480 insmod gfxterm insmod vbe terminal gfxterm fi if background_image (hd0,%(install_partition)s)/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 root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s %(bootoptions)s initrd /boot/release/%(grml_flavour)s/initrd.gz } menuentry "Memory test (memtest86+)" { set root=(hd0,%(install_partition)s) linux /boot/addons/memtest } menuentry "Grub - all in one image" { set root=(hd0,%(install_partition)s) linux /boot/addons/memdisk initrd /boot/addons/allinone.img } menuentry "FreeDOS" { set root=(hd0,%(install_partition)s) linux /boot/addons/memdisk initrd /boot/addons/balder10.imz } menuentry "Boot OS of first partition on first disk" { set root=(hd0,1) chainloader +1 } """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } ) def generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootoptions): """Generate grub2 configuration for use via grub.cfg TODO @grml_flavour: name of grml flavour the configuration should be generated for""" local_datestamp = DATESTAMP return("""\ ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s initrd /boot/release/%(grml_flavour)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s2ram" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s toram=%(grml_flavour)s %(bootoptions)s initrd /boot/release/%(grml_flavour)s/initrd.gz } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-debug" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s debug boot=live initcall_debug%(bootoptions)s } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-x" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s startx=wm-ng %(bootoptions)s } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-nofb" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=ofonly %(bootoptions)s } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-failsafe" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal lang=us boot=live noautoconfig atapicd noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia maxcpus=1 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm %(bootoptions)s } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-forensic" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s nofstab noraid nolvm noautoconfig noswap raid=noautodetect %(bootoptions)s } ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] menuentry "%(grml_flavour)s-serial" { set root=(hd0,%(install_partition)s) linux /boot/release/%(grml_flavour)s/linux26 initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=vesafb:off console=tty1 console=ttyS0,9600n8 %(bootoptions)s } """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } ) def generate_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("""\ # misc options: timeout 30 # color red/blue green/black splashimage=/boot/grub/splash.xpm.gz foreground = 000000 background = FFCC33 # root=(hd0,%(install_partition)s) # define entries: title %(grml_flavour)s - Default boot (using 1024x768 framebuffer) kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s initrd /boot/release/%(grml_flavour)s/initrd.gz """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, '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(grml_flavour, bootoptions): """Generate main configuration for use in syslinux.cfg @grml_flavour: name of grml flavour the configuration should be generated for @bootoptions: bootoptions that should be used as a default""" local_datestamp = DATESTAMP return("""\ ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s] # use this to control the bootup via a serial port # SERIAL 0 9600 DEFAULT grml TIMEOUT 300 PROMPT 1 DISPLAY /boot/syslinux/boot.msg F1 /boot/syslinux/boot.msg F2 /boot/syslinux/f2 F3 /boot/syslinux/f3 F4 /boot/syslinux/f4 F5 /boot/syslinux/f5 F6 /boot/syslinux/f6 F7 /boot/syslinux/f7 F8 /boot/syslinux/f8 F9 /boot/syslinux/f9 F10 /boot/syslinux/f10 ## end of main configuration ## global configuration # the default option (using %(grml_flavour)s) LABEL grml KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s # memtest LABEL memtest KERNEL /boot/addons/memtest # grub LABEL grub MENU LABEL grub KERNEL /boot/addons/memdisk APPEND initrd=/boot/addons/allinone.img # dos LABEL dos MENU LABEL dos KERNEL /boot/addons/memdisk APPEND initrd=/boot/addons/balder10.imz ## end of global configuration """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} ) def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions): """Generate flavour specific configuration for use in syslinux.cfg @grml_flavour: name of grml flavour the configuration should be generated for @bootoptions: bootoptions that should be used as a default""" local_datestamp = DATESTAMP return("""\ # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s2ram KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s toram=%(grml_flavour)s %(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s-debug KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s debug boot=live initcall_debug%(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s-x KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s startx=wm-ng %(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s-nofb KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=ofonly %(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s-failsafe KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal lang=us boot=live noautoconfig atapicd noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia maxcpus=1 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm %(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s-forensic KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s nofstab noraid nolvm noautoconfig noswap raid=noautodetect %(bootoptions)s # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s] LABEL %(grml_flavour)s-serial KERNEL /boot/release/%(grml_flavour)s/linux26 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=vesafb:off console=tty1 console=ttyS0,9600n8 %(bootoptions)s """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} ) 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() register_tmpfile(device_mountpoint) try: mount(device, device_mountpoint, "") logging.debug("grub-install --root-directory=%s %s", device_mountpoint, device) proc = subprocess.Popen(["grub-install", "--root-directory=%s" % device_mountpoint, device], stdout=file(os.devnull, "r+")) proc.wait() if proc.returncode != 0: raise Exception("error executing grub-install") 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 --grub?)") def install_bootloader(device): """Install bootloader on specified device. @device: partition where bootloader should be installed to""" # Install bootloader on the device (/dev/sda), # not on the partition itself (/dev/sda1)? #if partition[-1:].isdigit(): # device = re.match(r'(.*?)\d*$', partition).group(1) #else: # device = partition if options.grub: install_grub(device) else: try: install_syslinux(device) except CriticalException, error: logging.critical("Fatal: %s" % error) cleanup() sys.exit(1) def install_lilo_mbr(lilo, device): """TODO""" # 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): """TODO""" # 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:", error) def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True): """Installs an MBR to 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". "device" may be the 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 (439 if ismirbsdmbr) bytes. If "ismirbsdmbr", the partitions' active flags are not changed. Instead, the MBR's default value is set accordingly. """ 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 = '/usr/share/grml2usb/lilo/lilo.static.amd64' else: lilo = '/usr/share/grml2usb/lilo/lilo.static.i386' # finally prefer a specified lilo executable if options.lilo: lilo = options.lilo 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 install_lilo_mbr(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) 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") 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(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) usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable') if os.path.isfile(usbdevice): is_usb = open(usbdevice).readline() if is_usb == "1": return 0 else: return 1 def check_for_fat(partition): """Check whether specified partition is a valid VFAT/FAT16 filesystem @partition: device name of partition""" try: udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition], stdout=subprocess.PIPE, stderr=subprocess.PIPE) filesystem = udev_info.communicate()[0].rstrip() if udev_info.returncode == 2: raise CriticalException("Failed to read device %s" " (wrong UID/permissions or device 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, /lib/udev/vol_id not available.") 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 copy_system_files(grml_flavour, iso_mount, target): """TODO""" squashfs = search_file(grml_flavour + '.squashfs', iso_mount) if squashfs is None: logging.critical("Fatal: squashfs file not found") else: squashfs_target = target + '/live/' execute(mkdir, squashfs_target) # use install(1) for now to make sure we can write the files afterwards as normal user as well logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs')) proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"]) proc.wait() filesystem_module = search_file('filesystem.module', iso_mount) if filesystem_module is None: logging.critical("Fatal: filesystem.module not found") else: logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module')) proc = subprocess.Popen(["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module']) proc.wait() release_target = target + '/boot/release/' + grml_flavour execute(mkdir, release_target) kernel = search_file('linux26', iso_mount) if kernel is None: logging.critical("Fatal kernel not found") else: logging.debug("cp %s %s" % (kernel, release_target + '/linux26')) proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26']) proc.wait() initrd = search_file('initrd.gz', iso_mount) if initrd is None: logging.critical("Fatal: initrd not found") else: logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz')) proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz']) proc.wait() def copy_grml_files(iso_mount, target): """TODO""" grml_target = target + '/grml/' execute(mkdir, grml_target) for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt': 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: logging.debug("cp %s %s" % (grml_file, grml_target + grml_file)) proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_target + myfile]) proc.wait() 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: logging.debug("cp %s %s" % (grml_file, grml_web_target + grml_file)) proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_web_target + myfile]) proc.wait() 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: logging.debug("cp %s %s" % (grml_file, grml_webimg_target + grml_file)) proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_webimg_target + myfile]) proc.wait() def copy_addons(iso_mount, target): """TODO""" 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 - can not install it") else: logging.debug("cp %s %s" % (allinoneimg, addons + '/allinone.img')) proc = subprocess.Popen(["install", "--mode=664", allinoneimg, addons + 'allinone.img']) proc.wait() # freedos image balderimg = search_file('balder10.imz', iso_mount) if balderimg is None: logging.warn("Warning: balder10.imz not found - can not install it") else: logging.debug("cp %s %s" % (balderimg, addons + '/balder10.imz')) proc = subprocess.Popen(["install", "--mode=664", balderimg, addons + 'balder10.imz']) proc.wait() # memdisk image memdiskimg = search_file('memdisk', iso_mount) if memdiskimg is None: logging.warn("Warning: memdisk not found - can not install it") else: logging.debug("cp %s %s" % (memdiskimg, addons + '/memdisk')) proc = subprocess.Popen(["install", "--mode=664", memdiskimg, addons + 'memdisk']) proc.wait() # memtest86+ image memtestimg = search_file('memtest', iso_mount) if memtestimg is None: logging.warn("Warning: memtest not found - can not install it") else: logging.debug("cp %s %s" % (memtestimg, addons + '/memtest')) proc = subprocess.Popen(["install", "--mode=664", memtestimg, addons + 'memtest']) proc.wait() def copy_bootloader_files(iso_mount, target): """"TODO""" syslinux_target = target + '/boot/syslinux/' execute(mkdir, syslinux_target) logo = search_file('logo.16', iso_mount) logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16')) proc = subprocess.Popen(["install", "--mode=664", logo, syslinux_target + 'logo.16']) proc.wait() for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10': bootsplash = search_file(ffile, iso_mount) logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile)) proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile]) proc.wait() grub_target = target + '/boot/grub/' execute(mkdir, grub_target) if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"): logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.") raise else: logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz') proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz']) proc.wait() # grml splash in grub if os.path.isfile("/usr/share/grml2usb/grub/grml.png"): logging.debug("cp /usr/share/grml2usb/grub/grml.png to %s" % grub_target + 'grml.png') proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/grml.png', grub_target + 'grml.png']) proc.wait() # font file for graphical bootsplash in grub if os.path.isfile("/usr/share/grub/ascii.pff"): logging.debug("cp /usr/share/grub/ascii.pff to %s" % grub_target + 'ascii.pff') proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grub/ascii.pff', grub_target + 'ascii.pff']) proc.wait() if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"): logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.") raise else: logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito') proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito']) proc.wait() def install_iso_files(grml_flavour, iso_mount, device, target): """Copy files from ISO on given target""" # TODO # * 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 if options.dryrun: logging.info("Would copy files to %s", iso_mount) return 0 elif not options.bootloaderonly: logging.info("Copying files. This might take a while....") copy_system_files(grml_flavour, iso_mount, target) copy_grml_files(iso_mount, target) if not options.skipaddons: 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 logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device) 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 try: tmpfile = open(version_file, 'r') grml_info = tmpfile.readline() grml_flavour = re.match(r'[\w-]*', grml_info).group() except TypeError: raise except: logging.critical("Unexpected error:", sys.exc_info()[0]) raise return grml_flavour def handle_grub_config(grml_flavour, device, target): """ TODO """ logging.debug("Generating grub configuration") #with open("...", "w") as f: #f.write("bla bla bal") grub_target = target + '/boot/grub/' # should be present via copy_bootloader_files(), but make sure it exists: execute(mkdir, grub_target) # we have to adjust root() inside grub configuration if device[-1:].isdigit(): install_partition = device[-1:] # do NOT write "None" in kernel cmdline if options.bootoptions is None: bootopt = "" else: bootopt = options.bootoptions # grub1 config #logging.debug("Creating grub1 configuration file") #grub_config_file = open(grub_target + 'menu.lst', 'w') #grub_config_file.write(generate_grub1_config(grml_flavour, install_partition, bootopt)) #grub_config_file.close() # TODO => generate_main_grub1_config() && generate_flavour_specific_grub1_config() # grub2 config grub2_cfg = grub_target + 'grub.cfg' logging.debug("Creating grub2 configuration file") # install main configuration only *once*, no matter how many ISOs we have: 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') logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour) grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt)) grub2_config_file.close() else: grub2_config_file = open(grub2_cfg, 'w') grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt)) grub2_config_file.close() grub_flavour_config = True if os.path.isfile(grub2_cfg): string = open(grub2_cfg).readlines() logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour)) flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP)))) for line in string: if flavour.match(line): grub_flavour_config = False if grub_flavour_config: grub2_config_file = open(grub2_cfg, 'a') grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootopt)) grub2_config_file.close( ) def handle_syslinux_config(grml_flavour, target): """ TODO """ # do NOT write "None" in kernel cmdline if options.bootoptions is None: bootopt = "" else: bootopt = options.bootoptions logging.info("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: if os.path.isfile(syslinux_cfg): string = open(syslinux_cfg).readline() main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP))) if not re.match(main_identifier, string): syslinux_config_file = open(syslinux_cfg, 'w') logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour) syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt)) syslinux_config_file.close() else: syslinux_config_file = open(syslinux_cfg, 'w') syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt)) syslinux_config_file.close() # install flavour specific configuration only *once* as well # kind of ugly - I'm pretty sure this could be smoother... syslinux_flavour_config = True if os.path.isfile(syslinux_cfg): string = open(syslinux_cfg).readlines() logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour)) flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP)))) for line in string: if flavour.match(line): syslinux_flavour_config = False if syslinux_flavour_config: syslinux_config_file = open(syslinux_cfg, 'a') syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt)) syslinux_config_file.close( ) logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg') isolinux_splash = open(syslinux_target + 'boot.msg', 'w') isolinux_splash.write(generate_isolinux_splash(grml_flavour)) isolinux_splash.close( ) def handle_bootloader_config(grml_flavour, device, target): """TODO""" if options.grub: handle_grub_config(grml_flavour, device, target) else: handle_syslinux_config(grml_flavour, target) def handle_iso(iso, device): """Main logic for mounting ISOs and copying files. @iso: full path to the ISO that should be installed to the specified device @device: partition where the specified ISO should be installed to""" logging.info("Using ISO %s" % iso) if os.path.isdir(iso): logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO sys.exit(1) iso_mountpoint = tempfile.mkdtemp() register_tmpfile(iso_mountpoint) remove_iso_mountpoint = True if not os.path.isfile(iso): logging.critical("Fatal: specified ISO %s could not be read" % iso) cleanup() sys.exit(1) 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, not mounting therefor.") device_mountpoint = device remove_device_mountpoint = False # skip_mbr = True else: device_mountpoint = tempfile.mkdtemp() register_tmpfile(device_mountpoint) remove_device_mountpoint = True try: mount(device, device_mountpoint, "") except CriticalException, error: logging.critical("Fatal: %s" % error) cleanup() sys.exit(1) try: grml_flavour = identify_grml_flavour(iso_mountpoint) logging.info("Identified grml flavour \"%s\"." % grml_flavour) install_iso_files(grml_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: unmount(device_mountpoint, "") if os.path.isdir(device_mountpoint): os.rmdir(device_mountpoint) unregister_tmpfile(device_mountpoint) def handle_mbr(device): """TODO""" # install MBR # if not options.mbr: # logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.") # else: # make sure we install MBR on /dev/sdX and not /dev/sdX# # make sure we have syslinux available # if options.mbr: if not options.skipmbr: if not which("syslinux") and not options.copyonly and not options.dryrun: logging.critical('Sorry, syslinux not available. Exiting.') logging.critical('Please install syslinux or consider using the --grub option.') sys.exit(1) if not options.skipmbr: if device[-1:].isdigit(): mbr_device = re.match(r'(.*?)\d*$', device).group(1) partition_number = int(device[-1:]) - 1 try: if options.syslinuxmbr: handle_syslinux_mbr(mbr_device) else: if options.mbrmgr: install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True) else: install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, True) 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): """TODO""" # make sure we have mkfs.vfat available if options.fat16 and not options.force: if not which("mkfs.vfat") and not options.copyonly and not options.dryrun: logging.critical('Sorry, mkfs.vfat not available. Exiting.') logging.critical('Please make sure to install dosfstools.') sys.exit(1) # 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") 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: check_for_fat(device) except CriticalException, error: logging.critical("Execution failed: %s", error) sys.exit(1) if not check_for_usbdevice(device): print "Warning: the specified device %s does not look like a removable usb device." % device f = raw_input("Do you really want to continue? y/N ") if f == "y" or f == "Y": pass else: sys.exit(1) def handle_compat_warning(device): """TODO""" # 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(): """TODO""" if options.verbose: FORMAT = "%(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 = "Info: %(message)s" logging.basicConfig(level=logging.INFO, format=FORMAT) def handle_bootloader(device): """TODO""" # 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.") else: install_bootloader(device) 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() if options.dryrun: logging.info("Running in simulation mode as requested via option dry-run.") # specified arguments device = args[len(args) - 1] isos = args[0:len(args) - 1] if device[-1:].isdigit(): if int(device[-1:]) > 4: logging.critical("Fatal: installation on partition number >4 not supported. (As the BIOS won't support it.)") sys.exit(1) else: logging.critical("Fatal: installation on raw device not supported. (As the 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: handle_iso(iso, device) # install mbr handle_mbr(device) handle_bootloader(device) # 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