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 * implement missing options (--kernel, --initrd, --uninstall,...)
19 - improve error handling :)
20 - get rid of all TODOs in code :)
21 - use 'with open("...", "w") as f: ... f.write("...")'
22 - simplify functions/code as much as possible -> audit
23 * validate partition schema/layout: is the partition schema ok and the bootable flag set?
24 * implement logic for storing information about copied files -> register every file in a set()
25 * the last line in bootsplash (boot.msg) should mention all installed grml flavours
26 * graphical version? :)
29 from __future__ import with_statement
30 import os, re, subprocess, sys, tempfile
31 from optparse import OptionParser
32 from os.path import exists, join, abspath
33 from os import pathsep
34 from inspect import isroutine, isclass
39 PROG_VERSION = "0.0.1"
40 skip_mbr = True # hm, can we get rid of that? :)
41 mounted = set() # register mountpoints
42 tmpfiles = set() # register tmpfiles
43 datestamp= time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
46 usage = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/ice>\n\
48 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
49 Make sure you have at least a grml ISO or a running grml system (/live/image),\n\
50 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
53 parser = OptionParser(usage=usage)
54 parser.add_option("--bootoptions", dest="bootoptions",
55 action="store", type="string",
56 help="use specified bootoptions as defaut")
57 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
58 help="do not copy files only but just install a bootloader")
59 parser.add_option("--copy-only", dest="copyonly", action="store_true",
60 help="copy files only and do not install bootloader")
61 parser.add_option("--dry-run", dest="dryrun", action="store_true",
62 help="do not actually execute any commands")
63 parser.add_option("--fat16", dest="fat16", action="store_true",
64 help="format specified partition with FAT16")
65 parser.add_option("--force", dest="force", action="store_true",
66 help="force any actions requiring manual interaction")
67 parser.add_option("--grub", dest="grub", action="store_true",
68 help="install grub bootloader instead of syslinux")
69 parser.add_option("--initrd", dest="initrd", action="store", type="string",
70 help="install specified initrd instead of the default")
71 parser.add_option("--kernel", dest="kernel", action="store", type="string",
72 help="install specified kernel instead of the default")
73 parser.add_option("--lilo", dest="lilo", action="store", type="string",
74 help="lilo executable to be used for installing MBR")
75 parser.add_option("--mbr", dest="mbr", action="store_true",
76 help="install master boot record (MBR) on the device")
77 parser.add_option("--quiet", dest="quiet", action="store_true",
78 help="do not output anything than errors on console")
79 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
80 help="install specified squashfs file instead of the default")
81 parser.add_option("--uninstall", dest="uninstall", action="store_true",
82 help="remove grml ISO files")
83 parser.add_option("--verbose", dest="verbose", action="store_true",
84 help="enable verbose mode")
85 parser.add_option("-v", "--version", dest="version", action="store_true",
86 help="display version and exit")
87 (options, args) = parser.parse_args()
91 """Cleanup function to make sure there aren't any mounted devices left behind.
94 logging.info("Cleaning up before exiting...")
95 proc = subprocess.Popen(["sync"])
99 for device in mounted:
101 # ignore: RuntimeError: Set changed size during iteration
106 def get_function_name(obj):
107 if not (isroutine(obj) or isclass(obj)):
109 return obj.__module__ + '.' + obj.__name__
112 def execute(f, *args):
113 """Wrapper for executing a command. Either really executes
114 the command (default) or when using --dry-run commandline option
115 just displays what would be executed."""
116 # usage: execute(subprocess.Popen, (["ls", "-la"]))
117 # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
119 logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, args))))
125 """Check whether a given file can be executed
127 @fpath: full path to file
129 return os.path.exists(fpath) and os.access(fpath, os.X_OK)
133 """Check whether a given program is available in PATH
135 @program: name of executable"""
136 fpath, fname = os.path.split(program)
141 for path in os.environ["PATH"].split(os.pathsep):
142 exe_file = os.path.join(path, program)
149 def search_file(filename, search_path='/bin' + pathsep + '/usr/bin'):
150 """Given a search path, find file"""
152 paths = search_path.split(pathsep)
154 for current_dir, directories, files in os.walk(path):
155 if exists(join(current_dir, filename)):
159 return abspath(join(current_dir, filename))
164 def check_uid_root():
165 """Check for root permissions"""
166 if not os.geteuid()==0:
167 sys.exit("Error: please run this script with uid 0 (root).")
170 def mkfs_fat16(device):
171 """Format specified device with VFAT/FAT16 filesystem.
173 @device: partition that should be formated"""
175 # syslinux -d boot/isolinux /dev/sdb1
176 logging.info("Formating partition with fat16 filesystem")
177 logging.debug("mkfs.vfat -F 16 %s" % device)
178 proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
180 if proc.returncode != 0:
181 raise Exception, "error executing mkfs.vfat"
184 def install_syslinux(device):
185 """Install syslinux on specified device.
187 @device: partition where syslinux should be installed to"""
190 logging.info("Would install syslinux as bootloader on %s", device)
193 # syslinux -d boot/isolinux /dev/sdb1
194 logging.info("Installing syslinux as bootloader")
195 logging.debug("syslinux -d boot/syslinux %s" % device)
196 proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
198 if proc.returncode != 0:
199 raise Exception, "error executing syslinux"
202 def generate_grub_config(grml_flavour):
203 """Generate grub configuration for use via menu.lst
205 @grml_flavour: name of grml flavour the configuration should be generated for"""
207 # * what about systems using grub2 without having grub1 available?
213 # color red/blue green/black
214 splashimage=/boot/grub/splash.xpm.gz
219 title %(grml_flavour)s - Default boot (using 1024x768 framebuffer)
220 kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s
221 initrd /boot/release/%(grml_flavour)s/initrd.gz
223 # TODO: extend configuration :)
227 def generate_isolinux_splash(grml_flavour):
228 """Generate bootsplash for isolinux/syslinux
230 @grml_flavour: name of grml flavour the configuration should be generated for"""
232 # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
234 grml_name = grml_flavour
237 \ f17
\f\18/boot/syslinux/logo.16
239 Some information and boot options available via keys F2 - F10. http://grml.org/
244 def generate_main_syslinux_config(grml_flavour, bootoptions):
245 """Generate main configuration for use in syslinux.cfg
247 @grml_flavour: name of grml flavour the configuration should be generated for
248 @bootoptions: bootoptions that should be used as a default"""
250 local_datestamp = datestamp
253 ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
254 # use this to control the bootup via a serial port
259 DISPLAY /boot/syslinux/boot.msg
260 F1 /boot/syslinux/boot.msg
269 F10 /boot/syslinux/f10
270 ## end of main configuration
272 ## global configuration
273 # the default option (using %(grml_flavour)s)
275 KERNEL /boot/release/%(grml_flavour)s/linux26
276 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(bootoptions)s
280 KERNEL /boot/addons/memtest
281 APPEND BOOT_IMAGE=memtest
286 KERNEL /boot/addons/memdisk
287 APPEND initrd=/boot/addons/allinone.img
292 KERNEL /boot/addons/memdisk
293 APPEND initrd=/boot/addons/balder10.imz
295 ## end of global configuration
299 def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
300 """Generate flavour specific configuration for use in syslinux.cfg
302 @grml_flavour: name of grml flavour the configuration should be generated for
303 @bootoptions: bootoptions that should be used as a default"""
305 local_datestamp = datestamp
309 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
310 LABEL %(grml_flavour)s
311 KERNEL /boot/release/%(grml_flavour)s/linux26
312 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(bootoptions)s
314 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
315 LABEL %(grml_flavour)s2ram
316 KERNEL /boot/release/%(grml_flavour)s/linux26
317 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
319 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
320 LABEL %(grml_flavour)s-debug
321 KERNEL /boot/release/%(grml_flavour)s/linux26
322 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
324 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
325 LABEL %(grml_flavour)s-x
326 KERNEL /boot/release/%(grml_flavour)s/linux26
327 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
329 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
330 LABEL %(grml_flavour)s-nofb
331 KERNEL /boot/release/%(grml_flavour)s/linux26
332 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
334 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
335 LABEL %(grml_flavour)s-failsafe
336 KERNEL /boot/release/%(grml_flavour)s/linux26
337 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
339 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
340 LABEL %(grml_flavour)s-forensic
341 KERNEL /boot/release/%(grml_flavour)s/linux26
342 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
344 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
345 LABEL %(grml_flavour)s-serial
346 KERNEL /boot/release/%(grml_flavour)s/linux26
347 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
351 def install_grub(device):
352 """Install grub on specified device.
354 @device: partition where grub should be installed to"""
357 logging.info("Would execute grub-install %s now.", device)
359 logging.critical("TODO: sorry - grub-install %s not implemented yet" % device)
362 def install_bootloader(device):
363 """Install bootloader on specified device.
365 @device: partition where bootloader should be installed to"""
367 # Install bootloader on the device (/dev/sda),
368 # not on the partition itself (/dev/sda1)?
369 #if partition[-1:].isdigit():
370 # device = re.match(r'(.*?)\d*$', partition).group(1)
377 install_syslinux(device)
380 def is_writeable(device):
381 """Check if the device is writeable for the current user
383 @device: partition where bootloader should be installed to"""
387 #raise Exception, "no device for checking write permissions"
389 if not os.path.exists(device):
392 return os.access(device, os.W_OK) and os.access(device, os.R_OK)
395 def install_mbr(device):
396 """Install a default master boot record on given device
398 @device: device where MBR should be installed to"""
400 if not is_writeable(device):
401 raise IOError, "device not writeable for user"
406 lilo = '/usr/share/grml2usb/lilo/lilo.static'
409 raise Exception, "lilo executable can not be execute"
412 logging.info("Would install MBR running lilo and using syslinux.")
415 # to support -A for extended partitions:
416 logging.info("Installing MBR")
417 logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
418 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
420 if proc.returncode != 0:
421 raise Exception, "error executing lilo"
423 # activate partition:
424 logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
425 if not options.dryrun:
426 proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
428 if proc.returncode != 0:
429 raise Exception, "error executing lilo"
431 # lilo's mbr is broken, use the one from syslinux instead:
432 if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
433 raise Exception, "/usr/lib/syslinux/mbr.bin can not be read"
435 logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
436 if not options.dryrun:
438 # TODO -> use Popen instead?
439 retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
441 logging.critical("Error copying MBR to device (%s)" % retcode)
442 except OSError, error:
443 logging.critical("Execution failed:", error)
446 def register_tmpfile(path):
453 def unregister_tmpfile(path):
458 tmpfiles.remove(path)
461 def register_mountpoint(target):
468 def unregister_mountpoint(target):
472 if target in mounted:
473 mounted.remove(target)
476 def mount(source, target, options):
477 """Mount specified source on given target
479 @source: name of device/ISO that should be mounted
480 @target: directory where the ISO should be mounted to
481 @options: mount specific options"""
483 # notice: options.dryrun does not work here, as we have to
484 # locate files and identify the grml flavour
485 logging.debug("mount %s %s %s" % (options, source, target))
486 proc = subprocess.Popen(["mount"] + list(options) + [source, target])
488 if proc.returncode != 0:
489 raise Exception, "Error executing mount"
491 logging.debug("register_mountpoint(%s)" % target)
492 register_mountpoint(target)
495 def unmount(target, options):
496 """Unmount specified target
498 @target: target where something is mounted on and which should be unmounted
499 @options: options for umount command"""
501 # make sure we unmount only already mounted targets
502 target_unmount = False
503 mounts = open('/proc/mounts').readlines()
504 mountstring = re.compile(".*%s.*" % re.escape(target))
506 if re.match(mountstring, line):
507 target_unmount = True
509 if not target_unmount:
510 logging.debug("%s not mounted anymore" % target)
512 logging.debug("umount %s %s" % (list(options), target))
513 proc = subprocess.Popen(["umount"] + list(options) + [target])
515 if proc.returncode != 0:
516 raise Exception, "Error executing umount"
518 logging.debug("unregister_mountpoint(%s)" % target)
519 unregister_mountpoint(target)
522 def check_for_usbdevice(device):
523 """Check whether the specified device is a removable USB device
525 @device: device name, like /dev/sda1 or /dev/sda
528 usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
529 usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
530 if os.path.isfile(usbdevice):
531 is_usb = open(usbdevice).readline()
538 def check_for_fat(partition):
539 """Check whether specified partition is a valid VFAT/FAT16 filesystem
541 @partition: device name of partition"""
544 udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
545 filesystem = udev_info.communicate()[0].rstrip()
547 if udev_info.returncode == 2:
548 raise Exception, "Failed to read device %s - wrong UID / permissions?" % partition
550 if filesystem != "vfat":
551 raise Exception, "Device %s does not contain a FAT16 partition." % partition
554 raise Exception, "Sorry, /lib/udev/vol_id not available."
557 def mkdir(directory):
558 """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
560 if not os.path.isdir(directory):
562 os.makedirs(directory)
564 # just silently pass as it's just fine it the directory exists
568 def copy_grml_files(grml_flavour, iso_mount, target):
569 """Copy files from ISO on given target"""
572 # * provide alternative search_file() if file information is stored in a config.ini file?
573 # * catch "install: .. No space left on device" & CO
574 # * abstract copy logic to make the code shorter and get rid of spaghettis ;)
577 logging.info("Would copy files to %s", iso_mount)
578 elif not options.bootloaderonly:
579 logging.info("Copying files. This might take a while....")
581 squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
582 squashfs_target = target + '/live/'
583 execute(mkdir, squashfs_target)
585 # use install(1) for now to make sure we can write the files afterwards as normal user as well
586 logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
587 proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
590 filesystem_module = search_file('filesystem.module', iso_mount)
591 logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
592 proc = subprocess.Popen(["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
595 release_target = target + '/boot/release/' + grml_flavour
596 execute(mkdir, release_target)
598 kernel = search_file('linux26', iso_mount)
599 logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
600 proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
603 initrd = search_file('initrd.gz', iso_mount)
604 logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
605 proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
608 if not options.copyonly:
609 syslinux_target = target + '/boot/syslinux/'
610 execute(mkdir, syslinux_target)
612 logo = search_file('logo.16', iso_mount)
613 logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
614 proc = subprocess.Popen(["install", "--mode=664", logo, syslinux_target + 'logo.16'])
617 for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
618 bootsplash = search_file(ffile, iso_mount)
619 logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
620 proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile])
623 grub_target = target + '/boot/grub/'
624 execute(mkdir, grub_target)
626 if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
627 logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
630 logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
631 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz'])
634 if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"):
635 logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.")
638 logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito')
639 proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito'])
642 if not options.dryrun:
643 logging.debug("Generating grub configuration")
644 #with open("...", "w") as f:
645 #f.write("bla bla bal")
646 grub_config_file = open(grub_target + 'menu.lst', 'w')
647 grub_config_file.write(generate_grub_config(grml_flavour))
648 grub_config_file.close()
650 logging.info("Generating syslinux configuration")
651 syslinux_cfg = syslinux_target + 'syslinux.cfg'
653 # install main configuration only *once*, no matter how many ISOs we have:
654 if os.path.isfile(syslinux_cfg):
655 string = open(syslinux_cfg).readline()
656 main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(datestamp)))
657 if not re.match(main_identifier, string):
658 syslinux_config_file = open(syslinux_cfg, 'w')
659 logging.info("Notice: grml flavour %s is being installed as the default booting system." % grml_flavour)
660 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
661 syslinux_config_file.close()
663 syslinux_config_file = open(syslinux_cfg, 'w')
664 syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
665 syslinux_config_file.close()
667 # install flavour specific configuration only *once* as well
668 # kind of ugly - I'm pretty sure this could be smoother...
669 flavour_config = True
670 if os.path.isfile(syslinux_cfg):
671 string = open(syslinux_cfg).readlines()
672 logging.info("Notice: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
673 flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(datestamp))))
675 if flavour.match(line):
676 flavour_config = False
680 syslinux_config_file = open(syslinux_cfg, 'a')
681 syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, options.bootoptions))
682 syslinux_config_file.close( )
684 logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
685 isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
686 isolinux_splash.write(generate_isolinux_splash(grml_flavour))
687 isolinux_splash.close( )
690 # make sure we sync filesystems before returning
691 proc = subprocess.Popen(["sync"])
695 def uninstall_files(device):
696 """Get rid of all grml files on specified device
698 @device: partition where grml2usb files should be removed from"""
701 logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
704 def identify_grml_flavour(mountpath):
705 """Get name of grml flavour
707 @mountpath: path where the grml ISO is mounted to
708 @return: name of grml-flavour"""
710 version_file = search_file('grml-version', mountpath)
712 if version_file == "":
713 logging.critical("Error: could not find grml-version file.")
717 tmpfile = open(version_file, 'r')
718 grml_info = tmpfile.readline()
719 grml_flavour = re.match(r'[\w-]*', grml_info).group()
723 logging.critical("Unexpected error:", sys.exc_info()[0])
729 def handle_iso(iso, device):
730 """Main logic for mounting ISOs and copying files.
732 @iso: full path to the ISO that should be installed to the specified device
733 @device: partition where the specified ISO should be installed to"""
735 logging.info("Using ISO %s" % iso)
737 if os.path.isdir(iso):
738 logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
741 iso_mountpoint = tempfile.mkdtemp()
742 register_tmpfile(iso_mountpoint)
743 remove_iso_mountpoint = True
745 if not os.path.isfile(iso):
746 logging.critical("Fatal: specified ISO %s could not be read" % iso)
750 mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
752 if os.path.isdir(device):
753 logging.info("Specified target is a directory, not mounting therefore.")
754 device_mountpoint = device
755 remove_device_mountpoint = False
759 device_mountpoint = tempfile.mkdtemp()
760 register_tmpfile(device_mountpoint)
761 remove_device_mountpoint = True
763 mount(device, device_mountpoint, "")
764 except Exception, error:
765 logging.critical("Fatal: %s" % error)
769 grml_flavour = identify_grml_flavour(iso_mountpoint)
770 logging.info("Identified grml flavour \"%s\"." % grml_flavour)
771 copy_grml_files(grml_flavour, iso_mountpoint, device_mountpoint)
773 logging.critical("Fatal: a critical error happend during execution, giving up")
776 if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
777 unmount(iso_mountpoint, "")
779 os.rmdir(iso_mountpoint)
780 unregister_tmpfile(iso_mountpoint)
782 if remove_device_mountpoint:
783 unmount(device_mountpoint, "")
785 if os.path.isdir(device_mountpoint):
786 os.rmdir(device_mountpoint)
787 unregister_tmpfile(device_mountpoint)
789 # grml_flavour_short = grml_flavour.replace('-','')
790 # logging.debug("grml_flavour_short = %s" % grml_flavour_short)
794 """Main function [make pylint happy :)]"""
797 print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
801 parser.error("invalid usage")
805 FORMAT = "%(asctime)-15s %(message)s"
806 logging.basicConfig(level=logging.DEBUG, format=FORMAT)
808 FORMAT = "Critial: %(message)s"
809 logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
811 FORMAT = "Info: %(message)s"
812 logging.basicConfig(level=logging.INFO, format=FORMAT)
815 logging.info("Running in simulate mode as requested via option dry-run.")
819 # specified arguments
820 device = args[len(args) - 1]
821 isos = args[0:len(args) - 1]
823 # make sure we can replace old grml2usb script and warn user when using old way of life:
824 if device.startswith("/mnt/external") or device.startswith("/mnt/usb"):
825 print "Warning: the semantics of grml2usb has changed."
826 print "Instead of using grml2usb /path/to/iso %s you might" % device
827 print "want to use grml2usb /path/to/iso /dev/... instead."
828 print "Please check out the grml2usb manpage for details."
829 f = raw_input("Do you really want to continue? y/N ")
830 if f == "y" or f == "Y":
835 # make sure we have syslinux available
837 if not which("syslinux") and not options.copyonly and not options.dryrun:
838 logging.critical('Sorry, syslinux not available. Exiting.')
839 logging.critical('Please install syslinux or consider using the --grub option.')
842 # make sure we have mkfs.vfat available
844 if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
845 logging.critical('Sorry, mkfs.vfat not available. Exiting.')
846 logging.critical('Please install dosfstools.')
849 # check for vfat filesystem
850 if device is not None and not os.path.isdir(device):
852 check_for_fat(device)
853 except Exception, error:
854 logging.critical("Execution failed: %s", error)
857 if not check_for_usbdevice(device):
858 print "Warning: the specified device %s does not look like a removable usb device." % device
859 f = raw_input("Do you really want to continue? y/N ")
860 if f == "y" or f == "Y":
869 # main operation (like installing files)
871 handle_iso(iso, device)
874 if not options.mbr or skip_mbr:
875 logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
877 # make sure we install MBR on /dev/sdX and not /dev/sdX#
878 if device[-1:].isdigit():
879 mbr_device = re.match(r'(.*?)\d*$', device).group(1)
882 install_mbr(mbr_device)
883 except IOError, error:
884 logging.critical("Execution failed: %s", error)
886 except Exception, error:
887 logging.critical("Execution failed: %s", error)
890 # Install bootloader only if not using the --copy-only option
892 logging.info("Not installing bootloader and its files as requested via option copyonly.")
894 install_bootloader(device)
896 # finally be politely :)
897 logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
900 if __name__ == "__main__":
903 except KeyboardInterrupt:
904 logging.info("Received KeyboardInterrupt")
907 ## END OF FILE #################################################################
908 # vim:foldmethod=marker expandtab ai ft=python tw=120 fileencoding=utf-8