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/
16 * install memtest, dos, grub,... to /boot/addons/
17 * copy grml files to /grml/
18 * implement missing options (--grub, --kernel, --initrd, --squashfs, --uninstall)
20 - improve error handling wherever possible :)
21 - get rid of all TODOs in code
22 - use 'with open("...", "w") as f: ... f.write("...")'
23 - simplify functions/code as much as possible (move stuff to further functions) -> audit
24 * validate partition schema/layout: is the partition schema ok and the bootable flag set? (--validate?)
25 * implement logic for storing information about copied files -> register every file in a set()
26 * the last line in bootsplash (boot.msg) should mention all installed grml flavours
27 * graphical version? any volunteers? :)
30 from __future__ import with_statement
31 import os, re, subprocess, sys, tempfile
32 from optparse import OptionParser
33 from os.path import exists, join, abspath
34 from os import pathsep
35 from inspect import isroutine, isclass
40 PROG_VERSION = "0.0.1"
41 skip_mbr = True # hm, can we get rid of that? :)
42 mounted = set() # register mountpoints
43 tmpfiles = set() # register tmpfiles
44 datestamp= time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
47 usage = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/ice>\n\
49 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
50 Make sure you have at least a grml ISO or a running grml system (/live/image),\n\
51 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
54 parser = OptionParser(usage=usage)
55 parser.add_option("--bootoptions", dest="bootoptions",
56 action="store", type="string",
57 help="use specified bootoptions as default")
58 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
59 help="do not copy files but just install a bootloader")
60 parser.add_option("--copy-only", dest="copyonly", action="store_true",
61 help="copy files only but do not install bootloader")
62 parser.add_option("--dry-run", dest="dryrun", action="store_true",
63 help="avoid executing commands")
64 parser.add_option("--fat16", dest="fat16", action="store_true",
65 help="format specified partition with FAT16")
66 parser.add_option("--force", dest="force", action="store_true",
67 help="force any actions requiring manual interaction")
68 parser.add_option("--grub", dest="grub", action="store_true",
69 help="install grub bootloader instead of syslinux [TODO]")
70 parser.add_option("--initrd", dest="initrd", action="store", type="string",
71 help="install specified initrd instead of the default [TODO]")
72 parser.add_option("--kernel", dest="kernel", action="store", type="string",
73 help="install specified kernel instead of the default [TODO]")
74 parser.add_option("--lilo", dest="lilo", action="store", type="string",
75 help="lilo executable to be used for installing MBR")
76 parser.add_option("--mbr", dest="mbr", action="store_true",
77 help="install master boot record (MBR) on the device")
78 parser.add_option("--quiet", dest="quiet", action="store_true",
79 help="do not output anything but just errors on console")
80 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
81 help="install specified squashfs file instead of the default [TODO]")
82 parser.add_option("--uninstall", dest="uninstall", action="store_true",
83 help="remove grml ISO files from specified device [TODO]")
84 parser.add_option("--verbose", dest="verbose", action="store_true",
85 help="enable verbose mode")
86 parser.add_option("-v", "--version", dest="version", action="store_true",
87 help="display version and exit")
88 (options, args) = parser.parse_args()
92 """Cleanup function to make sure there aren't any mounted devices left behind.
95 logging.info("Cleaning up before exiting...")
96 proc = subprocess.Popen(["sync"])
100 for device in mounted:
102 # ignore: RuntimeError: Set changed size during iteration
107 def get_function_name(obj):
108 if not (isroutine(obj) or isclass(obj)):
110 return obj.__module__ + '.' + obj.__name__
113 def execute(f, *args):
114 """Wrapper for executing a command. Either really executes
115 the command (default) or when using --dry-run commandline option
116 just displays what would be executed."""
117 # usage: execute(subprocess.Popen, (["ls", "-la"]))
118 # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
120 logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, args))))
126 """Check whether a given file can be executed
128 @fpath: full path to file
130 return os.path.exists(fpath) and os.access(fpath, os.X_OK)
134 """Check whether a given program is available in PATH
136 @program: name of executable"""
137 fpath, fname = os.path.split(program)
142 for path in os.environ["PATH"].split(os.pathsep):
143 exe_file = os.path.join(path, program)
150 def search_file(filename, search_path='/bin' + pathsep + '/usr/bin'):
151 """Given a search path, find file"""
153 paths = search_path.split(pathsep)
155 for current_dir, directories, files in os.walk(path):
156 if exists(join(current_dir, filename)):
160 return abspath(join(current_dir, filename))
165 def check_uid_root():
166 """Check for root permissions"""
167 if not os.geteuid()==0:
168 sys.exit("Error: please run this script with uid 0 (root).")
171 def mkfs_fat16(device):
172 """Format specified device with VFAT/FAT16 filesystem.
174 @device: partition that should be formated"""
176 # syslinux -d boot/isolinux /dev/sdb1
177 logging.info("Formating partition with fat16 filesystem")
178 logging.debug("mkfs.vfat -F 16 %s" % device)
179 proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
181 if proc.returncode != 0:
182 raise Exception, "error executing mkfs.vfat"
185 def install_syslinux(device):
186 """Install syslinux on specified device.
188 @device: partition where syslinux should be installed to"""
191 logging.info("Would install syslinux as bootloader on %s", device)
194 # syslinux -d boot/isolinux /dev/sdb1
195 logging.info("Installing syslinux as bootloader")
196 logging.debug("syslinux -d boot/syslinux %s" % device)
197 proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
199 if proc.returncode != 0:
200 raise Exception, "error executing syslinux"
203 def generate_grub_config(grml_flavour):
204 """Generate grub configuration for use via menu.lst
206 @grml_flavour: name of grml flavour the configuration should be generated for"""
208 # * what about systems using grub2 without having grub1 available?
214 # color red/blue green/black
215 splashimage=/boot/grub/splash.xpm.gz
220 title %(grml_flavour)s - Default boot (using 1024x768 framebuffer)
221 kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s
222 initrd /boot/release/%(grml_flavour)s/initrd.gz
224 # TODO: extend configuration :)
228 def generate_isolinux_splash(grml_flavour):
229 """Generate bootsplash for isolinux/syslinux
231 @grml_flavour: name of grml flavour the configuration should be generated for"""
233 # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
235 grml_name = grml_flavour
238 \ f17
\f\18/boot/syslinux/logo.16
240 Some information and boot options available via keys F2 - F10. http://grml.org/
245 def generate_main_syslinux_config(grml_flavour, bootoptions):
246 """Generate main configuration for use in syslinux.cfg
248 @grml_flavour: name of grml flavour the configuration should be generated for
249 @bootoptions: bootoptions that should be used as a default"""
251 local_datestamp = datestamp
254 ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
255 # use this to control the bootup via a serial port
260 DISPLAY /boot/syslinux/boot.msg
261 F1 /boot/syslinux/boot.msg
270 F10 /boot/syslinux/f10
271 ## end of main configuration
273 ## global configuration
274 # the default option (using %(grml_flavour)s)
276 KERNEL /boot/release/%(grml_flavour)s/linux26
277 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(bootoptions)s
281 KERNEL /boot/addons/memtest
282 APPEND BOOT_IMAGE=memtest
287 KERNEL /boot/addons/memdisk
288 APPEND initrd=/boot/addons/allinone.img
293 KERNEL /boot/addons/memdisk
294 APPEND initrd=/boot/addons/balder10.imz
296 ## end of global configuration
300 def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
301 """Generate flavour specific configuration for use in syslinux.cfg
303 @grml_flavour: name of grml flavour the configuration should be generated for
304 @bootoptions: bootoptions that should be used as a default"""
306 local_datestamp = datestamp
310 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
311 LABEL %(grml_flavour)s
312 KERNEL /boot/release/%(grml_flavour)s/linux26
313 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(bootoptions)s
315 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
316 LABEL %(grml_flavour)s2ram
317 KERNEL /boot/release/%(grml_flavour)s/linux26
318 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s toram=%(grml_flavour)s %(bootoptions)s
320 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
321 LABEL %(grml_flavour)s-debug
322 KERNEL /boot/release/%(grml_flavour)s/linux26
323 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s debug boot=live initcall_debug%(bootoptions)s
325 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
326 LABEL %(grml_flavour)s-x
327 KERNEL /boot/release/%(grml_flavour)s/linux26
328 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s startx=wm-ng %(bootoptions)s
330 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
331 LABEL %(grml_flavour)s-nofb
332 KERNEL /boot/release/%(grml_flavour)s/linux26
333 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
335 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
336 LABEL %(grml_flavour)s-failsafe
337 KERNEL /boot/release/%(grml_flavour)s/linux26
338 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
340 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
341 LABEL %(grml_flavour)s-forensic
342 KERNEL /boot/release/%(grml_flavour)s/linux26
343 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s nofstab noraid nolvm noautoconfig noswap raid=noautodetect %(bootoptions)s
345 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
346 LABEL %(grml_flavour)s-serial
347 KERNEL /boot/release/%(grml_flavour)s/linux26
348 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
352 def install_grub(device):
353 """Install grub on specified device.
355 @device: partition where grub should be installed to"""
358 logging.info("Would execute grub-install %s now.", device)
360 logging.critical("TODO: sorry - grub-install %s not implemented yet" % device)
363 def install_bootloader(device):
364 """Install bootloader on specified device.
366 @device: partition where bootloader should be installed to"""
368 # Install bootloader on the device (/dev/sda),
369 # not on the partition itself (/dev/sda1)?
370 #if partition[-1:].isdigit():
371 # device = re.match(r'(.*?)\d*$', partition).group(1)
378 install_syslinux(device)
381 def is_writeable(device):
382 """Check if the device is writeable for the current user
384 @device: partition where bootloader should be installed to"""
388 #raise Exception, "no device for checking write permissions"
390 if not os.path.exists(device):
393 return os.access(device, os.W_OK) and os.access(device, os.R_OK)
396 def install_mbr(device):
397 """Install a default master boot record on given device
399 @device: device where MBR should be installed to"""
401 if not is_writeable(device):
402 raise IOError, "device not writeable for user"
407 from platform import architecture
408 if architecture()[0] == '64bit':
409 lilo = '/usr/share/grml2usb/lilo/lilo.static.amd64'
411 lilo = '/usr/share/grml2usb/lilo/lilo.static.i386'
414 raise Exception, "lilo executable can not be execute"
417 logging.info("Would install MBR running lilo and using syslinux.")
420 # to support -A for extended partitions:
421 logging.info("Installing MBR")
422 logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
423 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
425 if proc.returncode != 0:
426 raise Exception, "error executing lilo"
428 # activate partition:
429 logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
430 if not options.dryrun:
431 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
433 if proc.returncode != 0:
434 raise Exception, "error executing lilo"
436 # lilo's mbr is broken, use the one from syslinux instead:
437 if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
438 raise Exception, "/usr/lib/syslinux/mbr.bin can not be read"
440 logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
441 if not options.dryrun:
443 # TODO -> use Popen instead?
444 retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
446 logging.critical("Error copying MBR to device (%s)" % retcode)
447 except OSError, error:
448 logging.critical("Execution failed:", error)
451 def register_tmpfile(path):
458 def unregister_tmpfile(path):
463 tmpfiles.remove(path)
466 def register_mountpoint(target):
473 def unregister_mountpoint(target):
477 if target in mounted:
478 mounted.remove(target)
481 def mount(source, target, options):
482 """Mount specified source on given target
484 @source: name of device/ISO that should be mounted
485 @target: directory where the ISO should be mounted to
486 @options: mount specific options"""
488 # notice: options.dryrun does not work here, as we have to
489 # locate files and identify the grml flavour
490 logging.debug("mount %s %s %s" % (options, source, target))
491 proc = subprocess.Popen(["mount"] + list(options) + [source, target])
493 if proc.returncode != 0:
494 raise Exception, "Error executing mount"
496 logging.debug("register_mountpoint(%s)" % target)
497 register_mountpoint(target)
500 def unmount(target, options):
501 """Unmount specified target
503 @target: target where something is mounted on and which should be unmounted
504 @options: options for umount command"""
506 # make sure we unmount only already mounted targets
507 target_unmount = False
508 mounts = open('/proc/mounts').readlines()
509 mountstring = re.compile(".*%s.*" % re.escape(target))
511 if re.match(mountstring, line):
512 target_unmount = True
514 if not target_unmount:
515 logging.debug("%s not mounted anymore" % target)
517 logging.debug("umount %s %s" % (list(options), target))
518 proc = subprocess.Popen(["umount"] + list(options) + [target])
520 if proc.returncode != 0:
521 raise Exception, "Error executing umount"
523 logging.debug("unregister_mountpoint(%s)" % target)
524 unregister_mountpoint(target)
527 def check_for_usbdevice(device):
528 """Check whether the specified device is a removable USB device
530 @device: device name, like /dev/sda1 or /dev/sda
533 usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
534 usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
535 if os.path.isfile(usbdevice):
536 is_usb = open(usbdevice).readline()
543 def check_for_fat(partition):
544 """Check whether specified partition is a valid VFAT/FAT16 filesystem
546 @partition: device name of partition"""
549 udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
550 filesystem = udev_info.communicate()[0].rstrip()
552 if udev_info.returncode == 2:
553 raise Exception, "Failed to read device %s - wrong UID / permissions?" % partition
555 if filesystem != "vfat":
556 raise Exception, "Device %s does not contain a FAT16 partition." % partition
559 raise Exception, "Sorry, /lib/udev/vol_id not available."
562 def mkdir(directory):
563 """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
565 if not os.path.isdir(directory):
567 os.makedirs(directory)
569 # just silently pass as it's just fine it the directory exists
573 def copy_grml_files(grml_flavour, iso_mount, target):
574 """Copy files from ISO on given target"""
577 # * provide alternative search_file() if file information is stored in a config.ini file?
578 # * catch "install: .. No space left on device" & CO
579 # * abstract copy logic to make the code shorter and get rid of spaghettis ;)
582 logging.info("Would copy files to %s", iso_mount)
583 elif not options.bootloaderonly:
584 logging.info("Copying files. This might take a while....")
586 squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
587 squashfs_target = target + '/live/'
588 execute(mkdir, squashfs_target)
590 # use install(1) for now to make sure we can write the files afterwards as normal user as well
591 logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
592 proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
595 filesystem_module = search_file('filesystem.module', iso_mount)
596 logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
597 proc = subprocess.Popen(["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
600 release_target = target + '/boot/release/' + grml_flavour
601 execute(mkdir, release_target)
603 kernel = search_file('linux26', iso_mount)
604 logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
605 proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
608 initrd = search_file('initrd.gz', iso_mount)
609 logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
610 proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
613 if not options.copyonly:
614 syslinux_target = target + '/boot/syslinux/'
615 execute(mkdir, syslinux_target)
617 logo = search_file('logo.16', iso_mount)
618 logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
619 proc = subprocess.Popen(["install", "--mode=664", logo, syslinux_target + 'logo.16'])
622 for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
623 bootsplash = search_file(ffile, iso_mount)
624 logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
625 proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile])
628 grub_target = target + '/boot/grub/'
629 execute(mkdir, grub_target)
631 if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
632 logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
635 logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
636 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz'])
639 if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"):
640 logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.")
643 logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito')
644 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito'])
647 if not options.dryrun:
648 logging.debug("Generating grub configuration")
649 #with open("...", "w") as f:
650 #f.write("bla bla bal")
651 grub_config_file = open(grub_target + 'menu.lst', 'w')
652 grub_config_file.write(generate_grub_config(grml_flavour))
653 grub_config_file.close()
655 logging.info("Generating syslinux configuration")
656 syslinux_cfg = syslinux_target + 'syslinux.cfg'
658 # install main configuration only *once*, no matter how many ISOs we have:
659 if os.path.isfile(syslinux_cfg):
660 string = open(syslinux_cfg).readline()
661 main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(datestamp)))
662 if not re.match(main_identifier, string):
663 syslinux_config_file = open(syslinux_cfg, 'w')
664 logging.info("Notice: grml flavour %s is being installed as the default booting system." % grml_flavour)
665 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
666 syslinux_config_file.close()
668 syslinux_config_file = open(syslinux_cfg, 'w')
669 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
670 syslinux_config_file.close()
672 # install flavour specific configuration only *once* as well
673 # kind of ugly - I'm pretty sure this could be smoother...
674 flavour_config = True
675 if os.path.isfile(syslinux_cfg):
676 string = open(syslinux_cfg).readlines()
677 logging.info("Notice: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
678 flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(datestamp))))
680 if flavour.match(line):
681 flavour_config = False
685 syslinux_config_file = open(syslinux_cfg, 'a')
686 syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, options.bootoptions))
687 syslinux_config_file.close( )
689 logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
690 isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
691 isolinux_splash.write(generate_isolinux_splash(grml_flavour))
692 isolinux_splash.close( )
695 # make sure we sync filesystems before returning
696 proc = subprocess.Popen(["sync"])
700 def uninstall_files(device):
701 """Get rid of all grml files on specified device
703 @device: partition where grml2usb files should be removed from"""
706 logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
709 def identify_grml_flavour(mountpath):
710 """Get name of grml flavour
712 @mountpath: path where the grml ISO is mounted to
713 @return: name of grml-flavour"""
715 version_file = search_file('grml-version', mountpath)
717 if version_file == "":
718 logging.critical("Error: could not find grml-version file.")
722 tmpfile = open(version_file, 'r')
723 grml_info = tmpfile.readline()
724 grml_flavour = re.match(r'[\w-]*', grml_info).group()
728 logging.critical("Unexpected error:", sys.exc_info()[0])
734 def handle_iso(iso, device):
735 """Main logic for mounting ISOs and copying files.
737 @iso: full path to the ISO that should be installed to the specified device
738 @device: partition where the specified ISO should be installed to"""
740 logging.info("Using ISO %s" % iso)
742 if os.path.isdir(iso):
743 logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
746 iso_mountpoint = tempfile.mkdtemp()
747 register_tmpfile(iso_mountpoint)
748 remove_iso_mountpoint = True
750 if not os.path.isfile(iso):
751 logging.critical("Fatal: specified ISO %s could not be read" % iso)
755 mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
757 if os.path.isdir(device):
758 logging.info("Specified target is a directory, not mounting therefore.")
759 device_mountpoint = device
760 remove_device_mountpoint = False
764 device_mountpoint = tempfile.mkdtemp()
765 register_tmpfile(device_mountpoint)
766 remove_device_mountpoint = True
768 mount(device, device_mountpoint, "")
769 except Exception, error:
770 logging.critical("Fatal: %s" % error)
774 grml_flavour = identify_grml_flavour(iso_mountpoint)
775 logging.info("Identified grml flavour \"%s\"." % grml_flavour)
776 copy_grml_files(grml_flavour, iso_mountpoint, device_mountpoint)
778 logging.critical("Fatal: a critical error happend during execution, giving up")
781 if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
782 unmount(iso_mountpoint, "")
784 os.rmdir(iso_mountpoint)
785 unregister_tmpfile(iso_mountpoint)
787 if remove_device_mountpoint:
788 unmount(device_mountpoint, "")
790 if os.path.isdir(device_mountpoint):
791 os.rmdir(device_mountpoint)
792 unregister_tmpfile(device_mountpoint)
794 # grml_flavour_short = grml_flavour.replace('-','')
795 # logging.debug("grml_flavour_short = %s" % grml_flavour_short)
799 """Main function [make pylint happy :)]"""
802 print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
806 parser.error("invalid usage")
810 FORMAT = "%(asctime)-15s %(message)s"
811 logging.basicConfig(level=logging.DEBUG, format=FORMAT)
813 FORMAT = "Critial: %(message)s"
814 logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
816 FORMAT = "Info: %(message)s"
817 logging.basicConfig(level=logging.INFO, format=FORMAT)
820 logging.info("Running in simulate mode as requested via option dry-run.")
824 # specified arguments
825 device = args[len(args) - 1]
826 isos = args[0:len(args) - 1]
828 # make sure we can replace old grml2usb script and warn user when using old way of life:
829 if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
830 print "Warning: the semantics of grml2usb has changed."
831 print "Instead of using grml2usb /path/to/iso %s you might" % device
832 print "want to use grml2usb /path/to/iso /dev/... instead."
833 print "Please check out the grml2usb manpage for details."
834 f = raw_input("Do you really want to continue? y/N ")
835 if f == "y" or f == "Y":
840 # make sure we have syslinux available
842 if not which("syslinux") and not options.copyonly and not options.dryrun:
843 logging.critical('Sorry, syslinux not available. Exiting.')
844 logging.critical('Please install syslinux or consider using the --grub option.')
847 # make sure we have mkfs.vfat available
848 if options.fat16 and not options.force:
849 if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
850 logging.critical('Sorry, mkfs.vfat not available. Exiting.')
851 logging.critical('Please make sure to install dosfstools.')
854 # make sure the user is aware of what he is doing
855 f = raw_input("Are you sure you want to format the device with a fat16 filesystem? y/N ")
856 if f == "y" or f == "Y":
857 logging.info("Note: you can skip this question using the option --force")
862 # check for vfat filesystem
863 if device is not None and not os.path.isdir(device):
865 check_for_fat(device)
866 except Exception, error:
867 logging.critical("Execution failed: %s", error)
870 if not check_for_usbdevice(device):
871 print "Warning: the specified device %s does not look like a removable usb device." % device
872 f = raw_input("Do you really want to continue? y/N ")
873 if f == "y" or f == "Y":
882 # main operation (like installing files)
884 handle_iso(iso, device)
887 if not options.mbr or skip_mbr:
888 logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
890 # make sure we install MBR on /dev/sdX and not /dev/sdX#
891 if device[-1:].isdigit():
892 mbr_device = re.match(r'(.*?)\d*$', device).group(1)
895 install_mbr(mbr_device)
896 except IOError, error:
897 logging.critical("Execution failed: %s", error)
899 except Exception, error:
900 logging.critical("Execution failed: %s", error)
903 # Install bootloader only if not using the --copy-only option
905 logging.info("Not installing bootloader and its files as requested via option copyonly.")
907 install_bootloader(device)
909 # finally be politely :)
910 logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
913 if __name__ == "__main__":
916 except KeyboardInterrupt:
917 logging.info("Received KeyboardInterrupt")
920 ## END OF FILE #################################################################
921 # vim:foldmethod=marker expandtab ai ft=python tw=120 fileencoding=utf-8