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 lilo = '/usr/share/grml2usb/lilo/lilo.static'
410 raise Exception, "lilo executable can not be execute"
413 logging.info("Would install MBR running lilo and using syslinux.")
416 # to support -A for extended partitions:
417 logging.info("Installing MBR")
418 logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
419 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
421 if proc.returncode != 0:
422 raise Exception, "error executing lilo"
424 # activate partition:
425 logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
426 if not options.dryrun:
427 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
429 if proc.returncode != 0:
430 raise Exception, "error executing lilo"
432 # lilo's mbr is broken, use the one from syslinux instead:
433 if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
434 raise Exception, "/usr/lib/syslinux/mbr.bin can not be read"
436 logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
437 if not options.dryrun:
439 # TODO -> use Popen instead?
440 retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
442 logging.critical("Error copying MBR to device (%s)" % retcode)
443 except OSError, error:
444 logging.critical("Execution failed:", error)
447 def register_tmpfile(path):
454 def unregister_tmpfile(path):
459 tmpfiles.remove(path)
462 def register_mountpoint(target):
469 def unregister_mountpoint(target):
473 if target in mounted:
474 mounted.remove(target)
477 def mount(source, target, options):
478 """Mount specified source on given target
480 @source: name of device/ISO that should be mounted
481 @target: directory where the ISO should be mounted to
482 @options: mount specific options"""
484 # notice: options.dryrun does not work here, as we have to
485 # locate files and identify the grml flavour
486 logging.debug("mount %s %s %s" % (options, source, target))
487 proc = subprocess.Popen(["mount"] + list(options) + [source, target])
489 if proc.returncode != 0:
490 raise Exception, "Error executing mount"
492 logging.debug("register_mountpoint(%s)" % target)
493 register_mountpoint(target)
496 def unmount(target, options):
497 """Unmount specified target
499 @target: target where something is mounted on and which should be unmounted
500 @options: options for umount command"""
502 # make sure we unmount only already mounted targets
503 target_unmount = False
504 mounts = open('/proc/mounts').readlines()
505 mountstring = re.compile(".*%s.*" % re.escape(target))
507 if re.match(mountstring, line):
508 target_unmount = True
510 if not target_unmount:
511 logging.debug("%s not mounted anymore" % target)
513 logging.debug("umount %s %s" % (list(options), target))
514 proc = subprocess.Popen(["umount"] + list(options) + [target])
516 if proc.returncode != 0:
517 raise Exception, "Error executing umount"
519 logging.debug("unregister_mountpoint(%s)" % target)
520 unregister_mountpoint(target)
523 def check_for_usbdevice(device):
524 """Check whether the specified device is a removable USB device
526 @device: device name, like /dev/sda1 or /dev/sda
529 usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
530 usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
531 if os.path.isfile(usbdevice):
532 is_usb = open(usbdevice).readline()
539 def check_for_fat(partition):
540 """Check whether specified partition is a valid VFAT/FAT16 filesystem
542 @partition: device name of partition"""
545 udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
546 filesystem = udev_info.communicate()[0].rstrip()
548 if udev_info.returncode == 2:
549 raise Exception, "Failed to read device %s - wrong UID / permissions?" % partition
551 if filesystem != "vfat":
552 raise Exception, "Device %s does not contain a FAT16 partition." % partition
555 raise Exception, "Sorry, /lib/udev/vol_id not available."
558 def mkdir(directory):
559 """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
561 if not os.path.isdir(directory):
563 os.makedirs(directory)
565 # just silently pass as it's just fine it the directory exists
569 def copy_grml_files(grml_flavour, iso_mount, target):
570 """Copy files from ISO on given target"""
573 # * provide alternative search_file() if file information is stored in a config.ini file?
574 # * catch "install: .. No space left on device" & CO
575 # * abstract copy logic to make the code shorter and get rid of spaghettis ;)
578 logging.info("Would copy files to %s", iso_mount)
579 elif not options.bootloaderonly:
580 logging.info("Copying files. This might take a while....")
582 squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
583 squashfs_target = target + '/live/'
584 execute(mkdir, squashfs_target)
586 # use install(1) for now to make sure we can write the files afterwards as normal user as well
587 logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
588 proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
591 filesystem_module = search_file('filesystem.module', iso_mount)
592 logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
593 proc = subprocess.Popen(["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
596 release_target = target + '/boot/release/' + grml_flavour
597 execute(mkdir, release_target)
599 kernel = search_file('linux26', iso_mount)
600 logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
601 proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
604 initrd = search_file('initrd.gz', iso_mount)
605 logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
606 proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
609 if not options.copyonly:
610 syslinux_target = target + '/boot/syslinux/'
611 execute(mkdir, syslinux_target)
613 logo = search_file('logo.16', iso_mount)
614 logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
615 proc = subprocess.Popen(["install", "--mode=664", logo, syslinux_target + 'logo.16'])
618 for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
619 bootsplash = search_file(ffile, iso_mount)
620 logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
621 proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile])
624 grub_target = target + '/boot/grub/'
625 execute(mkdir, grub_target)
627 if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
628 logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
631 logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
632 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz'])
635 if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"):
636 logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.")
639 logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito')
640 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito'])
643 if not options.dryrun:
644 logging.debug("Generating grub configuration")
645 #with open("...", "w") as f:
646 #f.write("bla bla bal")
647 grub_config_file = open(grub_target + 'menu.lst', 'w')
648 grub_config_file.write(generate_grub_config(grml_flavour))
649 grub_config_file.close()
651 logging.info("Generating syslinux configuration")
652 syslinux_cfg = syslinux_target + 'syslinux.cfg'
654 # install main configuration only *once*, no matter how many ISOs we have:
655 if os.path.isfile(syslinux_cfg):
656 string = open(syslinux_cfg).readline()
657 main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(datestamp)))
658 if not re.match(main_identifier, string):
659 syslinux_config_file = open(syslinux_cfg, 'w')
660 logging.info("Notice: grml flavour %s is being installed as the default booting system." % grml_flavour)
661 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
662 syslinux_config_file.close()
664 syslinux_config_file = open(syslinux_cfg, 'w')
665 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
666 syslinux_config_file.close()
668 # install flavour specific configuration only *once* as well
669 # kind of ugly - I'm pretty sure this could be smoother...
670 flavour_config = True
671 if os.path.isfile(syslinux_cfg):
672 string = open(syslinux_cfg).readlines()
673 logging.info("Notice: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
674 flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(datestamp))))
676 if flavour.match(line):
677 flavour_config = False
681 syslinux_config_file = open(syslinux_cfg, 'a')
682 syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, options.bootoptions))
683 syslinux_config_file.close( )
685 logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
686 isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
687 isolinux_splash.write(generate_isolinux_splash(grml_flavour))
688 isolinux_splash.close( )
691 # make sure we sync filesystems before returning
692 proc = subprocess.Popen(["sync"])
696 def uninstall_files(device):
697 """Get rid of all grml files on specified device
699 @device: partition where grml2usb files should be removed from"""
702 logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
705 def identify_grml_flavour(mountpath):
706 """Get name of grml flavour
708 @mountpath: path where the grml ISO is mounted to
709 @return: name of grml-flavour"""
711 version_file = search_file('grml-version', mountpath)
713 if version_file == "":
714 logging.critical("Error: could not find grml-version file.")
718 tmpfile = open(version_file, 'r')
719 grml_info = tmpfile.readline()
720 grml_flavour = re.match(r'[\w-]*', grml_info).group()
724 logging.critical("Unexpected error:", sys.exc_info()[0])
730 def handle_iso(iso, device):
731 """Main logic for mounting ISOs and copying files.
733 @iso: full path to the ISO that should be installed to the specified device
734 @device: partition where the specified ISO should be installed to"""
736 logging.info("Using ISO %s" % iso)
738 if os.path.isdir(iso):
739 logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
742 iso_mountpoint = tempfile.mkdtemp()
743 register_tmpfile(iso_mountpoint)
744 remove_iso_mountpoint = True
746 if not os.path.isfile(iso):
747 logging.critical("Fatal: specified ISO %s could not be read" % iso)
751 mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
753 if os.path.isdir(device):
754 logging.info("Specified target is a directory, not mounting therefore.")
755 device_mountpoint = device
756 remove_device_mountpoint = False
760 device_mountpoint = tempfile.mkdtemp()
761 register_tmpfile(device_mountpoint)
762 remove_device_mountpoint = True
764 mount(device, device_mountpoint, "")
765 except Exception, error:
766 logging.critical("Fatal: %s" % error)
770 grml_flavour = identify_grml_flavour(iso_mountpoint)
771 logging.info("Identified grml flavour \"%s\"." % grml_flavour)
772 copy_grml_files(grml_flavour, iso_mountpoint, device_mountpoint)
774 logging.critical("Fatal: a critical error happend during execution, giving up")
777 if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
778 unmount(iso_mountpoint, "")
780 os.rmdir(iso_mountpoint)
781 unregister_tmpfile(iso_mountpoint)
783 if remove_device_mountpoint:
784 unmount(device_mountpoint, "")
786 if os.path.isdir(device_mountpoint):
787 os.rmdir(device_mountpoint)
788 unregister_tmpfile(device_mountpoint)
790 # grml_flavour_short = grml_flavour.replace('-','')
791 # logging.debug("grml_flavour_short = %s" % grml_flavour_short)
795 """Main function [make pylint happy :)]"""
798 print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
802 parser.error("invalid usage")
806 FORMAT = "%(asctime)-15s %(message)s"
807 logging.basicConfig(level=logging.DEBUG, format=FORMAT)
809 FORMAT = "Critial: %(message)s"
810 logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
812 FORMAT = "Info: %(message)s"
813 logging.basicConfig(level=logging.INFO, format=FORMAT)
816 logging.info("Running in simulate mode as requested via option dry-run.")
820 # specified arguments
821 device = args[len(args) - 1]
822 isos = args[0:len(args) - 1]
824 # make sure we can replace old grml2usb script and warn user when using old way of life:
825 if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
826 print "Warning: the semantics of grml2usb has changed."
827 print "Instead of using grml2usb /path/to/iso %s you might" % device
828 print "want to use grml2usb /path/to/iso /dev/... instead."
829 print "Please check out the grml2usb manpage for details."
830 f = raw_input("Do you really want to continue? y/N ")
831 if f == "y" or f == "Y":
836 # make sure we have syslinux available
838 if not which("syslinux") and not options.copyonly and not options.dryrun:
839 logging.critical('Sorry, syslinux not available. Exiting.')
840 logging.critical('Please install syslinux or consider using the --grub option.')
843 # make sure we have mkfs.vfat available
844 if options.fat16 and not options.force:
845 if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
846 logging.critical('Sorry, mkfs.vfat not available. Exiting.')
847 logging.critical('Please make sure to install dosfstools.')
850 # make sure the user is aware of what he is doing
851 f = raw_input("Are you sure you want to format the device with a fat16 filesystem? y/N ")
852 if f == "y" or f == "Y":
853 logging.info("Note: you can skip this question using the option --force")
858 # check for vfat filesystem
859 if device is not None and not os.path.isdir(device):
861 check_for_fat(device)
862 except Exception, error:
863 logging.critical("Execution failed: %s", error)
866 if not check_for_usbdevice(device):
867 print "Warning: the specified device %s does not look like a removable usb device." % device
868 f = raw_input("Do you really want to continue? y/N ")
869 if f == "y" or f == "Y":
878 # main operation (like installing files)
880 handle_iso(iso, device)
883 if not options.mbr or skip_mbr:
884 logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
886 # make sure we install MBR on /dev/sdX and not /dev/sdX#
887 if device[-1:].isdigit():
888 mbr_device = re.match(r'(.*?)\d*$', device).group(1)
891 install_mbr(mbr_device)
892 except IOError, error:
893 logging.critical("Execution failed: %s", error)
895 except Exception, error:
896 logging.critical("Execution failed: %s", error)
899 # Install bootloader only if not using the --copy-only option
901 logging.info("Not installing bootloader and its files as requested via option copyonly.")
903 install_bootloader(device)
905 # finally be politely :)
906 logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
909 if __name__ == "__main__":
912 except KeyboardInterrupt:
913 logging.info("Received KeyboardInterrupt")
916 ## END OF FILE #################################################################
917 # vim:foldmethod=marker expandtab ai ft=python tw=120 fileencoding=utf-8