Add nokms option to grub menu
[grml2usb.git] / grml2usb
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # pylint: disable-msg=C0302
4 """
5 grml2usb
6 ~~~~~~~~
7
8 This script installs a grml system (either a running system or ISO[s]) to a USB device
9
10 :copyright: (c) 2009, 2010, 2011 by Michael Prokop <mika@grml.org>
11 :license: GPL v2 or any later version
12 :bugreports: http://grml.org/bugs/
13
14 """
15
16 from optparse import OptionParser
17 from inspect import isroutine, isclass
18 import datetime, logging, os, re, subprocess, sys, tempfile, time, os.path
19 import fileinput
20 import glob
21 import uuid
22 import struct
23
24 # global variables
25 PROG_VERSION = "0.9.31"
26 MOUNTED = set()  # register mountpoints
27 TMPFILES = set() # register tmpfiles
28 DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
29 GRML_FLAVOURS = set() # which flavours are being installed?
30 GRML_DEFAULT = None
31 UUID = None
32 SYSLINUX_LIBS = "/usr/lib/syslinux/"
33
34 def syslinux_warning(option, opt, value, opt_parser):
35     """A helper function for printing a warning about deprecated option
36     """
37     # pylint: disable-msg=W0613
38     sys.stderr.write("Note: the --syslinux option is deprecated as syslinux "
39                      "is grml2usb's default. Continuing anyway.\n")
40     setattr(opt_parser.values, option.dest, True)
41
42 # if grub option is set, unset syslinux option
43 def grub_option(option, opt, value, opt_parser):
44     """A helper function adjusting other option values
45     """
46     # pylint: disable-msg=W0613
47     setattr(opt_parser.values, option.dest, True)
48     setattr(opt_parser.values, 'syslinux', False)
49
50 # cmdline parsing
51 USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/sdX#>\n\
52 \n\
53 %prog installs grml ISO[s] to an USB device to be able to boot from it.\n\
54 Make sure you have at least one grml ISO or a running grml system (/live/image),\n\
55 grub or syslinux and root access.\n\
56 \n\
57 Run %prog --help for usage hints, further information via: man grml2usb"
58
59 # pylint: disable-msg=C0103
60 # pylint: disable-msg=W0603
61 parser = OptionParser(usage=USAGE)
62 parser.add_option("--bootoptions", dest="bootoptions",
63                   action="append", type="string",
64                   help="use specified bootoptions as default")
65 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
66                   help="do not copy files but just install a bootloader")
67 parser.add_option("--copy-only", dest="copyonly", action="store_true",
68                   help="copy files only but do not install bootloader")
69 parser.add_option("--dry-run", dest="dryrun", action="store_true",
70                   help="avoid executing commands")
71 parser.add_option("--fat16", dest="fat16", action="store_true",
72                   help="format specified partition with FAT16")
73 parser.add_option("--force", dest="force", action="store_true",
74                   help="force any actions requiring manual interaction")
75 parser.add_option("--grub", dest="grub", action="callback",
76                   callback=grub_option,
77                   help="install grub bootloader instead of (default) syslinux")
78 parser.add_option("--grub-mbr", dest="grubmbr", action="store_true",
79                   help="install grub into MBR instead of (default) PBR")
80 parser.add_option("--mbr-menu", dest="mbrmenu", action="store_true",
81                   help="enable interactive boot menu in MBR")
82 parser.add_option("--quiet", dest="quiet", action="store_true",
83                   help="do not output anything but just errors on console")
84 parser.add_option("--remove-bootoption", dest="removeoption", action="append",
85                   help="regex for removing existing bootoptions")
86 parser.add_option("--skip-addons", dest="skipaddons", action="store_true",
87                   help="do not install /boot/addons/ files")
88 parser.add_option("--skip-grub-config", dest="skipgrubconfig", action="store_true",
89                   help="skip generation of grub configuration files")
90 parser.add_option("--skip-mbr", dest="skipmbr", action="store_true",
91                   help="do not install a master boot record (MBR) on the device")
92 parser.add_option("--skip-syslinux-config", dest="skipsyslinuxconfig", action="store_true",
93                   help="skip generation of syslinux configuration files")
94 parser.add_option("--syslinux", dest="syslinux", action="callback", default=True,
95                   callback=syslinux_warning,
96                   help="install syslinux bootloader (deprecated as it's the default)")
97 parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true",
98                   help="install syslinux master boot record (MBR) instead of default")
99 parser.add_option("--verbose", dest="verbose", action="store_true",
100                   help="enable verbose mode")
101 parser.add_option("-v", "--version", dest="version", action="store_true",
102                   help="display version and exit")
103 (options, args) = parser.parse_args()
104
105
106 GRML2USB_BASE = '/usr/share/grml2usb'
107 if not os.path.isdir(GRML2USB_BASE):
108     GRML2USB_BASE = os.path.dirname(os.path.realpath(__file__))
109
110
111 class CriticalException(Exception):
112     """Throw critical exception if the exact error is not known but fatal."
113
114     @Exception: message"""
115     pass
116
117
118 # The following two functions help to operate on strings as
119 # array (list) of bytes (octets). In Python 3000, the bytes
120 # datatype will need to be used. This is intended for using
121 # with manipulation of files on the octet level, like shell
122 # arrays, e.g. in MBR creation.
123
124
125 def array2string(*a):
126     """Convert a list of integers [0;255] to a string."""
127     return struct.pack("%sB" % len(a), *a)
128
129
130 def string2array(s):
131     """Convert a (bytes) string into a list of integers."""
132     return struct.unpack("%sB" % len(s), s)
133
134
135 def cleanup():
136     """Cleanup function to make sure there aren't any mounted devices left behind.
137     """
138
139     logging.info("Cleaning up before exiting...")
140     proc = subprocess.Popen(["sync"])
141     proc.wait()
142
143     try:
144         for device in MOUNTED:
145             unmount(device, "")
146         for tmpfile in TMPFILES:
147             os.unlink(tmpfile)
148     # ignore: RuntimeError: Set changed size during iteration
149     except RuntimeError:
150         logging.debug('caught expection RuntimeError, ignoring')
151
152
153 def register_tmpfile(path):
154     """
155     register tmpfile
156     """
157
158     TMPFILES.add(path)
159
160
161 def unregister_tmpfile(path):
162     """
163     remove registered tmpfile
164     """
165
166     try:
167         TMPFILES.remove(path)
168     except KeyError:
169         pass
170
171
172 def register_mountpoint(target):
173     """register specified target in a set() for handling clean exiting
174
175     @target: destination target of mountpoint
176     """
177
178     MOUNTED.add(target)
179
180
181 def unregister_mountpoint(target):
182     """unregister specified target in a set() for handling clean exiting
183
184     @target: destination target of mountpoint
185     """
186
187     if target in MOUNTED:
188         MOUNTED.remove(target)
189
190
191 def get_function_name(obj):
192     """Helper function for use in execute() to retrive name of a function
193
194     @obj: the function object
195     """
196     if not (isroutine(obj) or isclass(obj)):
197         obj = type(obj)
198     return obj.__module__ + '.' + obj.__name__
199
200
201 def execute(f, *exec_arguments):
202     """Wrapper for executing a command. Either really executes
203     the command (default) or when using --dry-run commandline option
204     just displays what would be executed."""
205     # usage: execute(subprocess.Popen, (["ls", "-la"]))
206     if options.dryrun:
207         # pylint: disable-msg=W0141
208         logging.debug('dry-run only: %s(%s)', get_function_name(f), ', '.join(map(repr, exec_arguments)))
209     else:
210         # pylint: disable-msg=W0142
211         return f(*exec_arguments)
212
213
214 def is_exe(fpath):
215     """Check whether a given file can be executed
216
217     @fpath: full path to file
218     @return:"""
219     return os.path.exists(fpath) and os.access(fpath, os.X_OK)
220
221
222 def which(program):
223     """Check whether a given program is available in PATH
224
225     @program: name of executable"""
226     fpath = os.path.split(program)[0]
227     if fpath:
228         if is_exe(program):
229             return program
230     else:
231         for path in os.environ["PATH"].split(os.pathsep):
232             exe_file = os.path.join(path, program)
233             if is_exe(exe_file):
234                 return exe_file
235
236     return None
237
238
239 def get_defaults_file(iso_mount, flavour, name):
240     """get the default file for syslinux
241     """
242     bootloader_dirs = ['/boot/isolinux/', '/boot/syslinux/']
243     for directory in bootloader_dirs:
244         for name in name, \
245         "%s_%s" % (get_flavour_filename(flavour), name):
246             if os.path.isfile(iso_mount + directory + name):
247                 return (directory, name)
248     return ('','')
249
250 def search_file(filename, search_path='/bin' + os.pathsep + '/usr/bin'):
251     """Given a search path, find file
252
253     @filename: name of file to search for
254     @search_path: path where searching for the specified filename"""
255     file_found = 0
256     paths = search_path.split(os.pathsep)
257     current_dir = '' # make pylint happy :)
258
259     def match_file(cwd):
260         """Helper function ffor testing if specified file exists in cwd
261
262         @cwd: current working directory
263         """
264         return  os.path.exists(os.path.join(cwd, filename))
265
266     for path in paths:
267         current_dir = path
268         if match_file(current_dir):
269             file_found = 1
270             break
271         # pylint: disable-msg=W0612
272         for current_dir, directories, files in os.walk(path):
273             if match_file(current_dir):
274                 file_found = 1
275                 break
276     if file_found:
277         return os.path.abspath(os.path.join(current_dir, filename))
278     else:
279         return None
280
281
282 def check_uid_root():
283     """Check for root permissions"""
284     if not os.geteuid()==0:
285         sys.exit("Error: please run this script with uid 0 (root).")
286
287
288 def mkfs_fat16(device):
289     """Format specified device with VFAT/FAT16 filesystem.
290
291     @device: partition that should be formated"""
292
293     if options.dryrun:
294         logging.info("Would execute mkfs.vfat -F 16 %s now.", device)
295         return 0
296
297     logging.info("Formating partition with fat16 filesystem")
298     logging.debug("mkfs.vfat -F 16 %s", device)
299     proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
300     proc.wait()
301     if proc.returncode != 0:
302         raise CriticalException("error executing mkfs.vfat")
303
304
305 def generate_main_grub2_config(grml_flavour, bootoptions):
306     """Generate grub2 configuration for use via grub.cfg
307
308     @grml_flavour: name of grml flavour the configuration should be generated for
309     @bootoptions: additional bootoptions that should be used by default"""
310
311     local_datestamp = DATESTAMP
312
313     return("""\
314 ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
315 set default=0
316 set timeout=10
317
318 insmod fat
319
320 if loadfont /boot/grub/ascii.pf2 ; then
321    insmod png
322    set gfxmode=640x480
323    insmod gfxterm
324    insmod vbe
325    if terminal_output gfxterm ; then true ; else
326     # For backward compatibility with versions of terminal.mod that don't
327     # understand terminal_output
328     terminal gfxterm
329    fi
330 fi
331
332 if background_image /boot/grub/grml.png ; then
333   set color_normal=black/black
334   set color_highlight=red/black
335 else
336   set menu_color_normal=white/black
337   set menu_color_highlight=black/yellow
338 fi
339
340 menuentry "%(grml_flavour)s (default)" {
341     set gfxpayload=1024x768x16,1024x768
342     linux   /boot/release/%(flavour_filename)s/linux26 apm=power-off quiet boot=live nomce live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s
343     initrd  /boot/release/%(flavour_filename)s/initrd.gz
344 }
345
346 menuentry "Memory test (memtest86+)" {
347     linux16   /boot/addons/memtest
348 }
349
350 menuentry "Boot Grub (all in one image)" {
351     linux   /boot/addons/memdisk
352     initrd  /boot/addons/allinone.img
353 }
354
355 menuentry "Boot FreeDOS" {
356     linux   /boot/addons/memdisk
357     initrd  /boot/addons/balder10.imz
358 }
359
360 if [ ${iso_path} ] ; then
361     # assume loopback.cfg boot
362     if [ -e /boot/addons/bsd4grml/loopback.0 ] ; then
363         # bsd4grml 20100815 and later
364         menuentry "Boot MirOS bsd4grml" {
365             multiboot /boot/addons/bsd4grml/ldbsd.com
366             module /boot/addons/bsd4grml/bsd.rd bsd
367             module /boot/addons/bsd4grml/loopback.0 boot.cfg
368             module /boot/addons/bsd4grml/loopback.1 boot.1
369             module /boot/addons/bsd4grml/loopback.2 boot.2
370             module /boot/addons/bsd4grml/loopback.3 boot.3
371             module /boot/addons/bsd4grml/loopback.4 boot.4
372             module /boot/addons/bsd4grml/loopback.5 boot.5
373             module /boot/addons/bsd4grml/loopback.6 boot.6
374         }
375     else
376         # old bsd4grml
377         menuentry "Boot MirOS bsd4grml" {
378             multiboot /boot/addons/bsd4grml/ldbsd.com
379             module /boot/addons/bsd4grml/bsd.rd bsd.rd
380             module /boot/addons/bsd4grml/boot.cfg boot.cfg
381             module /boot/addons/bsd4grml/boot.1 boot.1
382             module /boot/addons/bsd4grml/boot.2 boot.2
383             module /boot/addons/bsd4grml/boot.3 boot.3
384             module /boot/addons/bsd4grml/boot.4 boot.4
385             module /boot/addons/bsd4grml/boot.5 boot.5
386         }
387     fi
388 else
389     # assume grub.cfg boot
390     menuentry "Boot MirOS bsd4grml" {
391         multiboot /boot/addons/bsd4grml/ldbsd.com
392         module /boot/addons/bsd4grml/bsd.rd bsd.rd
393         module /boot/addons/bsd4grml/boot.cfg boot.cfg
394         module /boot/addons/bsd4grml/boot.1 boot.1
395         module /boot/addons/bsd4grml/boot.2 boot.2
396         module /boot/addons/bsd4grml/boot.3 boot.3
397         module /boot/addons/bsd4grml/boot.4 boot.4
398         module /boot/addons/bsd4grml/boot.5 boot.5
399         module /boot/addons/bsd4grml/boot.6 boot.6
400     }
401 fi
402
403 menuentry "Boot OS of first partition on first disk" {
404     set root=(hd0,1)
405     chainloader +1
406 }
407
408 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
409        'flavour_filename': grml_flavour.replace('-', ''),
410        'uid': UUID, 'bootoptions': bootoptions } )
411
412
413 def generate_flavour_specific_grub2_config(grml_flavour, bootoptions):
414     """Generate grub2 configuration for use via grub.cfg
415
416     @grml_flavour: name of grml flavour the configuration should be generated for
417     @bootoptions: additional bootoptions that should be used by default"""
418
419     local_datestamp = DATESTAMP
420
421     return("""\
422 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
423 menuentry "%(grml_flavour)s            - boot in default mode" {
424     set gfxpayload=1024x768x16,1024x768
425     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s
426     initrd /boot/release/%(flavour_filename)s/initrd.gz
427 }
428
429 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
430 menuentry "%(grml_flavour)s-persistent - enable persistency feature" {
431     set gfxpayload=1024x768x16,1024x768
432     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet persistent live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s
433     initrd /boot/release/%(flavour_filename)s/initrd.gz
434 }
435
436 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
437 menuentry "%(grml_flavour)s2ram        - copy compressed grml file to RAM" {
438     set gfxpayload=1024x768x16,1024x768
439     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs bootid=%(uid)s %(bootoptions)s
440     initrd /boot/release/%(flavour_filename)s/initrd.gz
441 }
442
443 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
444 menuentry "%(grml_flavour)s-debug      - enable debugging options" {
445     set gfxpayload=1024x768x16,1024x768
446     linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ debug bootid=%(uid)s initcall_debug %(bootoptions)s
447     initrd /boot/release/%(flavour_filename)s/initrd.gz
448 }
449
450 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
451 menuentry "%(grml_flavour)s-x          - start X Window System" {
452     set gfxpayload=1024x768x16,1024x768
453     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng bootid=%(uid)s %(bootoptions)s
454     initrd /boot/release/%(flavour_filename)s/initrd.gz
455 }
456
457 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
458 menuentry "%(grml_flavour)s-nofb       - disable framebuffer" {
459     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=ofonly bootid=%(uid)s %(bootoptions)s
460     initrd /boot/release/%(flavour_filename)s/initrd.gz
461 }
462
463 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
464 menuentry "%(grml_flavour)s-nokms      - disable Kernel Mode-Setting" {
465     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ bootid=%(uid)s %(bootoptions)s radeon.modeset=0 i915.modeset=0 nouveau.modeset=0 nomodeset
466     initrd /boot/release/%(flavour_filename)s/initrd.gz
467 }
468
469 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
470 menuentry "%(grml_flavour)s-failsafe   - disable hardware detection" {
471     linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal noautoconfig atapicd noapic noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia nosmp maxcpus=0 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm noresume selinux=0 edd=off pci=nomsi bootid=%(uid)s %(bootoptions)s
472     initrd /boot/release/%(flavour_filename)s/initrd.gz
473 }
474
475 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
476 menuentry "%(grml_flavour)s-forensic   - do not touch harddisks during hw recognition" {
477     set gfxpayload=1024x768x16,1024x768
478     linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ nofstab noraid nolvm noautoconfig noswap raid=noautodetect forensic readonly bootid=%(uid)s %(bootoptions)s
479     initrd /boot/release/%(flavour_filename)s/initrd.gz
480 }
481
482 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
483        'flavour_filename': grml_flavour.replace('-', ''),
484        'uid': UUID, 'bootoptions': bootoptions } )
485
486 def generate_isolinux_splash(grml_flavour):
487     """Generate bootsplash for isolinux/syslinux
488
489     @grml_flavour: name of grml flavour the configuration should be generated for"""
490
491     grml_name = grml_flavour
492
493     return("""\
494 \ f17\f\18/boot/syslinux/logo.16
495
496 Some information and boot options available via keys F2 - F10. http://grml.org/
497 %(grml_name)s
498 """ % {'grml_name': grml_name} )
499
500
501 def generate_main_syslinux_config(*arg):
502     """Generate main configuration for use in syslinux.cfg
503
504     @*arg: just for backward compatibility"""
505     # pylint: disable-msg=W0613
506     # remove warning about unused arg
507
508     return("""\
509 label -
510 menu label Default boot modes:
511 menu disable
512 include defaults.cfg
513
514 menu end
515 menu separator
516
517 # flavours:
518 label -
519 menu label Additional boot entries for:
520 menu disable
521 include additional.cfg
522
523 menu separator
524 include options.cfg
525 include addons.cfg
526
527 label help
528   include promptname.cfg
529   config prompt.cfg
530   text help
531                                         Jump to old style isolinux prompt
532                                         featuring further information
533                                         regarding available boot options.
534   endtext
535
536
537 include hiddens.cfg
538 """)
539
540
541 def generate_flavour_specific_syslinux_config(grml_flavour):
542     """Generate flavour specific configuration for use in syslinux.cfg
543
544     @grml_flavour: name of grml flavour the configuration should be generated for"""
545
546
547     return("""\
548 menu begin grml %(grml_flavour)s
549     menu title %(display_name)s
550     label mainmenu
551     menu label ^Back to main menu...
552     menu exit
553     menu separator
554     # include config for boot parameters from disk
555     include %(grml_flavour)s_grml.cfg
556     menu hide
557 menu end
558 """ % {'grml_flavour': grml_flavour, 'display_name' : grml_flavour.replace('_', '-') } )
559
560
561 def install_grub(device):
562     """Install grub on specified device.
563
564     @mntpoint: mountpoint of device where grub should install its files to
565     @device: partition where grub should be installed to"""
566
567     if options.dryrun:
568         logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device)
569     else:
570         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
571         register_tmpfile(device_mountpoint)
572         try:
573             try:
574                 mount(device, device_mountpoint, "")
575
576                 # If using --grub-mbr then make sure we install grub in MBR instead of PBR
577                 if options.grubmbr:
578                     logging.debug("Using option --grub-mbr ...")
579                     if device[-1:].isdigit():
580                         grub_device = re.match(r'(.*?)\d*$', device).group(1)
581                     else:
582                         grub_device = device
583                 else:
584                     grub_device = device
585
586                 logging.info("Installing grub as bootloader")
587                 for opt in ["", "--force" ]:
588                     logging.debug("grub-install --recheck %s --no-floppy --root-directory=%s %s",
589                                   opt, device_mountpoint, grub_device)
590                     proc = subprocess.Popen(["grub-install", "--recheck", opt, "--no-floppy",
591                                              "--root-directory=%s" % device_mountpoint, grub_device],
592                                             stdout=file(os.devnull, "r+"))
593                     proc.wait()
594                     if proc.returncode == 0:
595                         break
596
597                 if proc.returncode != 0:
598                     # raise Exception("error executing grub-install")
599                     logging.critical("Fatal: error executing grub-install "
600                                      + "(please check the grml2usb FAQ or drop the --grub option)")
601                     logging.critical("Note:  if using grub2 consider using "
602                                      + "the --grub-mbr option as grub considers PBR problematic.")
603                     cleanup()
604                     sys.exit(1)
605             except CriticalException, error:
606                 logging.critical("Fatal: %s", error)
607                 cleanup()
608                 sys.exit(1)
609
610         finally:
611             unmount(device_mountpoint, "")
612             os.rmdir(device_mountpoint)
613             unregister_tmpfile(device_mountpoint)
614
615
616 def install_syslinux(device):
617     """Install syslinux on specified device.
618
619     @device: partition where syslinux should be installed to"""
620
621     if options.dryrun:
622         logging.info("Would install syslinux as bootloader on %s", device)
623         return 0
624
625     # syslinux -d boot/isolinux /dev/sdb1
626     logging.info("Installing syslinux as bootloader")
627     logging.debug("syslinux -d boot/syslinux %s", device)
628     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
629     proc.wait()
630     if proc.returncode != 0:
631         raise CriticalException("Error executing syslinux (either try --fat16 or use grub?)")
632
633
634 def install_bootloader(device):
635     """Install bootloader on specified device.
636
637     @device: partition where bootloader should be installed to"""
638
639     # by default we use grub, so install syslinux only on request
640     if options.grub:
641         try:
642             install_grub(device)
643         except CriticalException, error:
644             logging.critical("Fatal: %s", error)
645             cleanup()
646             sys.exit(1)
647     else:
648         try:
649             install_syslinux(device)
650         except CriticalException, error:
651             logging.critical("Fatal: %s", error)
652             cleanup()
653             sys.exit(1)
654
655
656 def install_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
657     """install 'mbr' master boot record (MBR) on a device
658
659     Retrieve the partition table from "device", install an MBR from the
660     "mbrtemplate" file, set the "partition" (0..3) active, and install the
661     result back to "device".
662
663     @mbrtemplate: default MBR file
664
665     @device: name of a file assumed to be a hard disc (or USB stick) image, or
666     something like "/dev/sdb"
667
668     @partition: must be a number between 0 and 3, inclusive
669
670     @mbrtemplate: must be a valid MBR file of at least 440 (or 439 if
671     ismirbsdmbr) bytes.
672
673     @ismirbsdmbr: if true then ignore the active flag, set the mirbsdmbr
674     specific flag to 0/1/2/3 and set the MBR's default value accordingly. If
675     false then leave the mirbsdmbr specific flag set to FFh, set all
676     active flags to 0 and set the active flag of the partition to 80h.  Note:
677     behaviour of mirbsdmbr: if flag = 0/1/2/3 then use it, otherwise search for
678     the active flag."""
679
680     logging.info("Installing default MBR")
681
682     if not os.path.isfile(mbrtemplate):
683         logging.critical("Error: %s can not be read.", mbrtemplate)
684         raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file \"%s\"?)" % mbrtemplate)
685
686     if partition is not None and ((partition < 0) or (partition > 3)):
687         logging.warn("Cannot activate partition %d" % partition)
688         partition = None
689
690     if ismirbsdmbr:
691         nmbrbytes = 439
692     else:
693         nmbrbytes = 440
694
695     tmpf = tempfile.NamedTemporaryFile()
696
697     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1", device, tmpf.name)
698     proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"],
699                             stderr=file(os.devnull, "r+"))
700     proc.wait()
701     if proc.returncode != 0:
702         raise Exception("error executing dd (first run)")
703
704     logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc", mbrtemplate,
705                   tmpf.name, nmbrbytes)
706     proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes,
707                              "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
708     proc.wait()
709     if proc.returncode != 0:
710         raise Exception("error executing dd (second run)")
711
712     mbrcode = tmpf.file.read(512)
713     if len(mbrcode) < 512:
714         raise EOFError("MBR size (%d) < 512" % len(mbrcode))
715
716     if partition is not None:
717         if ismirbsdmbr:
718             mbrcode = mbrcode[0:439] + chr(partition) + \
719                     mbrcode[440:510] + "\x55\xAA"
720         else:
721             actives = ["\x00", "\x00", "\x00", "\x00"]
722             actives[partition] = "\x80"
723             mbrcode = mbrcode[0:446] + actives[0] + \
724                     mbrcode[447:462] + actives[1] + \
725                     mbrcode[463:478] + actives[2] + \
726                     mbrcode[479:494] + actives[3] + \
727                     mbrcode[495:510] + "\x55\xAA"
728     
729     tmpf.file.seek(0)
730     tmpf.file.truncate()
731     tmpf.file.write(mbrcode)
732     tmpf.file.close()
733
734     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc", tmpf.name, device)
735     proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1",
736                              "conv=notrunc"], stderr=file(os.devnull, "r+"))
737     proc.wait()
738     if proc.returncode != 0:
739         raise Exception("error executing dd (third run)")
740     del tmpf
741
742
743 def is_writeable(device):
744     """Check if the device is writeable for the current user
745
746     @device: partition where bootloader should be installed to"""
747
748     if not device:
749         return False
750         #raise Exception("no device for checking write permissions")
751
752     if not os.path.exists(device):
753         return False
754
755     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
756
757
758 def mount(source, target, mount_options):
759     """Mount specified source on given target
760
761     @source: name of device/ISO that should be mounted
762     @target: directory where the ISO should be mounted to
763     @options: mount specific options"""
764
765     # note: options.dryrun does not work here, as we have to
766     # locate files and identify the grml flavour
767
768     for x in file('/proc/mounts').readlines():
769         if x.startswith(source):
770             raise CriticalException("Error executing mount: %s already mounted - " % source
771                                     + "please unmount before invoking grml2usb")
772
773     if os.path.isdir(source):
774         logging.debug("Source %s is not a device, therefore not mounting.", source)
775         return 0
776
777     logging.debug("mount %s %s %s", mount_options, source, target)
778     proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target])
779     proc.wait()
780     if proc.returncode != 0:
781         raise CriticalException("Error executing mount (no filesystem on the partition?)")
782     else:
783         logging.debug("register_mountpoint(%s)", target)
784         register_mountpoint(target)
785
786
787 def unmount(target, unmount_options):
788     """Unmount specified target
789
790     @target: target where something is mounted on and which should be unmounted
791     @options: options for umount command"""
792
793     # make sure we unmount only already mounted targets
794     target_unmount = False
795     mounts = open('/proc/mounts').readlines()
796     mountstring = re.compile(".*%s.*" % re.escape(os.path.realpath(target)))
797     for line in mounts:
798         if re.match(mountstring, line):
799             target_unmount = True
800
801     if not target_unmount:
802         logging.debug("%s not mounted anymore", target)
803     else:
804         logging.debug("umount %s %s", list(unmount_options), target)
805         proc = subprocess.Popen(["umount"] + list(unmount_options) + [target])
806         proc.wait()
807         if proc.returncode != 0:
808             raise Exception("Error executing umount")
809         else:
810             logging.debug("unregister_mountpoint(%s)", target)
811             unregister_mountpoint(target)
812
813
814 def check_for_usbdevice(device):
815     """Check whether the specified device is a removable USB device
816
817     @device: device name, like /dev/sda1 or /dev/sda
818     """
819
820     usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
821     # newer systems:
822     usbdev = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
823     if not os.path.isfile(usbdev):
824         # Ubuntu with kernel 2.6.24 for example:
825         usbdev = os.path.realpath('/sys/block/' + usbdevice + '/removable')
826
827     if os.path.isfile(usbdev):
828         is_usb = open(usbdev).readline()
829         if is_usb.find("1"):
830             return 0
831
832     return 1
833
834
835 def check_for_fat(partition):
836     """Check whether specified partition is a valid VFAT/FAT16 filesystem
837
838     @partition: device name of partition"""
839
840     if not os.access(partition, os.R_OK):
841         raise CriticalException("Failed to read device %s"
842                 " (wrong UID/permissions or device/directory not present?)" % partition)
843
844     try:
845         udev_info = subprocess.Popen(["/sbin/blkid", "-s", "TYPE", "-o", "value", partition],
846                                      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
847         filesystem = udev_info.communicate()[0].rstrip()
848
849         if filesystem != "vfat":
850             raise CriticalException(
851                     "Partition %s does not contain a FAT16 filesystem. "
852                     "(Use --fat16 or run mkfs.vfat %s)" % (partition, partition))
853
854     except OSError:
855         raise CriticalException("Sorry, /sbin/blkid not available (install e2fsprogs?)")
856
857
858 def mkdir(directory):
859     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
860
861     # just silently pass as it's just fine it the directory exists
862     if not os.path.isdir(directory):
863         try:
864             os.makedirs(directory)
865         # pylint: disable-msg=W0704
866         except OSError:
867             pass
868
869
870 def exec_rsync(source, target):
871     """Simple wrapper around rsync to install files
872
873     @source: source file/directory
874     @target: target file/directory"""
875     logging.debug("Source: %s / Target: %s", source, target)
876     proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", source, target])
877     proc.wait()
878     if proc.returncode == 12:
879         logging.critical("Fatal: No space left on device")
880         cleanup()
881         sys.exit(1)
882
883     if proc.returncode != 0:
884         logging.critical("Fatal: could not install %s", source)
885         cleanup()
886         sys.exit(1)
887
888
889 def write_uuid(target_file):
890     """Generates an returns uuid and write it to the specified file
891
892     @target_file: filename to write the uuid to
893     """
894
895     fileh = open(target_file, 'w')
896     uid = str(uuid.uuid4())
897     fileh.write(uid)
898     fileh.close()
899     return uid
900
901
902 def get_uuid(target):
903     """Get the uuid of the specified target. Will generate an uuid if none exist.
904
905     @target: directory/mountpoint containing the grml layout
906     """
907
908     conf_target = target + "/conf/"
909     uuid_file_name = conf_target + "/bootid.txt"
910     if os.path.isdir(conf_target):
911         if os.path.isfile(uuid_file_name):
912             uuid_file = open(uuid_file_name, 'r')
913             uid = uuid_file.readline().strip()
914             uuid_file.close()
915             return uid
916         else:
917             return write_uuid(uuid_file_name)
918     else:
919         execute(mkdir, conf_target)
920         return write_uuid(uuid_file_name)
921
922
923 def copy_system_files(grml_flavour, iso_mount, target):
924     """copy grml's main files (like squashfs, kernel and initrd) to a given target
925
926     @grml_flavour: name of grml flavour the configuration should be generated for
927     @iso_mount: path where a grml ISO is mounted on
928     @target: path where grml's main files should be copied to"""
929
930     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
931     if squashfs is None:
932         logging.critical("Fatal: squashfs file not found"
933         ", please check that your iso is not corrupt")
934         raise CriticalException("error locating squashfs file")
935     else:
936         squashfs_target = target + '/live/' + grml_flavour + '/'
937         execute(mkdir, squashfs_target)
938     exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs')
939
940     for prefix in grml_flavour + "/", "":
941         filesystem_module = search_file(prefix + 'filesystem.module', iso_mount)
942         if filesystem_module:
943             break
944     if filesystem_module is None:
945         logging.critical("Fatal: filesystem.module not found")
946         raise CriticalException("error locating filesystem.module file")
947     else:
948         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
949
950
951     release_path = 'boot/release/' + grml_flavour.replace('-', '')
952     release_target = target + "/" + release_path
953     execute(mkdir, release_target)
954
955     prefix = ""
956     if os.path.isdir(iso_mount + '/boot/release'):
957         prefix = release_path + '/'
958
959     kernel = search_file(prefix + 'linux26', iso_mount)
960     if kernel is None:
961         logging.critical("Fatal kernel not found")
962         raise CriticalException("error locating kernel file")
963     else:
964         exec_rsync(kernel, release_target + '/linux26')
965
966     initrd = search_file(prefix + 'initrd.gz', iso_mount)
967     if initrd is None:
968         logging.critical("Fatal: initrd not found")
969         raise CriticalException("error locating initrd file")
970     else:
971         exec_rsync(initrd, release_target + '/initrd.gz')
972
973
974 def update_grml_versions(iso_mount, target):
975     """Update the grml version file on a cd
976     Returns true if version was updated successfully,
977     False if grml-version does not exist yet on the mountpoint
978
979     @iso_mount: string of the iso mount point
980     @target: path of the target mount point
981     """
982     grml_target = target + '/grml/'
983     target_grml_version_file = search_file('grml-version', grml_target)
984     if target_grml_version_file:
985         iso_grml_version_file = search_file('grml-version', iso_mount)
986         if not iso_grml_version_file:
987             logging.warn("Warning: %s could not be found - can not install it", iso_grml_version_file)
988             return False
989         try:
990             # read the flavours from the iso image
991             iso_versions = {}
992             iso_file = open(iso_grml_version_file, 'r')
993             for line in iso_file:
994                 iso_versions[get_flavour(line)] = line.strip()
995
996             # update the existing flavours on the target
997             for line in fileinput.input([target_grml_version_file], inplace=1):
998                 flavour = get_flavour(line)
999                 if flavour in iso_versions.keys():
1000                     print iso_versions.pop(flavour)
1001                 else:
1002                     print line.strip()
1003             fileinput.close()
1004
1005             target_file = open(target_grml_version_file, 'a')
1006             # add the new flavours from the current iso
1007             for flavour in iso_versions:
1008                 target_file.write("%s\n" % iso_versions[flavour])
1009         except IOError:
1010             logging.warn("Warning: Could not write file")
1011         finally:
1012             iso_file.close()
1013             target_file.close()
1014         return True
1015     else:
1016         return False
1017
1018 def copy_grml_files(iso_mount, target):
1019     """copy some minor grml files to a given target
1020
1021     @iso_mount: path where a grml ISO is mounted on
1022     @target: path where grml's main files should be copied to"""
1023
1024     grml_target = target + '/grml/'
1025     execute(mkdir, grml_target)
1026
1027     copy_files = [ 'grml-cheatcodes.txt', 'LICENSE.txt', 'md5sums', 'README.txt' ]
1028     # handle grml-version
1029     if not update_grml_versions(iso_mount, target):
1030         copy_files.append('grml-version')
1031
1032     for myfile in copy_files:
1033         grml_file = search_file(myfile, iso_mount)
1034         if grml_file is None:
1035             logging.warn("Warning: file %s could not be found - can not install it", myfile)
1036         else:
1037             exec_rsync(grml_file, grml_target + myfile)
1038
1039     grml_web_target = grml_target + '/web/'
1040     execute(mkdir, grml_web_target)
1041
1042     for myfile in 'index.html', 'style.css':
1043         grml_file = search_file(myfile, iso_mount)
1044         if grml_file is None:
1045             logging.warn("Warning: file %s could not be found - can not install it", myfile)
1046         else:
1047             exec_rsync(grml_file, grml_web_target + myfile)
1048
1049     grml_webimg_target = grml_web_target + '/images/'
1050     execute(mkdir, grml_webimg_target)
1051
1052     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1053         grml_file = search_file(myfile, iso_mount)
1054         if grml_file is None:
1055             logging.warn("Warning: file %s could not be found - can not install it", myfile)
1056         else:
1057             exec_rsync(grml_file, grml_webimg_target + myfile)
1058
1059
1060 def handle_addon_copy(filename, dst, iso_mount, ignore_errors=False):
1061     """handle copy of optional addons
1062
1063     @filename: filename of the addon
1064     @dst: destination directory
1065     @iso_mount: location of the iso mount
1066     @ignore_errors: don't report missing files
1067     """
1068     file_location = search_file(filename, iso_mount)
1069     if file_location is None:
1070         if not ignore_errors:
1071             logging.warn("Warning: %s not found (that's fine if you don't need it)",  filename)
1072     else:
1073         exec_rsync(file_location, dst)
1074
1075
1076 def copy_addons(iso_mount, target):
1077     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1078
1079     @iso_mount: path where a grml ISO is mounted on
1080     @target: path where grml's main files should be copied to"""
1081
1082     addons = target + '/boot/addons/'
1083     execute(mkdir, addons)
1084
1085     # grub all-in-one image
1086     handle_addon_copy('allinone.img', addons, iso_mount)
1087
1088     # bsd imag
1089     handle_addon_copy('bsd4grml', addons, iso_mount)
1090
1091     handle_addon_copy('balder10.imz', addons, iso_mount)
1092
1093     # install hdt and pci.ids only when using syslinux (grub doesn't support it)
1094     if options.syslinux:
1095         # hdt (hardware detection tool) image
1096         hdtimg = search_file('hdt.c32', iso_mount)
1097         if hdtimg:
1098             exec_rsync(hdtimg, addons + '/hdt.c32')
1099
1100         # pci.ids file
1101         picids = search_file('pci.ids', iso_mount)
1102         if picids:
1103             exec_rsync(picids, addons + '/pci.ids')
1104
1105     # memdisk image
1106     handle_addon_copy('memdisk', addons, iso_mount)
1107
1108     # memtest86+ image
1109     handle_addon_copy('memtest', addons, iso_mount)
1110
1111     # gpxe.lkrn: got replaced by ipxe
1112     handle_addon_copy('gpxe.lkrn', addons, iso_mount, ignore_errors=True)
1113
1114     # ipxe.lkrn
1115     handle_addon_copy('ipxe.lkrn', addons, iso_mount)
1116
1117 def glob_and_copy(filepattern, dst):
1118     """Glob on specified filepattern and copy the result to dst
1119
1120     @filepattern: globbing pattern
1121     @dst: target directory
1122     """
1123     for name in glob.glob(filepattern):
1124         copy_if_exist(name, dst)
1125
1126 def search_and_copy(filename, search_path, dst):
1127     """Search for the specified filename at searchpath and copy it to dst
1128
1129     @filename: filename to look for
1130     @search_path: base search file
1131     @dst: destionation to copy the file to
1132     """
1133     file_location = search_file(filename, search_path)
1134     copy_if_exist(file_location, dst)
1135
1136 def copy_if_exist(filename, dst):
1137     """Copy filename to dst if filename is set.
1138
1139     @filename: a filename
1140     @dst: dst file
1141     """
1142     if filename and (os.path.isfile(filename) or os.path.isdir(filename)):
1143         exec_rsync(filename, dst)
1144
1145 def copy_bootloader_files(iso_mount, target, grml_flavour):
1146     """Copy grml's bootloader files to a given target
1147
1148     @iso_mount: path where a grml ISO is mounted on
1149     @target: path where grml's main files should be copied to
1150     @grml_flavour: name of the current processed grml_flavour
1151     """
1152
1153     syslinux_target = target + '/boot/syslinux/'
1154     execute(mkdir, syslinux_target)
1155
1156     grub_target = target + '/boot/grub/'
1157     execute(mkdir, grub_target)
1158
1159     logo = search_file('logo.16', iso_mount)
1160     exec_rsync(logo, syslinux_target + 'logo.16')
1161
1162     bootx64_efi = search_file('bootx64.efi', iso_mount)
1163     if bootx64_efi:
1164         mkdir(target + '/efi/boot/')
1165         exec_rsync(bootx64_efi, target + '/efi/boot/bootx64.efi')
1166
1167     efi_img = search_file('efi.img', iso_mount)
1168     if efi_img:
1169         mkdir(target + '/boot/')
1170         exec_rsync(efi_img, target + '/boot/efi.img')
1171
1172     for ffile in ['f%d' % number for number in range(1, 11) ]:
1173         search_and_copy(ffile, iso_mount, syslinux_target + ffile)
1174
1175     loopback_cfg = search_file("loopback.cfg", iso_mount)
1176     if loopback_cfg:
1177         directory = os.path.dirname(loopback_cfg)
1178         directory = directory.replace(iso_mount, "")
1179         mkdir(os.path.join(target, directory))
1180         exec_rsync(loopback_cfg, target + os.path.sep + directory)
1181
1182     # avoid the "file is read only, overwrite anyway (y/n) ?" question
1183     # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file")
1184     if os.path.isfile(syslinux_target + 'ldlinux.sys'):
1185         os.unlink(syslinux_target + 'ldlinux.sys')
1186
1187     (source_dir, name) = get_defaults_file(iso_mount, grml_flavour, "default.cfg")
1188     (source_dir, defaults_file) = get_defaults_file(iso_mount, grml_flavour, "grml.cfg")
1189
1190     if not source_dir:
1191         logging.critical("Fatal: file default.cfg could not be found.")
1192         logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
1193         logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
1194         raise
1195
1196     for expr in name, 'distri.cfg', \
1197         defaults_file, 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \
1198         'isoprompt.cfg', 'options.cfg', \
1199         'prompt.cfg', 'vesamenu.cfg', 'grml.png', '*.c32':
1200         glob_and_copy(iso_mount + source_dir + expr, syslinux_target)
1201
1202     for filename in glob.glob1(syslinux_target, "*.c32"):
1203         copy_if_exist(os.path.join(SYSLINUX_LIBS, filename), syslinux_target)
1204
1205
1206     # copy the addons_*.cfg file to the new syslinux directory
1207     glob_and_copy(iso_mount + source_dir + 'addon*.cfg', syslinux_target)
1208
1209     search_and_copy('hidden.cfg', iso_mount + source_dir, syslinux_target + "new_" + 'hidden.cfg')
1210
1211
1212     grub_target = target + '/boot/grub/'
1213     execute(mkdir, grub_target)
1214
1215     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1216         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1217         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1218         raise
1219     else:
1220         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1221
1222     # grml splash in grub
1223     copy_if_exist(GRML2USB_BASE + "/grub/grml.png", grub_target + 'grml.png')
1224
1225     # font file for graphical bootsplash in grub
1226     copy_if_exist('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1227
1228     # always copy grub content as it might be useful
1229
1230     glob_and_copy(iso_mount + '/boot/grub/*.mod', grub_target)
1231     glob_and_copy(iso_mount + '/boot/grub/*.img', grub_target)
1232     glob_and_copy(iso_mount + '/boot/grub/stage*', grub_target)
1233
1234 def install_iso_files(grml_flavour, iso_mount, device, target):
1235     """Copy files from ISO to given target
1236
1237     @grml_flavour: name of grml flavour the configuration should be generated for
1238     @iso_mount: path where a grml ISO is mounted on
1239     @device: device/partition where bootloader should be installed to
1240     @target: path where grml's main files should be copied to"""
1241
1242     global GRML_DEFAULT
1243     GRML_DEFAULT = GRML_DEFAULT or grml_flavour
1244     if options.dryrun:
1245         return 0
1246     elif not options.bootloaderonly:
1247         logging.info("Copying files. This might take a while....")
1248         try:
1249             copy_system_files(grml_flavour, iso_mount, target)
1250             copy_grml_files(iso_mount, target)
1251         except CriticalException, error:
1252             logging.critical("Execution failed: %s", error)
1253             sys.exit(1)
1254
1255     if not options.skipaddons:
1256         if not search_file('addons', iso_mount):
1257             logging.info("Could not find addons, therefore not installing.")
1258         else:
1259             copy_addons(iso_mount, target)
1260
1261     if not options.copyonly:
1262         copy_bootloader_files(iso_mount, target, grml_flavour)
1263
1264         if not options.dryrun:
1265             handle_bootloader_config(grml_flavour, device, target)
1266
1267     # make sure we sync filesystems before returning
1268     proc = subprocess.Popen(["sync"])
1269     proc.wait()
1270
1271
1272 def get_flavour(flavour_str):
1273     """Returns the flavour of a grml version string
1274     """
1275     return re.match(r'[\w-]*', flavour_str).group()
1276
1277 def identify_grml_flavour(mountpath):
1278     """Get name of grml flavour
1279
1280     @mountpath: path where the grml ISO is mounted to
1281     @return: name of grml-flavour"""
1282
1283     version_file = search_file('grml-version', mountpath)
1284
1285     if version_file == "":
1286         logging.critical("Error: could not find grml-version file.")
1287         raise
1288
1289     flavours = []
1290     tmpfile = None
1291     try:
1292         tmpfile = open(version_file, 'r')
1293         for line in tmpfile.readlines():
1294             flavours.append(get_flavour(line))
1295     except TypeError, e:
1296         raise
1297     except Exception, e:
1298         logging.critical("Unexpected error: %s", e)
1299         raise
1300     finally:
1301         if tmpfile:
1302             tmpfile.close()
1303
1304     return flavours
1305
1306
1307 def modify_grub_config(filename):
1308     """Adjust bootoptions for a grub file
1309
1310     @filename: filename to modify
1311     """
1312     if options.removeoption:
1313         regexe = []
1314         for regex in options.removeoption:
1315             regexe.append(re.compile(r'%s' % regex))
1316
1317         option_re = re.compile(r'(.*/boot/release/.*linux26.*)')
1318
1319         for line in fileinput.input(filename, inplace=1):
1320             if regexe and option_re.search(line):
1321                 for regex in regexe:
1322                     line = regex.sub(' ', line)
1323
1324             sys.stdout.write(line)
1325
1326         fileinput.close()
1327
1328 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1329     """Main handler for generating grub2 configuration
1330
1331     @grml_flavour: name of grml flavour the configuration should be generated for
1332     @grub_target: path of grub's configuration files
1333     @bootoptions: additional bootoptions that should be used by default"""
1334
1335     # grub2 config
1336     grub2_cfg = grub_target + 'grub.cfg'
1337     logging.debug("Creating grub2 configuration file (grub.cfg)")
1338
1339     global GRML_DEFAULT
1340
1341     # install main configuration only *once*, no matter how many ISOs we have:
1342     grub_flavour_is_default = False
1343     if os.path.isfile(grub2_cfg):
1344         string = open(grub2_cfg).readline()
1345         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1346         if not re.match(main_identifier, string):
1347             grub2_config_file = open(grub2_cfg, 'w')
1348             GRML_DEFAULT = grml_flavour
1349             grub_flavour_is_default = True
1350             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1351             grub2_config_file.close()
1352     else:
1353         grub2_config_file = open(grub2_cfg, 'w')
1354         GRML_DEFAULT = grml_flavour
1355         grub_flavour_is_default = True
1356         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1357         grub2_config_file.close()
1358
1359     # install flavour specific configuration only *once* as well
1360     grub_flavour_config = True
1361     if os.path.isfile(grub2_cfg):
1362         string = open(grub2_cfg).readlines()
1363         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1364         for line in string:
1365             if flavour.match(line):
1366                 grub_flavour_config = False
1367
1368     if grub_flavour_config:
1369         grub2_config_file = open(grub2_cfg, 'a')
1370         # display only if the grml flavour isn't the default
1371         if not grub_flavour_is_default:
1372             GRML_FLAVOURS.add(grml_flavour)
1373         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1374         grub2_config_file.close()
1375
1376     modify_grub_config(grub2_cfg)
1377
1378
1379 def get_bootoptions(grml_flavour):
1380     """Returns bootoptions for specific flavour
1381
1382     @grml_flavour: name of the grml_flavour
1383     """
1384     # do NOT write "None" in kernel cmdline
1385     if not options.bootoptions:
1386         bootopt = ""
1387     else:
1388         bootopt = " ".join(options.bootoptions)
1389     bootopt = bootopt.replace("%flavour", grml_flavour)
1390     return bootopt
1391
1392
1393 def handle_grub_config(grml_flavour, device, target):
1394     """Main handler for generating grub (v1 and v2) configuration
1395
1396     @grml_flavour: name of grml flavour the configuration should be generated for
1397     @device: device/partition where grub should be installed to
1398     @target: path of grub's configuration files"""
1399
1400     logging.debug("Generating grub configuration")
1401
1402     grub_target = target + '/boot/grub/'
1403
1404     bootopt = get_bootoptions(grml_flavour)
1405
1406     # write grub.cfg
1407     handle_grub2_config(grml_flavour, grub_target, bootopt)
1408
1409
1410 def initial_syslinux_config(target):
1411     """Generates intial syslinux configuration
1412
1413     @target path of syslinux's configuration files"""
1414
1415     target = target + "/"
1416     filename = target + "grmlmain.cfg"
1417     if os.path.isfile(target + "grmlmain.cfg"):
1418         return
1419     data = open(filename, "w")
1420     data.write(generate_main_syslinux_config())
1421     data.close()
1422
1423     filename = target + "hiddens.cfg"
1424     data = open(filename, "w")
1425     data.write("include hidden.cfg\n")
1426     data.close()
1427
1428 def add_entry_if_not_present(filename, entry):
1429     """Write entry into filename if entry is not already in the file
1430
1431     @filanme: name of the file
1432     @entry: data to write to the file
1433     """
1434     data = open(filename, "a+")
1435     for line in data:
1436         if line == entry:
1437             break
1438     else:
1439         data.write(entry)
1440
1441     data.close()
1442
1443 def get_flavour_filename(flavour):
1444     """Generate a iso9960 save filename out of the specified flavour
1445
1446     @flavour: grml flavour
1447     """
1448     return flavour.replace('-', '_')
1449
1450 def adjust_syslinux_bootoptions(src, flavour):
1451     """Adjust existing bootoptions of specified syslinux config to
1452     grml2usb specific ones, e.g. change the location of the kernel...
1453
1454     @src: config file to alter
1455     @flavour: grml flavour
1456     """
1457
1458     append_re = re.compile("^(\s*append.*/boot/release.*)$", re.I)
1459     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1460     # flavour_re = re.compile("(label.*)(grml\w+)")
1461     default_re = re.compile("(default.cfg)")
1462     bootid_re = re.compile("bootid=[\w_-]+")
1463     live_media_path_re = re.compile("live-media-path=[\w_/-]+")
1464
1465     bootopt = get_bootoptions(flavour)
1466
1467     regexe = []
1468     option_re = None
1469     if options.removeoption:
1470         option_re = re.compile(r'/boot/release/.*/initrd.gz')
1471
1472         for regex in options.removeoption:
1473             regexe.append(re.compile(r'%s' % regex))
1474
1475     for line in fileinput.input(src, inplace=1):
1476         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour.replace('-', ''), line)
1477         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1478         line = default_re.sub(r'%s-\1' % flavour, line)
1479         line = bootid_re.sub('', line)
1480         line = live_media_path_re.sub('', line)
1481         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1482         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1483         line = append_re.sub(r'\1 %s=%s ' % ("bootid", UUID), line)
1484         if option_re and option_re.search(line):
1485             for regex in regexe:
1486                 line = regex.sub(' ', line)
1487         sys.stdout.write(line)
1488     fileinput.close()
1489
1490 def adjust_labels(src, replacement):
1491     """Adjust the specified labels in the syslinux config file src with
1492     specified replacement
1493     """
1494     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1495     for line in fileinput.input(src, inplace=1):
1496         line = label_re.sub(replacement, line)
1497         sys.stdout.write(line)
1498     fileinput.close()
1499
1500
1501 def add_syslinux_entry(filename, grml_flavour):
1502     """Add includes for a specific grml_flavour to the specified filename
1503
1504     @filename: syslinux config file
1505     @grml_flavour: grml flavour to add
1506     """
1507
1508     entry_filename = "option_%s.cfg" % grml_flavour
1509     entry = "include %s\n" % entry_filename
1510
1511     add_entry_if_not_present(filename, entry)
1512     path = os.path.dirname(filename)
1513
1514     data = open(path + "/" + entry_filename, "w")
1515     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1516     data.close()
1517
1518 def modify_filenames(grml_flavour, target, filenames):
1519     """Replace the standarf filenames with the new ones
1520
1521     @grml_flavour: grml-flavour strin
1522     @target: directory where the files are located
1523     @filenames: list of filenames to alter
1524     """
1525     grml_filename = grml_flavour.replace('-', '_')
1526     for filename in filenames:
1527         old_filename = "%s/%s" % (target, filename)
1528         new_filename = "%s/%s_%s" % (target, grml_filename, filename)
1529         os.rename(old_filename, new_filename)
1530
1531
1532 def remove_default_entry(filename):
1533     """Remove the default entry from specified syslinux file
1534
1535     @filename: syslinux config file
1536     """
1537     default_re = re.compile("^(\s*menu\s*default\s*)$", re.I)
1538     for line in fileinput.input(filename, inplace=1):
1539         if default_re.match(line):
1540             continue
1541         sys.stdout.write(line)
1542     fileinput.close()
1543
1544
1545 def handle_syslinux_config(grml_flavour, target):
1546     """Main handler for generating syslinux configuration
1547
1548     @grml_flavour: name of grml flavour the configuration should be generated for
1549     @target: path of syslinux's configuration files"""
1550
1551     logging.debug("Generating syslinux configuration")
1552     syslinux_target = target + '/boot/syslinux/'
1553     # should be present via  copy_bootloader_files(), but make sure it exits:
1554     execute(mkdir, syslinux_target)
1555     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1556
1557
1558     # install main configuration only *once*, no matter how many ISOs we have:
1559     syslinux_config_file = open(syslinux_cfg, 'w')
1560     syslinux_config_file.write("TIMEOUT 300\n")
1561     syslinux_config_file.write("include vesamenu.cfg\n")
1562     syslinux_config_file.close()
1563
1564     prompt_name = open(syslinux_target + 'promptname.cfg', 'w')
1565     prompt_name.write('menu label S^yslinux prompt\n')
1566     prompt_name.close()
1567
1568     initial_syslinux_config(syslinux_target)
1569     flavour_filename = grml_flavour.replace('-', '_')
1570
1571     if search_file('default.cfg', syslinux_target):
1572         modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1573
1574     filename = search_file("new_hidden.cfg", syslinux_target)
1575
1576
1577     # process hidden file
1578     if not search_file("hidden.cfg", syslinux_target):
1579         new_hidden = syslinux_target + "hidden.cfg"
1580         os.rename(filename, new_hidden)
1581         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1582     else:
1583         new_hidden_file =  "%s/%s_hidden.cfg" % (syslinux_target, flavour_filename)
1584         os.rename(filename, new_hidden_file)
1585         adjust_labels(new_hidden_file, r'\1 %s-\2' % grml_flavour)
1586         adjust_syslinux_bootoptions(new_hidden_file, grml_flavour)
1587         entry = 'include %s_hidden.cfg\n' % flavour_filename
1588         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1589
1590
1591
1592     new_default = "%s_default.cfg" % (flavour_filename)
1593     entry = 'include %s\n' % new_default
1594     defaults_file = '%s/defaults.cfg' % syslinux_target
1595     new_default_with_path = "%s/%s" % (syslinux_target, new_default)
1596     new_grml_cfg = "%s/%s_grml.cfg" % ( syslinux_target, flavour_filename)
1597
1598     if os.path.isfile(defaults_file):
1599
1600         # remove default menu entry in menu
1601         remove_default_entry(new_default_with_path)
1602
1603         # adjust all labels for additional isos
1604         adjust_labels(new_default_with_path, r'\1 %s' % grml_flavour)
1605         adjust_labels(new_grml_cfg, r'\1 %s-\2' % grml_flavour)
1606
1607     # always adjust bootoptions
1608     adjust_syslinux_bootoptions(new_default_with_path, grml_flavour)
1609     adjust_syslinux_bootoptions(new_grml_cfg, grml_flavour)
1610
1611     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1612
1613     add_syslinux_entry("%s/additional.cfg" % syslinux_target, flavour_filename)
1614
1615
1616 def handle_bootloader_config(grml_flavour, device, target):
1617     """Main handler for generating bootloader's configuration
1618
1619     @grml_flavour: name of grml flavour the configuration should be generated for
1620     @device: device/partition where bootloader should be installed to
1621     @target: path of bootloader's configuration files"""
1622
1623     global UUID
1624     UUID = get_uuid(target)
1625     if options.skipsyslinuxconfig:
1626         logging.info("Skipping generation of syslinux configuration as requested.")
1627     else:
1628         try:
1629             handle_syslinux_config(grml_flavour, target)
1630         except CriticalException, error:
1631             logging.critical("Fatal: %s", error)
1632             sys.exit(1)
1633
1634     if options.skipgrubconfig:
1635         logging.info("Skipping generation of grub configuration as requested.")
1636     else:
1637         try:
1638             handle_grub_config(grml_flavour, device, target)
1639         except CriticalException, error:
1640             logging.critical("Fatal: %s", error)
1641             sys.exit(1)
1642
1643
1644
1645 def install(image, device):
1646     """Install a grml image to the specified device
1647
1648     @image: directory or is file
1649     @device: partition or directory to install the device
1650     """
1651     iso_mountpoint = image
1652     remove_image_mountpoint = False
1653     if os.path.isdir(image):
1654         logging.info("Using %s as install base", image)
1655     else:
1656         logging.info("Using ISO %s", image)
1657         iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1658         register_tmpfile(iso_mountpoint)
1659         remove_image_mountpoint = True
1660         try:
1661             mount(image, iso_mountpoint, ["-o", "loop,ro", "-t", "iso9660"])
1662         except CriticalException, error:
1663             logging.critical("Fatal: %s", error)
1664             sys.exit(1)
1665
1666     try:
1667         install_grml(iso_mountpoint, device)
1668     finally:
1669         if remove_image_mountpoint:
1670             try:
1671                 remove_mountpoint(iso_mountpoint)
1672             except CriticalException, error:
1673                 logging.critical("Fatal: %s", error)
1674                 cleanup()
1675
1676
1677
1678 def install_grml(mountpoint, device):
1679     """Main logic for copying files of the currently running grml system.
1680
1681     @mountpoin: directory where currently running live system resides (usually /live/image)
1682     @device: partition where the specified ISO should be installed to"""
1683
1684     device_mountpoint = device
1685     if os.path.isdir(device):
1686         logging.info("Specified device is not a directory, therefore not mounting.")
1687         remove_device_mountpoint = False
1688     else:
1689         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1690         register_tmpfile(device_mountpoint)
1691         remove_device_mountpoint = True
1692         try:
1693             check_for_fat(device)
1694             mount(device, device_mountpoint, ['-o', 'utf8,iocharset=iso8859-1'])
1695         except CriticalException, error:
1696             try:
1697                 mount(device, device_mountpoint, "")
1698             except CriticalException, error:
1699                 logging.critical("Fatal: %s", error)
1700                 raise
1701     try:
1702         grml_flavours = identify_grml_flavour(mountpoint)
1703         for flavour in set(grml_flavours):
1704             if not flavour:
1705                 logging.warning("No valid flavour found, please check your iso")
1706             logging.info("Identified grml flavour \"%s\".", flavour)
1707             install_iso_files(flavour, mountpoint, device, device_mountpoint)
1708             GRML_FLAVOURS.add(flavour)
1709     finally:
1710         if remove_device_mountpoint:
1711             remove_mountpoint(device_mountpoint)
1712
1713 def remove_mountpoint(mountpoint):
1714     """remove a registred mountpoint
1715     """
1716
1717     try:
1718         unmount(mountpoint, "")
1719         if os.path.isdir(mountpoint):
1720             os.rmdir(mountpoint)
1721             unregister_tmpfile(mountpoint)
1722     except CriticalException, error:
1723         logging.critical("Fatal: %s", error)
1724         cleanup()
1725
1726 def handle_mbr(device):
1727     """Main handler for installing master boot record (MBR)
1728
1729     @device: device where the MBR should be installed to"""
1730
1731     if options.dryrun:
1732         logging.info("Would install MBR")
1733         return 0
1734
1735     if device[-1:].isdigit():
1736         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1737         partition_number = int(device[-1:]) - 1
1738     else:
1739         logging.warn("Could not detect partition number, not activating partition")
1740         partition_number = None
1741
1742     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1743     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1744     if mbr_device == "/dev/loop":
1745         mbr_device = device
1746         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1747
1748     mbrcode = GRML2USB_BASE + '/mbr/mbrldr'
1749     if options.syslinuxmbr:
1750         mbrcode = '/usr/lib/syslinux/mbr.bin'
1751     elif options.mbrmenu:
1752         mbrcode = GRML2USB_BASE + '/mbr/mbrldr'
1753
1754     try:
1755         install_mbr(mbrcode, mbr_device, partition_number, True)
1756     except IOError, error:
1757         logging.critical("Execution failed: %s", error)
1758         sys.exit(1)
1759     except Exception, error:
1760         logging.critical("Execution failed: %s", error)
1761         sys.exit(1)
1762
1763
1764 def handle_vfat(device):
1765     """Check for FAT specific settings and options
1766
1767     @device: device that should checked / formated"""
1768
1769     # make sure we have mkfs.vfat available
1770     if options.fat16:
1771         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1772             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1773             logging.critical('Please make sure to install dosfstools.')
1774             sys.exit(1)
1775
1776         if options.force:
1777             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1778         else:
1779             # make sure the user is aware of what he is doing
1780             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1781             if f == "y" or f == "Y":
1782                 logging.info("Note: you can skip this question using the option --force")
1783             else:
1784                 sys.exit(1)
1785         try:
1786             mkfs_fat16(device)
1787         except CriticalException, error:
1788             logging.critical("Execution failed: %s", error)
1789             sys.exit(1)
1790
1791     # check for vfat filesystem
1792     if device is not None and not os.path.isdir(device) and options.syslinux:
1793         try:
1794             check_for_fat(device)
1795         except CriticalException, error:
1796             logging.critical("Execution failed: %s", error)
1797             sys.exit(1)
1798
1799     if not os.path.isdir(device) and not check_for_usbdevice(device) and not options.force:
1800         print "Warning: the specified device %s does not look like a removable usb device." % device
1801         f = raw_input("Do you really want to continue? y/N ")
1802         if f == "y" or f == "Y":
1803             pass
1804         else:
1805             sys.exit(1)
1806
1807
1808 def handle_compat_warning(device):
1809     """Backwards compatible checks
1810
1811     @device: device that should be checked"""
1812
1813     # make sure we can replace old grml2usb script and warn user when using old way of life:
1814     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1815         print "Warning: the semantics of grml2usb has changed."
1816         print "Instead of using grml2usb /path/to/iso %s you might" % device
1817         print "want to use grml2usb /path/to/iso /dev/... instead."
1818         print "Please check out the grml2usb manpage for details."
1819         f = raw_input("Do you really want to continue? y/N ")
1820         if f == "y" or f == "Y":
1821             pass
1822         else:
1823             sys.exit(1)
1824
1825
1826 def handle_logging():
1827     """Log handling and configuration"""
1828
1829     if options.verbose and options.quiet:
1830         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1831
1832     if options.verbose:
1833         FORMAT = "Debug: %(asctime)-15s %(message)s"
1834         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1835     elif options.quiet:
1836         FORMAT = "Critical: %(message)s"
1837         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1838     else:
1839         FORMAT = "%(message)s"
1840         logging.basicConfig(level=logging.INFO, format=FORMAT)
1841
1842
1843 def handle_bootloader(device):
1844     """wrapper for installing bootloader
1845
1846     @device: device where bootloader should be installed to"""
1847
1848     # Install bootloader only if not using the --copy-only option
1849     if options.copyonly:
1850         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1851     elif os.path.isdir(device):
1852         logging.info("Not installing bootloader as %s is a directory.", device)
1853     else:
1854         install_bootloader(device)
1855
1856
1857 def check_options(opts):
1858     """Check compability of provided user opts
1859
1860     @opts option dict from OptionParser
1861     """
1862     if opts.grubmbr and not opts.grub:
1863         logging.critical("Error: --grub-mbr requires --grub option.")
1864         sys.exit(1)
1865
1866
1867 def check_programs():
1868     """check if all needed programs are installed"""
1869     if options.grub:
1870         if not which("grub-install"):
1871             logging.critical("Fatal: grub-install not available (please install the "
1872                              + "grub package or drop the --grub option)")
1873             sys.exit(1)
1874
1875     if options.syslinux:
1876         if not which("syslinux"):
1877             logging.critical("Fatal: syslinux not available (please install the "
1878                              + "syslinux package or use the --grub option)")
1879             sys.exit(1)
1880
1881     if not which("rsync"):
1882         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1883         sys.exit(1)
1884
1885 def main():
1886     """Main function [make pylint happy :)]"""
1887
1888     if options.version:
1889         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1890         sys.exit(0)
1891
1892     if len(args) < 2:
1893         parser.error("invalid usage")
1894
1895     # log handling
1896     handle_logging()
1897
1898     # make sure we have the appropriate permissions
1899     check_uid_root()
1900
1901     check_options(options)
1902
1903     logging.info("Executing grml2usb version %s", PROG_VERSION)
1904
1905     if options.dryrun:
1906         logging.info("Running in simulation mode as requested via option dry-run.")
1907
1908     check_programs()
1909
1910     # specified arguments
1911     device = args[len(args) - 1]
1912     isos = args[0:len(args) - 1]
1913
1914     if not os.path.isdir(device):
1915         if device[-1:].isdigit():
1916             if int(device[-1:]) > 4 or device[-2:].isdigit():
1917                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1918                 sys.exit(1)
1919
1920     # provide upgrade path
1921     handle_compat_warning(device)
1922
1923     # check for vfat partition
1924     handle_vfat(device)
1925
1926     # main operation (like installing files)
1927     for iso in isos:
1928         install(iso, device)
1929
1930     # install mbr
1931     is_superfloppy = not device[-1:].isdigit()
1932     if is_superfloppy:
1933         logging.info("Detected superfloppy format - not installing MBR")
1934
1935     if not options.skipmbr and not os.path.isdir(device) and not is_superfloppy:
1936         handle_mbr(device)
1937
1938     handle_bootloader(device)
1939
1940     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
1941
1942     for flavour in GRML_FLAVOURS:
1943         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
1944
1945     # finally be politely :)
1946     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
1947
1948
1949 if __name__ == "__main__":
1950     try:
1951         main()
1952     except KeyboardInterrupt:
1953         logging.info("Received KeyboardInterrupt")
1954         cleanup()
1955
1956 ## END OF FILE #################################################################
1957 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8