2 # -*- coding: utf-8 -*-
7 This script installs a grml system (either a running system or ISO[s]) to a USB device
9 :copyright: (c) 2009 by Michael Prokop <mika@grml.org>
10 :license: GPL v2 or any later version
11 :bugreports: http://grml.org/bugs/
15 from __future__ import with_statement
16 from optparse import OptionParser
17 from inspect import isroutine, isclass
18 import datetime, logging, os, re, subprocess, sys, tempfile, time
21 PROG_VERSION = "0.9.2(pre2)"
22 MOUNTED = set() # register mountpoints
23 TMPFILES = set() # register tmpfiles
24 DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
27 USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/sdX#>\n\
29 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
30 Make sure you have at least one grml ISO or a running grml system (/live/image),\n\
31 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
32 and root access. Further information can be found in: man grml2usb"
34 # pylint: disable-msg=C0103
35 parser = OptionParser(usage=USAGE)
36 parser.add_option("--bootoptions", dest="bootoptions",
37 action="store", type="string",
38 help="use specified bootoptions as default")
39 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
40 help="do not copy files but just install a bootloader")
41 parser.add_option("--copy-only", dest="copyonly", action="store_true",
42 help="copy files only but do not install bootloader")
43 parser.add_option("--dry-run", dest="dryrun", action="store_true",
44 help="avoid executing commands")
45 parser.add_option("--fat16", dest="fat16", action="store_true",
46 help="format specified partition with FAT16")
47 parser.add_option("--force", dest="force", action="store_true",
48 help="force any actions requiring manual interaction")
49 parser.add_option("--grub", dest="grub", action="store_true",
50 help="install grub bootloader instead of syslinux")
51 parser.add_option("--initrd", dest="initrd", action="store", type="string",
52 help="install specified initrd instead of the default [TODO]")
53 parser.add_option("--kernel", dest="kernel", action="store", type="string",
54 help="install specified kernel instead of the default [TODO]")
55 parser.add_option("--lilo", dest="lilo", action="store", type="string",
56 help="lilo executable to be used for installing MBR")
57 parser.add_option("--mbr-manager", dest="mbrmgr", action="store_true",
58 help="enable boot manager menu in MBR")
59 parser.add_option("--quiet", dest="quiet", action="store_true",
60 help="do not output anything but just errors on console")
61 parser.add_option("--skip-addons", dest="skipaddons", action="store_true",
62 help="do not install /boot/addons/ files")
63 parser.add_option("--skip-mbr", dest="skipmbr", action="store_true",
64 help="do not install a master boot record (MBR) on the device")
65 parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true",
66 help="install syslinux master boot record (MBR) instead of default")
67 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
68 help="install specified squashfs file instead of the default [TODO]")
69 parser.add_option("--uninstall", dest="uninstall", action="store_true",
70 help="remove grml ISO files from specified device [TODO]")
71 parser.add_option("--verbose", dest="verbose", action="store_true",
72 help="enable verbose mode")
73 parser.add_option("-v", "--version", dest="version", action="store_true",
74 help="display version and exit")
75 (options, args) = parser.parse_args()
78 class CriticalException(Exception):
79 """Throw critical exception if the exact error is not known but fatal."
81 @Exception: message"""
86 """Cleanup function to make sure there aren't any mounted devices left behind.
89 logging.info("Cleaning up before exiting...")
90 proc = subprocess.Popen(["sync"])
94 for device in MOUNTED:
96 # ignore: RuntimeError: Set changed size during iteration
98 logging.debug('caught expection RuntimeError, ignoring')
101 def register_tmpfile(path):
108 def unregister_tmpfile(path):
113 TMPFILES.remove(path)
116 def register_mountpoint(target):
123 def unregister_mountpoint(target):
127 if target in MOUNTED:
128 MOUNTED.remove(target)
131 def get_function_name(obj):
132 """Helper function for use in execute() to retrive name of a function
134 @obj: the function object
136 if not (isroutine(obj) or isclass(obj)):
138 return obj.__module__ + '.' + obj.__name__
141 def execute(f, *exec_arguments):
142 """Wrapper for executing a command. Either really executes
143 the command (default) or when using --dry-run commandline option
144 just displays what would be executed."""
145 # usage: execute(subprocess.Popen, (["ls", "-la"]))
146 # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
148 # pylint: disable-msg=W0141
149 logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, exec_arguments))))
151 # pylint: disable-msg=W0142
152 return f(*exec_arguments)
156 """Check whether a given file can be executed
158 @fpath: full path to file
160 return os.path.exists(fpath) and os.access(fpath, os.X_OK)
164 """Check whether a given program is available in PATH
166 @program: name of executable"""
167 fpath = os.path.split(program)[0]
172 for path in os.environ["PATH"].split(os.pathsep):
173 exe_file = os.path.join(path, program)
180 def search_file(filename, search_path='/bin' + os.pathsep + '/usr/bin'):
181 """Given a search path, find file
183 @filename: name of file to search for
184 @search_path: path where searching for the specified filename"""
186 paths = search_path.split(os.pathsep)
187 current_dir = '' # make pylint happy :)
189 # pylint: disable-msg=W0612
190 for current_dir, directories, files in os.walk(path):
191 if os.path.exists(os.path.join(current_dir, filename)):
195 return os.path.abspath(os.path.join(current_dir, filename))
200 def check_uid_root():
201 """Check for root permissions"""
202 if not os.geteuid()==0:
203 sys.exit("Error: please run this script with uid 0 (root).")
206 def mkfs_fat16(device):
207 """Format specified device with VFAT/FAT16 filesystem.
209 @device: partition that should be formated"""
211 # syslinux -d boot/isolinux /dev/sdb1
212 logging.info("Formating partition with fat16 filesystem")
213 logging.debug("mkfs.vfat -F 16 %s" % device)
214 proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
216 if proc.returncode != 0:
217 raise Exception("error executing mkfs.vfat")
220 def generate_main_grub2_config(grml_flavour, install_partition, bootoptions):
221 """Generate grub2 configuration for use via grub.cfg
225 @grml_flavour: name of grml flavour the configuration should be generated for"""
227 local_datestamp = DATESTAMP
230 ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
237 if background_image (hd0, %(install_partition)s)/boot/grub/grml.png ; then
238 set color_normal=black/black
239 set color_highlight=magenta/black
241 set menu_color_normal=cyan/blue
242 set menu_color_highlight=white/blue
245 menuentry "%(grml_flavour)s (default)" {
246 set root=(hd0,%(install_partition)s)
247 linux /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s %(bootoptions)s
248 initrd /boot/release/%(grml_flavour)s/initrd.gz
251 menuentry "Memory test (memtest86+)" {
252 set root=(hd0,%(install_partition)s)
253 linux /boot/addons/memtest
256 menuentry "Grub - all in one image" {
257 set root=(hd0,%(install_partition)s)
258 linux /boot/addons/memdisk
259 initrd /boot/addons/allinone.img
262 menuentry "FreeDOS" {
263 set root=(hd0,%(install_partition)s)
264 linux /boot/addons/memdisk
265 initrd /boot/addons/balder10.imz
268 menuentry "Boot Operating System on first partition of first disk" {
273 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } )
276 def generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootoptions):
277 """Generate grub2 configuration for use via grub.cfg
281 @grml_flavour: name of grml flavour the configuration should be generated for"""
283 local_datestamp = DATESTAMP
286 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
287 menuentry "%(grml_flavour)s" {
288 set root=(hd0,%(install_partition)s)
289 linux /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s
290 initrd /boot/release/%(grml_flavour)s/initrd.gz
293 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
294 menuentry "%(grml_flavour)s2ram" {
295 set root=(hd0,%(install_partition)s)
296 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
297 initrd /boot/release/%(grml_flavour)s/initrd.gz
300 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
301 menuentry "%(grml_flavour)s-debug" {
302 set root=(hd0,%(install_partition)s)
303 linux /boot/release/%(grml_flavour)s/linux26
304 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
307 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
308 menuentry "%(grml_flavour)s-x" {
309 set root=(hd0,%(install_partition)s)
310 linux /boot/release/%(grml_flavour)s/linux26
311 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
314 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
315 menuentry "%(grml_flavour)s-nofb" {
316 set root=(hd0,%(install_partition)s)
317 linux /boot/release/%(grml_flavour)s/linux26
318 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
321 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
322 menuentry "%(grml_flavour)s-failsafe" {
323 set root=(hd0,%(install_partition)s)
324 linux /boot/release/%(grml_flavour)s/linux26
325 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
328 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
329 menuentry "%(grml_flavour)s-forensic" {
330 set root=(hd0,%(install_partition)s)
331 linux /boot/release/%(grml_flavour)s/linux26
332 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
335 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
336 menuentry "%(grml_flavour)s-serial" {
337 set root=(hd0,%(install_partition)s)
338 linux /boot/release/%(grml_flavour)s/linux26
339 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
342 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } )
345 def generate_grub1_config(grml_flavour, install_partition, bootoptions):
346 """Generate grub1 configuration for use via menu.lst
348 @grml_flavour: name of grml flavour the configuration should be generated for"""
350 local_datestamp = DATESTAMP
355 # color red/blue green/black
356 splashimage=/boot/grub/splash.xpm.gz
360 # root=(hd0,%(install_partition)s)
363 title %(grml_flavour)s - Default boot (using 1024x768 framebuffer)
364 kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s
365 initrd /boot/release/%(grml_flavour)s/initrd.gz
367 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } )
370 def generate_isolinux_splash(grml_flavour):
371 """Generate bootsplash for isolinux/syslinux
373 @grml_flavour: name of grml flavour the configuration should be generated for"""
375 # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
377 grml_name = grml_flavour
380 \ f17
\f\18/boot/syslinux/logo.16
382 Some information and boot options available via keys F2 - F10. http://grml.org/
384 """ % {'grml_name': grml_name} )
387 def generate_main_syslinux_config(grml_flavour, bootoptions):
388 """Generate main configuration for use in syslinux.cfg
390 @grml_flavour: name of grml flavour the configuration should be generated for
391 @bootoptions: bootoptions that should be used as a default"""
393 local_datestamp = DATESTAMP
396 ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
397 # use this to control the bootup via a serial port
402 DISPLAY /boot/syslinux/boot.msg
403 F1 /boot/syslinux/boot.msg
412 F10 /boot/syslinux/f10
413 ## end of main configuration
415 ## global configuration
416 # the default option (using %(grml_flavour)s)
418 KERNEL /boot/release/%(grml_flavour)s/linux26
419 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s
423 KERNEL /boot/addons/memtest
428 KERNEL /boot/addons/memdisk
429 APPEND initrd=/boot/addons/allinone.img
434 KERNEL /boot/addons/memdisk
435 APPEND initrd=/boot/addons/balder10.imz
437 ## end of global configuration
438 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
441 def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
442 """Generate flavour specific configuration for use in syslinux.cfg
444 @grml_flavour: name of grml flavour the configuration should be generated for
445 @bootoptions: bootoptions that should be used as a default"""
447 local_datestamp = DATESTAMP
451 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
452 LABEL %(grml_flavour)s
453 KERNEL /boot/release/%(grml_flavour)s/linux26
454 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s
456 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
457 LABEL %(grml_flavour)s2ram
458 KERNEL /boot/release/%(grml_flavour)s/linux26
459 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
461 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
462 LABEL %(grml_flavour)s-debug
463 KERNEL /boot/release/%(grml_flavour)s/linux26
464 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
466 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
467 LABEL %(grml_flavour)s-x
468 KERNEL /boot/release/%(grml_flavour)s/linux26
469 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
471 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
472 LABEL %(grml_flavour)s-nofb
473 KERNEL /boot/release/%(grml_flavour)s/linux26
474 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
476 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
477 LABEL %(grml_flavour)s-failsafe
478 KERNEL /boot/release/%(grml_flavour)s/linux26
479 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
481 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
482 LABEL %(grml_flavour)s-forensic
483 KERNEL /boot/release/%(grml_flavour)s/linux26
484 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
486 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
487 LABEL %(grml_flavour)s-serial
488 KERNEL /boot/release/%(grml_flavour)s/linux26
489 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
490 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
493 def install_grub(device):
494 """Install grub on specified device.
496 @mntpoint: mountpoint of device where grub should install its files to
497 @device: partition where grub should be installed to"""
500 logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device)
502 device_mountpoint = tempfile.mkdtemp()
503 register_tmpfile(device_mountpoint)
505 mount(device, device_mountpoint, "")
506 logging.debug("grub-install --root-directory=%s %s", device_mountpoint, device)
507 proc = subprocess.Popen(["grub-install", "--root-directory=%s" % device_mountpoint, device], stdout=file(os.devnull, "r+"))
509 if proc.returncode != 0:
510 raise Exception("error executing grub-install")
511 except CriticalException, error:
512 logging.critical("Fatal: %s" % error)
517 unmount(device_mountpoint, "")
518 os.rmdir(device_mountpoint)
519 unregister_tmpfile(device_mountpoint)
522 def install_syslinux(device):
523 """Install syslinux on specified device.
525 @device: partition where syslinux should be installed to"""
528 logging.info("Would install syslinux as bootloader on %s", device)
531 # syslinux -d boot/isolinux /dev/sdb1
532 logging.info("Installing syslinux as bootloader")
533 logging.debug("syslinux -d boot/syslinux %s" % device)
534 proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
536 if proc.returncode != 0:
537 raise CriticalException("Error executing syslinux (either try --fat16 or --grub?)")
540 def install_bootloader(device):
541 """Install bootloader on specified device.
543 @device: partition where bootloader should be installed to"""
545 # Install bootloader on the device (/dev/sda),
546 # not on the partition itself (/dev/sda1)?
547 #if partition[-1:].isdigit():
548 # device = re.match(r'(.*?)\d*$', partition).group(1)
556 install_syslinux(device)
557 except CriticalException, error:
558 logging.critical("Fatal: %s" % error)
563 def install_lilo_mbr(lilo, device):
566 # to support -A for extended partitions:
567 logging.info("Activating partitions in MBR via lilo")
568 logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
569 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
571 if proc.returncode != 0:
572 raise Exception("error executing lilo")
574 # activate partition:
575 logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
576 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
578 if proc.returncode != 0:
579 raise Exception("error executing lilo")
582 def install_syslinux_mbr(device):
585 # lilo's mbr is broken, use the one from syslinux instead:
586 if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
587 raise Exception("/usr/lib/syslinux/mbr.bin can not be read")
589 logging.info("Installing syslinux MBR")
590 logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
592 # TODO -> use Popen instead?
593 retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
595 logging.critical("Error copying MBR to device (%s)" % retcode)
596 except OSError, error:
597 logging.critical("Execution failed:", error)
600 def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
601 """Installs an MBR to a device.
603 Retrieve the partition table from "device", install an MBR from
604 the "mbrtemplate" file, set the "partition" (0..3) active, and
605 install the result back to "device".
607 "device" may be the name of a file assumed to be a hard disc
608 (or USB stick) image, or something like "/dev/sdb". "partition"
609 must be a number between 0 and 3, inclusive. "mbrtemplate" must
610 be a valid MBR file of at least 440 (439 if ismirbsdmbr) bytes.
612 If "ismirbsdmbr", the partitions' active flags are not changed.
613 Instead, the MBR's default value is set accordingly.
616 logging.info("Installing default MBR")
618 if not os.path.isfile(mbrtemplate):
619 logging.critical("Error: %s can not be read." % mbrtemplate)
620 raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file?)")
622 if (partition < 0) or (partition > 3):
623 raise ValueError("partition must be between 0 and 3")
630 tmpf = tempfile.NamedTemporaryFile()
632 logging.debug("executing: dd if='%s' of='%s' bs=512 count=1" % (device, tmpf.name))
633 proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"], stderr=file(os.devnull, "r+"))
635 if proc.returncode != 0:
636 raise Exception("error executing dd (first run)")
638 logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc" % (mbrtemplate, tmpf.name, nmbrbytes))
639 proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes, "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
641 if proc.returncode != 0:
642 raise Exception("error executing dd (second run)")
644 mbrcode = tmpf.file.read(512)
645 if len(mbrcode) < 512:
646 raise EOFError("MBR size (%d) < 512" % len(mbrcode))
649 mbrcode = mbrcode[0:439] + chr(partition) + \
650 mbrcode[440:510] + "\x55\xAA"
652 actives = ["\x00", "\x00", "\x00", "\x00"]
653 actives[partition] = "\x80"
654 mbrcode = mbrcode[0:446] + actives[0] + \
655 mbrcode[447:462] + actives[1] + \
656 mbrcode[463:478] + actives[2] + \
657 mbrcode[479:494] + actives[3] + \
658 mbrcode[495:510] + "\x55\xAA"
662 tmpf.file.write(mbrcode)
665 logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc" % (tmpf.name, device))
666 proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
668 if proc.returncode != 0:
669 raise Exception("error executing dd (third run)")
673 def handle_syslinux_mbr(device):
674 """Install syslinux master boot record on given device
676 @device: device where MBR should be installed to"""
678 if not is_writeable(device):
679 raise IOError("device not writeable for user")
681 # try to use system's lilo
685 # otherwise fall back to our static version
686 from platform import architecture
687 if architecture()[0] == '64bit':
688 lilo = '/usr/share/grml2usb/lilo/lilo.static.amd64'
690 lilo = '/usr/share/grml2usb/lilo/lilo.static.i386'
691 # finally prefer a specified lilo executable
696 raise Exception("lilo executable can not be execute")
699 logging.info("Would install MBR running lilo and using syslinux.")
702 install_lilo_mbr(lilo, device)
703 install_syslinux_mbr(device)
706 def is_writeable(device):
707 """Check if the device is writeable for the current user
709 @device: partition where bootloader should be installed to"""
713 #raise Exception("no device for checking write permissions")
715 if not os.path.exists(device):
718 return os.access(device, os.W_OK) and os.access(device, os.R_OK)
721 def mount(source, target, mount_options):
722 """Mount specified source on given target
724 @source: name of device/ISO that should be mounted
725 @target: directory where the ISO should be mounted to
726 @options: mount specific options"""
728 # note: options.dryrun does not work here, as we have to
729 # locate files and identify the grml flavour
731 for x in file('/proc/mounts').readlines():
732 if x.startswith(source):
733 raise CriticalException("Error executing mount: %s already mounted - please unmount before invoking grml2usb" % source)
735 logging.debug("mount %s %s %s" % (mount_options, source, target))
736 proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target])
738 if proc.returncode != 0:
739 raise CriticalException("Error executing mount")
741 logging.debug("register_mountpoint(%s)" % target)
742 register_mountpoint(target)
745 def unmount(target, unmount_options):
746 """Unmount specified target
748 @target: target where something is mounted on and which should be unmounted
749 @options: options for umount command"""
751 # make sure we unmount only already mounted targets
752 target_unmount = False
753 mounts = open('/proc/mounts').readlines()
754 mountstring = re.compile(".*%s.*" % re.escape(target))
756 if re.match(mountstring, line):
757 target_unmount = True
759 if not target_unmount:
760 logging.debug("%s not mounted anymore" % target)
762 logging.debug("umount %s %s" % (list(unmount_options), target))
763 proc = subprocess.Popen(["umount"] + list(unmount_options) + [target])
765 if proc.returncode != 0:
766 raise Exception("Error executing umount")
768 logging.debug("unregister_mountpoint(%s)" % target)
769 unregister_mountpoint(target)
772 def check_for_usbdevice(device):
773 """Check whether the specified device is a removable USB device
775 @device: device name, like /dev/sda1 or /dev/sda
778 usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
779 usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
780 if os.path.isfile(usbdevice):
781 is_usb = open(usbdevice).readline()
788 def check_for_fat(partition):
789 """Check whether specified partition is a valid VFAT/FAT16 filesystem
791 @partition: device name of partition"""
794 udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],
795 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
796 filesystem = udev_info.communicate()[0].rstrip()
798 if udev_info.returncode == 2:
799 raise CriticalException("Failed to read device %s"
800 " (wrong UID/permissions or device not present?)" % partition)
802 if filesystem != "vfat":
803 raise CriticalException("Partition %s does not contain a FAT16 filesystem. (Use --fat16 or run mkfs.vfat %s)" % (partition, partition))
806 raise CriticalException("Sorry, /lib/udev/vol_id not available.")
809 def mkdir(directory):
810 """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
812 # just silently pass as it's just fine it the directory exists
813 if not os.path.isdir(directory):
815 os.makedirs(directory)
816 # pylint: disable-msg=W0704
821 def copy_system_files(grml_flavour, iso_mount, target):
824 squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
826 logging.critical("Fatal: squashfs file not found")
828 squashfs_target = target + '/live/'
829 execute(mkdir, squashfs_target)
830 # use install(1) for now to make sure we can write the files afterwards as normal user as well
831 logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
832 proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
835 filesystem_module = search_file('filesystem.module', iso_mount)
836 if filesystem_module is None:
837 logging.critical("Fatal: filesystem.module not found")
839 logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
840 proc = subprocess.Popen(["install", "--mode=664", filesystem_module,
841 squashfs_target + grml_flavour + '.module'])
844 release_target = target + '/boot/release/' + grml_flavour
845 execute(mkdir, release_target)
847 kernel = search_file('linux26', iso_mount)
849 logging.critical("Fatal kernel not found")
851 logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
852 proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
855 initrd = search_file('initrd.gz', iso_mount)
857 logging.critical("Fatal: initrd not found")
859 logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
860 proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
864 def copy_grml_files(iso_mount, target):
867 grml_target = target + '/grml/'
868 execute(mkdir, grml_target)
870 for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
871 grml_file = search_file(myfile, iso_mount)
872 if grml_file is None:
873 logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
875 logging.debug("cp %s %s" % (grml_file, grml_target + grml_file))
876 proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_target + myfile])
879 grml_web_target = grml_target + '/web/'
880 execute(mkdir, grml_web_target)
882 for myfile in 'index.html', 'style.css':
883 grml_file = search_file(myfile, iso_mount)
884 if grml_file is None:
885 logging.warn("Warning: myfile %s could not be found - can not install it")
887 logging.debug("cp %s %s" % (grml_file, grml_web_target + grml_file))
888 proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_web_target + myfile])
891 grml_webimg_target = grml_web_target + '/images/'
892 execute(mkdir, grml_webimg_target)
894 for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
895 grml_file = search_file(myfile, iso_mount)
896 if grml_file is None:
897 logging.warn("Warning: myfile %s could not be found - can not install it")
899 logging.debug("cp %s %s" % (grml_file, grml_webimg_target + grml_file))
900 proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_webimg_target + myfile])
904 def copy_addons(iso_mount, target):
906 addons = target + '/boot/addons/'
907 execute(mkdir, addons)
909 # grub all-in-one image
910 allinoneimg = search_file('allinone.img', iso_mount)
911 if allinoneimg is None:
912 logging.warn("Warning: allinone.img not found - can not install it")
914 logging.debug("cp %s %s" % (allinoneimg, addons + '/allinone.img'))
915 proc = subprocess.Popen(["install", "--mode=664", allinoneimg, addons + 'allinone.img'])
919 balderimg = search_file('balder10.imz', iso_mount)
920 if balderimg is None:
921 logging.warn("Warning: balder10.imz not found - can not install it")
923 logging.debug("cp %s %s" % (balderimg, addons + '/balder10.imz'))
924 proc = subprocess.Popen(["install", "--mode=664", balderimg, addons + 'balder10.imz'])
928 memdiskimg = search_file('memdisk', iso_mount)
929 if memdiskimg is None:
930 logging.warn("Warning: memdisk not found - can not install it")
932 logging.debug("cp %s %s" % (memdiskimg, addons + '/memdisk'))
933 proc = subprocess.Popen(["install", "--mode=664", memdiskimg, addons + 'memdisk'])
937 memtestimg = search_file('memtest', iso_mount)
938 if memtestimg is None:
939 logging.warn("Warning: memtest not found - can not install it")
941 logging.debug("cp %s %s" % (memtestimg, addons + '/memtest'))
942 proc = subprocess.Popen(["install", "--mode=664", memtestimg, addons + 'memtest'])
946 def copy_bootloader_files(iso_mount, target):
949 syslinux_target = target + '/boot/syslinux/'
950 execute(mkdir, syslinux_target)
952 logo = search_file('logo.16', iso_mount)
953 logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
954 proc = subprocess.Popen(["install", "--mode=664", logo, syslinux_target + 'logo.16'])
957 for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
958 bootsplash = search_file(ffile, iso_mount)
959 logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
960 proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile])
963 grub_target = target + '/boot/grub/'
964 execute(mkdir, grub_target)
966 if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
967 logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
970 logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
971 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz',
972 grub_target + 'splash.xpm.gz'])
975 if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"):
976 logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.")
979 logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito')
980 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito',
981 grub_target + 'stage2_eltorito'])
985 def install_iso_files(grml_flavour, iso_mount, device, target):
986 """Copy files from ISO on given target"""
989 # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
990 # * provide alternative search_file() if file information is stored in a config.ini file?
991 # * catch "install: .. No space left on device" & CO
994 logging.info("Would copy files to %s", iso_mount)
996 elif not options.bootloaderonly:
997 logging.info("Copying files. This might take a while....")
998 copy_system_files(grml_flavour, iso_mount, target)
999 copy_grml_files(iso_mount, target)
1001 if not options.skipaddons:
1002 copy_addons(iso_mount, target)
1004 if not options.copyonly:
1005 copy_bootloader_files(iso_mount, target)
1007 if not options.dryrun:
1008 handle_bootloader_config(grml_flavour, device, target)
1010 # make sure we sync filesystems before returning
1011 proc = subprocess.Popen(["sync"])
1015 def uninstall_files(device):
1016 """Get rid of all grml files on specified device
1018 @device: partition where grml2usb files should be removed from"""
1021 logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
1024 def identify_grml_flavour(mountpath):
1025 """Get name of grml flavour
1027 @mountpath: path where the grml ISO is mounted to
1028 @return: name of grml-flavour"""
1030 version_file = search_file('grml-version', mountpath)
1032 if version_file == "":
1033 logging.critical("Error: could not find grml-version file.")
1037 tmpfile = open(version_file, 'r')
1038 grml_info = tmpfile.readline()
1039 grml_flavour = re.match(r'[\w-]*', grml_info).group()
1043 logging.critical("Unexpected error:", sys.exc_info()[0])
1049 def handle_grub_config(grml_flavour, device, target):
1052 logging.debug("Generating grub configuration")
1053 #with open("...", "w") as f:
1054 #f.write("bla bla bal")
1056 grub_target = target + '/boot/grub/'
1057 # should be present via copy_bootloader_files(), but make sure it exists:
1058 execute(mkdir, grub_target)
1059 # we have to adjust root() inside grub configuration
1060 if device[-1:].isdigit():
1061 install_partition = device[-1:]
1063 # do NOT write "None" in kernel cmdline
1064 if options.bootoptions is None:
1067 bootopt = options.bootoptions
1070 #logging.debug("Creating grub1 configuration file")
1071 #grub_config_file = open(grub_target + 'menu.lst', 'w')
1072 #grub_config_file.write(generate_grub1_config(grml_flavour, install_partition, bootopt))
1073 #grub_config_file.close()
1074 # TODO => generate_main_grub1_config() && generate_flavour_specific_grub1_config()
1077 grub2_cfg = grub_target + 'grub.cfg'
1078 logging.debug("Creating grub2 configuration file")
1080 # install main configuration only *once*, no matter how many ISOs we have:
1081 if os.path.isfile(grub2_cfg):
1082 string = open(grub2_cfg).readline()
1083 main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1084 if not re.match(main_identifier, string):
1085 grub2_config_file = open(grub2_cfg, 'w')
1086 logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1087 grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1088 grub2_config_file.close()
1090 grub2_config_file = open(grub2_cfg, 'w')
1091 grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1092 grub2_config_file.close()
1094 grub_flavour_config = True
1095 if os.path.isfile(grub2_cfg):
1096 string = open(grub2_cfg).readlines()
1097 logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1098 flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1100 if flavour.match(line):
1101 grub_flavour_config = False
1103 if grub_flavour_config:
1104 grub2_config_file = open(grub2_cfg, 'a')
1105 grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootopt))
1106 grub2_config_file.close( )
1109 def handle_syslinux_config(grml_flavour, target):
1113 # do NOT write "None" in kernel cmdline
1114 if options.bootoptions is None:
1117 bootopt = options.bootoptions
1119 logging.info("Generating syslinux configuration")
1120 syslinux_target = target + '/boot/syslinux/'
1121 # should be present via copy_bootloader_files(), but make sure it exits:
1122 execute(mkdir, syslinux_target)
1123 syslinux_cfg = syslinux_target + 'syslinux.cfg'
1125 # install main configuration only *once*, no matter how many ISOs we have:
1126 if os.path.isfile(syslinux_cfg):
1127 string = open(syslinux_cfg).readline()
1128 main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1129 if not re.match(main_identifier, string):
1130 syslinux_config_file = open(syslinux_cfg, 'w')
1131 logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1132 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1133 syslinux_config_file.close()
1135 syslinux_config_file = open(syslinux_cfg, 'w')
1136 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1137 syslinux_config_file.close()
1139 # install flavour specific configuration only *once* as well
1140 # kind of ugly - I'm pretty sure this could be smoother...
1141 syslinux_flavour_config = True
1142 if os.path.isfile(syslinux_cfg):
1143 string = open(syslinux_cfg).readlines()
1144 logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1145 flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1147 if flavour.match(line):
1148 syslinux_flavour_config = False
1150 if syslinux_flavour_config:
1151 syslinux_config_file = open(syslinux_cfg, 'a')
1152 syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
1153 syslinux_config_file.close( )
1155 logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
1156 isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
1157 isolinux_splash.write(generate_isolinux_splash(grml_flavour))
1158 isolinux_splash.close( )
1161 def handle_bootloader_config(grml_flavour, device, target):
1165 handle_grub_config(grml_flavour, device, target)
1167 handle_syslinux_config(grml_flavour, target)
1170 def handle_iso(iso, device):
1171 """Main logic for mounting ISOs and copying files.
1173 @iso: full path to the ISO that should be installed to the specified device
1174 @device: partition where the specified ISO should be installed to"""
1176 logging.info("Using ISO %s" % iso)
1178 if os.path.isdir(iso):
1179 logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
1182 iso_mountpoint = tempfile.mkdtemp()
1183 register_tmpfile(iso_mountpoint)
1184 remove_iso_mountpoint = True
1186 if not os.path.isfile(iso):
1187 logging.critical("Fatal: specified ISO %s could not be read" % iso)
1192 mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1193 except CriticalException, error:
1194 logging.critical("Fatal: %s" % error)
1197 if os.path.isdir(device):
1198 logging.info("Specified target is a directory, not mounting therefor.")
1199 device_mountpoint = device
1200 remove_device_mountpoint = False
1203 device_mountpoint = tempfile.mkdtemp()
1204 register_tmpfile(device_mountpoint)
1205 remove_device_mountpoint = True
1207 mount(device, device_mountpoint, "")
1208 except CriticalException, error:
1209 logging.critical("Fatal: %s" % error)
1214 grml_flavour = identify_grml_flavour(iso_mountpoint)
1215 logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1216 install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1218 logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1221 if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1222 unmount(iso_mountpoint, "")
1223 os.rmdir(iso_mountpoint)
1224 unregister_tmpfile(iso_mountpoint)
1225 if remove_device_mountpoint:
1226 unmount(device_mountpoint, "")
1227 if os.path.isdir(device_mountpoint):
1228 os.rmdir(device_mountpoint)
1229 unregister_tmpfile(device_mountpoint)
1232 def handle_mbr(device):
1236 # if not options.mbr:
1237 # logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
1239 # make sure we install MBR on /dev/sdX and not /dev/sdX#
1241 # make sure we have syslinux available
1243 if not options.skipmbr:
1244 if not which("syslinux") and not options.copyonly and not options.dryrun:
1245 logging.critical('Sorry, syslinux not available. Exiting.')
1246 logging.critical('Please install syslinux or consider using the --grub option.')
1249 if not options.skipmbr:
1250 if device[-1:].isdigit():
1251 mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1252 partition_number = int(device[-1:]) - 1
1255 if options.syslinuxmbr:
1256 handle_syslinux_mbr(mbr_device)
1259 install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True)
1261 install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, True)
1262 except IOError, error:
1263 logging.critical("Execution failed: %s", error)
1265 except Exception, error:
1266 logging.critical("Execution failed: %s", error)
1270 def handle_vfat(device):
1273 # make sure we have mkfs.vfat available
1274 if options.fat16 and not options.force:
1275 if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1276 logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1277 logging.critical('Please make sure to install dosfstools.')
1280 # make sure the user is aware of what he is doing
1281 f = raw_input("Are you sure you want to format the device with a fat16 filesystem? y/N ")
1282 if f == "y" or f == "Y":
1283 logging.info("Note: you can skip this question using the option --force")
1288 # check for vfat filesystem
1289 if device is not None and not os.path.isdir(device):
1291 check_for_fat(device)
1292 except CriticalException, error:
1293 logging.critical("Execution failed: %s", error)
1296 if not check_for_usbdevice(device):
1297 print "Warning: the specified device %s does not look like a removable usb device." % device
1298 f = raw_input("Do you really want to continue? y/N ")
1299 if f == "y" or f == "Y":
1305 def handle_compat_warning(device):
1308 # make sure we can replace old grml2usb script and warn user when using old way of life:
1309 if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1310 print "Warning: the semantics of grml2usb has changed."
1311 print "Instead of using grml2usb /path/to/iso %s you might" % device
1312 print "want to use grml2usb /path/to/iso /dev/... instead."
1313 print "Please check out the grml2usb manpage for details."
1314 f = raw_input("Do you really want to continue? y/N ")
1315 if f == "y" or f == "Y":
1321 def handle_logging():
1325 FORMAT = "%(asctime)-15s %(message)s"
1326 logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1328 FORMAT = "Critical: %(message)s"
1329 logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1331 FORMAT = "Info: %(message)s"
1332 logging.basicConfig(level=logging.INFO, format=FORMAT)
1335 def handle_bootloader(device):
1337 # Install bootloader only if not using the --copy-only option
1338 if options.copyonly:
1339 logging.info("Not installing bootloader and its files as requested via option copyonly.")
1341 install_bootloader(device)
1345 """Main function [make pylint happy :)]"""
1348 print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1352 parser.error("invalid usage")
1357 # make sure we have the appropriate permissions
1361 logging.info("Running in simulation mode as requested via option dry-run.")
1363 # specified arguments
1364 device = args[len(args) - 1]
1365 isos = args[0:len(args) - 1]
1367 if device[-1:].isdigit():
1368 if int(device[-1:]) > 4:
1369 logging.critical("Fatal: installation on partition number >4 not supported. (As the BIOS won't support it.)")
1372 logging.critical("Fatal: installation on raw device not supported. (As the BIOS won't support it.)")
1375 # provide upgrade path
1376 handle_compat_warning(device)
1378 # check for vfat partition
1381 # main operation (like installing files)
1383 handle_iso(iso, device)
1388 handle_bootloader(device)
1390 # finally be politely :)
1391 logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
1394 if __name__ == "__main__":
1397 except KeyboardInterrupt:
1398 logging.info("Received KeyboardInterrupt")
1401 ## END OF FILE #################################################################
1402 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8