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