fe3b9210509ece8a32127674c92f64ea887d91ec
[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 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 %(grml_flavour)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 } )
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                 # Thanks to grub2. NOT.
578                 if options.grubmbr:
579                     logging.debug("Using option --grub-mbr ...")
580                     if device[-1:].isdigit():
581                         grub_device = re.match(r'(.*?)\d*$', device).group(1)
582                     else:
583                         grub_device = device
584                 else:
585                     grub_device = device
586
587                 logging.info("Installing grub as bootloader")
588                 logging.debug("grub-install --recheck --no-floppy --root-directory=%s %s",
589                     device_mountpoint, grub_device)
590                 proc = subprocess.Popen(["grub-install", "--recheck", "--no-floppy",
591                     "--root-directory=%s" % device_mountpoint, grub_device], stdout=file(os.devnull, "r+"))
592                 proc.wait()
593                 if proc.returncode != 0:
594                     # raise Exception("error executing grub-install")
595                     logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ or drop the --grub option)")
596                     logging.critical("Note:  if using grub2 consider using the --grub-mbr option because grub2's PBR feature is broken.")
597                     cleanup()
598                     sys.exit(1)
599             except CriticalException, error:
600                 logging.critical("Fatal: %s", error)
601                 cleanup()
602                 sys.exit(1)
603
604         finally:
605             unmount(device_mountpoint, "")
606             os.rmdir(device_mountpoint)
607             unregister_tmpfile(device_mountpoint)
608
609
610 def install_syslinux(device):
611     """Install syslinux on specified device.
612
613     @device: partition where syslinux should be installed to"""
614
615     if options.dryrun:
616         logging.info("Would install syslinux as bootloader on %s", device)
617         return 0
618
619     # syslinux -d boot/isolinux /dev/sdb1
620     logging.info("Installing syslinux as bootloader")
621     logging.debug("syslinux -d boot/syslinux %s", device)
622     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
623     proc.wait()
624     if proc.returncode != 0:
625         raise CriticalException("Error executing syslinux (either try --fat16 or use grub?)")
626
627
628 def install_bootloader(device):
629     """Install bootloader on specified device.
630
631     @device: partition where bootloader should be installed to"""
632
633     # by default we use grub, so install syslinux only on request
634     if options.syslinux:
635         logging.info("Note: the --syslinux option is deprecated as syslinux is grml2usb's default. Continuing anyway.")
636
637     if options.grub:
638         if not which("grub-install"):
639             logging.critical("Fatal: grub-install not available (please install the grub package or use the --syslinux option)")
640             cleanup()
641             sys.exit(1)
642         else:
643             try:
644                 install_grub(device)
645             except CriticalException, error:
646                 logging.critical("Fatal: %s", error)
647                 cleanup()
648                 sys.exit(1)
649     else:
650         try:
651             install_syslinux(device)
652         except CriticalException, error:
653             logging.critical("Fatal: %s", error)
654             cleanup()
655             sys.exit(1)
656
657
658 def execute_lilo(lilo, device):
659     """execute lilo for activating the partitions in the MBR
660
661     @lilo: path of lilo executable
662     @device: device where lilo should be executed on"""
663
664     # to support -A for extended partitions:
665     logging.info("Activating partitions in MBR via lilo")
666     logging.debug("%s -S /dev/null -M %s ext", lilo, device)
667     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
668     proc.wait()
669     if proc.returncode != 0:
670         raise Exception("error executing lilo")
671
672     # activate partition:
673     logging.debug("%s -S /dev/null -A %s 1", lilo, device)
674     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
675     proc.wait()
676     if proc.returncode != 0:
677         raise Exception("error executing lilo")
678
679
680 def install_syslinux_mbr(device):
681     """install syslinux's master boot record (MBR) on the specified device
682
683     @device: device where MBR of syslinux should be installed to"""
684
685     # make sure we have syslinux available
686     if not which("syslinux") and not options.copyonly:
687         raise Exception("syslinux not available (either install it or consider using the --grub option)")
688
689     # lilo's mbr is broken, use the one from syslinux instead:
690     if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
691         raise Exception("/usr/lib/syslinux/mbr.bin can not be read")
692
693     logging.info("Installing syslinux MBR")
694     logging.debug("cat /usr/lib/syslinux/mbr.bin > %s", device)
695     try:
696         # TODO -> use Popen instead?
697         retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
698         if retcode < 0:
699             logging.critical("Error copying MBR to device (%s)", retcode)
700     except OSError, error:
701         logging.critical("Execution failed: %s", error)
702
703
704 def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
705     """install 'mbr' master boot record (MBR) on a device
706
707     Retrieve the partition table from "device", install an MBR from the
708     "mbrtemplate" file, set the "partition" (0..3) active, and install the
709     result back to "device".
710
711     @mbrtemplate: default MBR file
712
713     @device: name of a file assumed to be a hard disc (or USB stick) image, or
714     something like "/dev/sdb"
715
716     @partition: must be a number between 0 and 3, inclusive
717
718     @mbrtemplate: must be a valid MBR file of at least 440 (or 439 if
719     ismirbsdmbr) bytes.
720
721     @ismirbsdmbr: if true then ignore the active flag, set the mirbsdmbr
722     specific flag to 0/1/2/3 and set the MBR's default value accordingly. If
723     false then leave the mirbsdmbr specific flag set to FFh, set all
724     active flags to 0 and set the active flag of the partition to 80h.  Note:
725     behaviour of mirbsdmbr: if flag = 0/1/2/3 then use it, otherwise search for
726     the active flag."""
727
728     logging.info("Installing default MBR")
729
730     if not os.path.isfile(mbrtemplate):
731         logging.critical("Error: %s can not be read.", mbrtemplate)
732         raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file?)")
733
734     if (partition < 0) or (partition > 3):
735         raise ValueError("partition must be between 0 and 3")
736
737     if ismirbsdmbr:
738         nmbrbytes = 439
739     else:
740         nmbrbytes = 440
741
742     tmpf = tempfile.NamedTemporaryFile()
743
744     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1", device, tmpf.name)
745     proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"], stderr=file(os.devnull, "r+"))
746     proc.wait()
747     if proc.returncode != 0:
748         raise Exception("error executing dd (first run)")
749
750     logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc", mbrtemplate,
751         tmpf.name, nmbrbytes)
752     proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes,
753         "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
754     proc.wait()
755     if proc.returncode != 0:
756         raise Exception("error executing dd (second run)")
757
758     mbrcode = tmpf.file.read(512)
759     if len(mbrcode) < 512:
760         raise EOFError("MBR size (%d) < 512" % len(mbrcode))
761
762     if ismirbsdmbr:
763         mbrcode = mbrcode[0:439] + chr(partition) + \
764           mbrcode[440:510] + "\x55\xAA"
765     else:
766         actives = ["\x00", "\x00", "\x00", "\x00"]
767         actives[partition] = "\x80"
768         mbrcode = mbrcode[0:446] + actives[0] + \
769           mbrcode[447:462] + actives[1] + \
770           mbrcode[463:478] + actives[2] + \
771           mbrcode[479:494] + actives[3] + \
772           mbrcode[495:510] + "\x55\xAA"
773
774     tmpf.file.seek(0)
775     tmpf.file.truncate()
776     tmpf.file.write(mbrcode)
777     tmpf.file.close()
778
779     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc", tmpf.name, device)
780     proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1",
781                             "conv=notrunc"], stderr=file(os.devnull, "r+"))
782     proc.wait()
783     if proc.returncode != 0:
784         raise Exception("error executing dd (third run)")
785     del tmpf
786
787
788 def handle_syslinux_mbr(device):
789     """Install syslinux master boot record on given device
790
791     @device: device where MBR should be installed to"""
792
793     if not is_writeable(device):
794         raise IOError("device not writeable for user")
795
796     # try to use system's lilo
797     if which("lilo"):
798         lilo = which("lilo")
799     else:
800         # otherwise fall back to our static version
801         from platform import architecture
802         if architecture()[0] == '64bit':
803             lilo = GRML2USB_BASE + '/lilo/lilo.static.amd64'
804         else:
805             lilo = GRML2USB_BASE + '/lilo/lilo.static.i386'
806     # finally prefer a specified lilo executable
807     if options.lilobin:
808         lilo = options.lilobin
809
810     if not is_exe(lilo):
811         raise Exception("lilo executable can not be execute")
812
813     if options.dryrun:
814         logging.info("Would install MBR running lilo and using syslinux.")
815         return 0
816
817     execute_lilo(lilo, device)
818     install_syslinux_mbr(device)
819
820
821 def is_writeable(device):
822     """Check if the device is writeable for the current user
823
824     @device: partition where bootloader should be installed to"""
825
826     if not device:
827         return False
828         #raise Exception("no device for checking write permissions")
829
830     if not os.path.exists(device):
831         return False
832
833     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
834
835
836 def mount(source, target, mount_options):
837     """Mount specified source on given target
838
839     @source: name of device/ISO that should be mounted
840     @target: directory where the ISO should be mounted to
841     @options: mount specific options"""
842
843     # note: options.dryrun does not work here, as we have to
844     # locate files and identify the grml flavour
845
846     for x in file('/proc/mounts').readlines():
847         if x.startswith(source):
848             raise CriticalException("Error executing mount: %s already mounted - please unmount before invoking grml2usb" % source)
849
850     if os.path.isdir(source):
851         logging.debug("Source %s is not a device, therefore not mounting.", source)
852         return 0
853
854     logging.debug("mount %s %s %s", mount_options, source, target)
855     proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target])
856     proc.wait()
857     if proc.returncode != 0:
858         raise CriticalException("Error executing mount (no filesystem on the partition?)")
859     else:
860         logging.debug("register_mountpoint(%s)", target)
861         register_mountpoint(target)
862
863
864 def unmount(target, unmount_options):
865     """Unmount specified target
866
867     @target: target where something is mounted on and which should be unmounted
868     @options: options for umount command"""
869
870     # make sure we unmount only already mounted targets
871     target_unmount = False
872     mounts = open('/proc/mounts').readlines()
873     mountstring = re.compile(".*%s.*" % re.escape(os.path.realpath(target)))
874     for line in mounts:
875         if re.match(mountstring, line):
876             target_unmount = True
877
878     if not target_unmount:
879         logging.debug("%s not mounted anymore", target)
880     else:
881         logging.debug("umount %s %s", list(unmount_options), target)
882         proc = subprocess.Popen(["umount"] + list(unmount_options) + [target])
883         proc.wait()
884         if proc.returncode != 0:
885             raise Exception("Error executing umount")
886         else:
887             logging.debug("unregister_mountpoint(%s)", target)
888             unregister_mountpoint(target)
889
890
891 def check_for_usbdevice(device):
892     """Check whether the specified device is a removable USB device
893
894     @device: device name, like /dev/sda1 or /dev/sda
895     """
896
897     usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
898     # newer systems:
899     usbdev = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
900     if not os.path.isfile(usbdev):
901         # Ubuntu with kernel 2.6.24 for example:
902         usbdev = os.path.realpath('/sys/block/' + usbdevice + '/removable')
903
904     if os.path.isfile(usbdev):
905         is_usb = open(usbdev).readline()
906         if is_usb.find("1"):
907             return 0
908
909     return 1
910
911
912 def check_for_fat(partition):
913     """Check whether specified partition is a valid VFAT/FAT16 filesystem
914
915     @partition: device name of partition"""
916
917     try:
918         udev_info = subprocess.Popen(["/sbin/blkid", "-s", "TYPE", "-o", "value", partition],
919                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
920         filesystem = udev_info.communicate()[0].rstrip()
921
922         if udev_info.returncode == 2:
923             raise CriticalException("Failed to read device %s"
924                                     " (wrong UID/permissions or device/directory not present?)" % partition)
925
926         if options.syslinux and filesystem != "vfat":
927             raise CriticalException("Partition %s does not contain a FAT16 filesystem. (Use --fat16 or run mkfs.vfat %s)" % (partition, partition))
928
929     except OSError:
930         raise CriticalException("Sorry, /sbin/blkid not available (install e2fsprogs?)")
931
932
933 def mkdir(directory):
934     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
935
936     # just silently pass as it's just fine it the directory exists
937     if not os.path.isdir(directory):
938         try:
939             os.makedirs(directory)
940         # pylint: disable-msg=W0704
941         except OSError:
942             pass
943
944
945 def exec_rsync(source, target):
946     """Simple wrapper around rsync to install files
947
948     @source: source file/directory
949     @target: target file/directory"""
950     logging.debug("Source: %s / Target: %s", source, target)
951     proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", source, target])
952     proc.wait()
953     if proc.returncode == 12:
954         logging.critical("Fatal: No space left on device")
955         cleanup()
956         sys.exit(1)
957
958     if proc.returncode != 0:
959         logging.critical("Fatal: could not install %s", source)
960         cleanup()
961         sys.exit(1)
962
963
964 def copy_system_files(grml_flavour, iso_mount, target):
965     """copy grml's main files (like squashfs, kernel and initrd) to a given target
966
967     @grml_flavour: name of grml flavour the configuration should be generated for
968     @iso_mount: path where a grml ISO is mounted on
969     @target: path where grml's main files should be copied to"""
970
971     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
972     if squashfs is None:
973         logging.critical("Fatal: squashfs file not found")
974         raise CriticalException("error locating squashfs file")
975     else:
976         squashfs_target = target + '/live/' + grml_flavour + '/'
977         execute(mkdir, squashfs_target)
978     exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs')
979
980     filesystem_module = search_file('filesystem.module', iso_mount)
981     if filesystem_module is None:
982         logging.critical("Fatal: filesystem.module not found")
983         raise CriticalException("error locating filesystem.module file")
984     else:
985         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
986
987     release_target = target + '/boot/release/' + grml_flavour
988     execute(mkdir, release_target)
989
990     kernel = search_file('linux26', iso_mount)
991     if kernel is None:
992         logging.critical("Fatal kernel not found")
993         raise CriticalException("error locating kernel file")
994     else:
995         exec_rsync(kernel, release_target + '/linux26')
996
997     initrd = search_file('initrd.gz', iso_mount)
998     if initrd is None:
999         logging.critical("Fatal: initrd not found")
1000         raise CriticalException("error locating initrd file")
1001     else:
1002         exec_rsync(initrd, release_target + '/initrd.gz')
1003
1004
1005 def copy_grml_files(iso_mount, target):
1006     """copy some minor grml files to a given target
1007
1008     @iso_mount: path where a grml ISO is mounted on
1009     @target: path where grml's main files should be copied to"""
1010
1011     grml_target = target + '/grml/'
1012     execute(mkdir, grml_target)
1013
1014     for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
1015         grml_file = search_file(myfile, iso_mount)
1016         if grml_file is None:
1017             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
1018         else:
1019             exec_rsync(grml_file, grml_target + myfile)
1020
1021     grml_web_target = grml_target + '/web/'
1022     execute(mkdir, grml_web_target)
1023
1024     for myfile in 'index.html', 'style.css':
1025         grml_file = search_file(myfile, iso_mount)
1026         if grml_file is None:
1027             logging.warn("Warning: myfile %s could not be found - can not install it")
1028         else:
1029             exec_rsync(grml_file, grml_web_target + myfile)
1030
1031     grml_webimg_target = grml_web_target + '/images/'
1032     execute(mkdir, grml_webimg_target)
1033
1034     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1035         grml_file = search_file(myfile, iso_mount)
1036         if grml_file is None:
1037             logging.warn("Warning: myfile %s could not be found - can not install it")
1038         else:
1039             exec_rsync(grml_file, grml_webimg_target + myfile)
1040
1041
1042 def copy_addons(iso_mount, target):
1043     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1044
1045     @iso_mount: path where a grml ISO is mounted on
1046     @target: path where grml's main files should be copied to"""
1047
1048     addons = target + '/boot/addons/'
1049     execute(mkdir, addons)
1050
1051     # grub all-in-one image
1052     allinoneimg = search_file('allinone.img', iso_mount)
1053     if allinoneimg is None:
1054         logging.warn("Warning: allinone.img not found (that's fine if you don't need it)")
1055     else:
1056         exec_rsync(allinoneimg, addons + 'allinone.img')
1057
1058     # bsd imag
1059     bsdimg = search_file('bsd4grml', iso_mount)
1060     if bsdimg is None:
1061         logging.warn("Warning: bsd4grml not found (that's fine if you don't need it)")
1062     else:
1063         exec_rsync(bsdimg, addons + '/')
1064
1065     # freedos image
1066     balderimg = search_file('balder10.imz', iso_mount)
1067     if balderimg is None:
1068         logging.warn("Warning: balder10.imz not found (that's fine if you don't need it)")
1069     else:
1070         exec_rsync(balderimg, addons + 'balder10.imz')
1071
1072     # install hdt and pci.ids only when using syslinux (grub doesn't support it)
1073     if options.syslinux:
1074         # hdt (hardware detection tool) image
1075         hdtimg = search_file('hdt.c32', iso_mount)
1076         if hdtimg:
1077             exec_rsync(hdtimg, addons + '/hdt.c32')
1078
1079         # pci.ids file
1080         picids = search_file('pci.ids', iso_mount)
1081         if picids:
1082             exec_rsync(picids, addons + '/pci.ids')
1083
1084     # memdisk image
1085     memdiskimg = search_file('memdisk', iso_mount)
1086     if memdiskimg is None:
1087         logging.warn("Warning: memdisk not found (that's fine if you don't need it)")
1088     else:
1089         exec_rsync(memdiskimg, addons + 'memdisk')
1090
1091     # memtest86+ image
1092     memtestimg = search_file('memtest', iso_mount)
1093     if memtestimg is None:
1094         logging.warn("Warning: memtest not found (that's fine if you don't need it)")
1095     else:
1096         exec_rsync(memtestimg, addons + 'memtest')
1097
1098
1099 def copy_bootloader_files(iso_mount, target):
1100     """copy grml's bootloader files to a given target
1101
1102     @iso_mount: path where a grml ISO is mounted on
1103     @target: path where grml's main files should be copied to"""
1104
1105     syslinux_target = target + '/boot/syslinux/'
1106     execute(mkdir, syslinux_target)
1107
1108     logo = search_file('logo.16', iso_mount)
1109     exec_rsync(logo, syslinux_target + 'logo.16')
1110
1111     for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
1112         bootsplash = search_file(ffile, iso_mount)
1113         exec_rsync(bootsplash, syslinux_target + ffile)
1114
1115     # avoid the "file is read only, overwrite anyway (y/n) ?" question
1116     # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file")
1117     if os.path.isfile(syslinux_target + 'ldlinux.sys'):
1118         os.unlink(syslinux_target + 'ldlinux.sys')
1119
1120     if not search_file('default.cfg', iso_mount + '/boot/isolinux/'):
1121         logging.critical("Fatal: file default.cfg could not be found.")
1122         logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
1123         logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
1124         raise
1125
1126     for filename in 'addons.cfg', 'default.cfg', 'distri.cfg', \
1127                     'grml.cfg', 'grml.png', 'hd.cfg', 'isoprompt.cfg', 'options.cfg', \
1128                     'vesamenu.c32', 'vesamenu.cfg', 'grml.png':
1129         path = search_file(filename, iso_mount + '/boot/isolinux/')
1130         exec_rsync(path, syslinux_target + filename)
1131
1132     path = search_file('hidden.cfg', iso_mount + '/boot/isolinux/')
1133     exec_rsync(path, syslinux_target + "new-" + 'hidden.cfg')
1134
1135
1136     grub_target = target + '/boot/grub/'
1137     execute(mkdir, grub_target)
1138
1139     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1140         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1141         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1142         raise
1143     else:
1144         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1145
1146     # grml splash in grub
1147     if os.path.isfile(GRML2USB_BASE + "/grub/grml.png"):
1148         exec_rsync(GRML2USB_BASE + '/grub/grml.png', grub_target + 'grml.png')
1149
1150     # font file for graphical bootsplash in grub
1151     if os.path.isfile("/usr/share/grub/ascii.pf2"):
1152         exec_rsync('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1153
1154
1155 def install_iso_files(grml_flavour, iso_mount, device, target):
1156     """Copy files from ISO to given target
1157
1158     @grml_flavour: name of grml flavour the configuration should be generated for
1159     @iso_mount: path where a grml ISO is mounted on
1160     @device: device/partition where bootloader should be installed to
1161     @target: path where grml's main files should be copied to"""
1162
1163     # TODO => several improvements:
1164     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1165     # * provide alternative search_file() if file information is stored in a config.ini file?
1166     # * catch "install: .. No space left on device" & CO
1167
1168     if options.dryrun:
1169         return 0
1170     elif not options.bootloaderonly:
1171         logging.info("Copying files. This might take a while....")
1172         try:
1173             copy_system_files(grml_flavour, iso_mount, target)
1174             copy_grml_files(iso_mount, target)
1175         except CriticalException, error:
1176             logging.critical("Execution failed: %s", error)
1177             sys.exit(1)
1178
1179     if not options.skipaddons:
1180         if grml_flavour.endswith('-small'):
1181             logging.info("Note: grml-small doesn't provide any addons, not installing them therefore.")
1182         else:
1183             copy_addons(iso_mount, target)
1184
1185     if not options.copyonly:
1186         copy_bootloader_files(iso_mount, target)
1187
1188         if not options.dryrun:
1189             handle_bootloader_config(grml_flavour, device, target)
1190
1191     # make sure we sync filesystems before returning
1192     proc = subprocess.Popen(["sync"])
1193     proc.wait()
1194
1195
1196 def uninstall_files(device):
1197     """Get rid of all grml files on specified device
1198
1199     @device: partition where grml2usb files should be removed from"""
1200
1201     # TODO - not implemented yet
1202     logging.critical("TODO: uninstalling files from %s not yet implement, sorry.", device)
1203
1204
1205 def identify_grml_flavour(mountpath):
1206     """Get name of grml flavour
1207
1208     @mountpath: path where the grml ISO is mounted to
1209     @return: name of grml-flavour"""
1210
1211     version_file = search_file('grml-version', mountpath)
1212
1213     if version_file == "":
1214         logging.critical("Error: could not find grml-version file.")
1215         raise
1216
1217     try:
1218         tmpfile = open(version_file, 'r')
1219         grml_info = tmpfile.readline()
1220         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1221     except TypeError:
1222         raise
1223     except Exception, e:
1224         logging.critical("Unexpected error: %s", e)
1225         raise
1226
1227     return grml_flavour
1228
1229
1230 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1231     """Main handler for generating grub1 configuration
1232
1233     @grml_flavour: name of grml flavour the configuration should be generated for
1234     @install_partition: partition number for use in (hd0,X)
1235     @grub_target: path of grub's configuration files
1236     @bootoptions: additional bootoptions that should be used by default"""
1237
1238     # grub1 config
1239     grub1_cfg = grub_target + 'menu.lst'
1240     logging.debug("Creating grub1 configuration file (menu.lst)")
1241
1242     # install main configuration only *once*, no matter how many ISOs we have:
1243     if os.path.isfile(grub1_cfg):
1244         string = open(grub1_cfg).readline()
1245         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1246         if not re.match(main_identifier, string):
1247             grub1_config_file = open(grub1_cfg, 'w')
1248             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1249             grub1_config_file.close()
1250     else:
1251         grub1_config_file = open(grub1_cfg, 'w')
1252         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1253         grub1_config_file.close()
1254
1255     grub_flavour_config = True
1256     if os.path.isfile(grub1_cfg):
1257         string = open(grub1_cfg).readlines()
1258         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1259         for line in string:
1260             if flavour.match(line):
1261                 grub_flavour_config = False
1262
1263     if grub_flavour_config:
1264         grub1_config_file = open(grub1_cfg, 'a')
1265         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1266         grub1_config_file.close()
1267
1268     # make sure grub.conf isn't a symlink but a plain file instead,
1269     # otherwise it will break on FAT16 filesystems
1270     # this works around grub-install of (at least) Fedora 10
1271     if os.path.isfile(grub1_cfg):
1272         grubconf = grub_target + 'grub.conf'
1273         if not os.path.islink(grubconf):
1274             import shutil
1275             shutil.copyfile(grub1_cfg, grub_target + 'grub.conf')
1276
1277 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1278     """Main handler for generating grub2 configuration
1279
1280     @grml_flavour: name of grml flavour the configuration should be generated for
1281     @grub_target: path of grub's configuration files
1282     @bootoptions: additional bootoptions that should be used by default"""
1283
1284     # grub2 config
1285     grub2_cfg = grub_target + 'grub.cfg'
1286     logging.debug("Creating grub2 configuration file (grub.lst)")
1287
1288     global GRML_DEFAULT
1289
1290     # install main configuration only *once*, no matter how many ISOs we have:
1291     grub_flavour_is_default = False
1292     if os.path.isfile(grub2_cfg):
1293         string = open(grub2_cfg).readline()
1294         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1295         if not re.match(main_identifier, string):
1296             grub2_config_file = open(grub2_cfg, 'w')
1297             GRML_DEFAULT = grml_flavour
1298             grub_flavour_is_default = True
1299             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1300             grub2_config_file.close()
1301     else:
1302         grub2_config_file = open(grub2_cfg, 'w')
1303         GRML_DEFAULT = grml_flavour
1304         grub_flavour_is_default = True
1305         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1306         grub2_config_file.close()
1307
1308     # install flavour specific configuration only *once* as well
1309     grub_flavour_config = True
1310     if os.path.isfile(grub2_cfg):
1311         string = open(grub2_cfg).readlines()
1312         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1313         for line in string:
1314             if flavour.match(line):
1315                 grub_flavour_config = False
1316
1317     if grub_flavour_config:
1318         grub2_config_file = open(grub2_cfg, 'a')
1319         # display only if the grml flavour isn't the default
1320         if not grub_flavour_is_default:
1321             GRML_FLAVOURS.add(grml_flavour)
1322         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1323         grub2_config_file.close()
1324
1325
1326 def handle_grub_config(grml_flavour, device, target):
1327     """Main handler for generating grub (v1 and v2) configuration
1328
1329     @grml_flavour: name of grml flavour the configuration should be generated for
1330     @device: device/partition where grub should be installed to
1331     @target: path of grub's configuration files"""
1332
1333     logging.debug("Generating grub configuration")
1334
1335     grub_target = target + '/boot/grub/'
1336     execute(mkdir, grub_target)
1337
1338     if os.path.isdir(device):
1339         install_grub1_partition = None
1340     else:
1341         if device[-1:].isdigit():
1342             install_grub1_partition = int(device[-1:]) - 1
1343         else:
1344             raise CriticalException("error validating partition schema (raw device?)")
1345
1346     # do NOT write "None" in kernel cmdline
1347     if options.bootoptions is None:
1348         bootopt = ""
1349     else:
1350         bootopt = options.bootoptions
1351
1352     # write menu.lst
1353     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1354     # write grub.cfg
1355     handle_grub2_config(grml_flavour, grub_target, bootopt)
1356
1357
1358 def initial_syslinux_config(target):
1359     """Generates intial syslinux configuration
1360
1361     @target path of syslinux's configuration files"""
1362
1363     target = target + "/"
1364     filename = target + "grmlmain.cfg"
1365     if os.path.isfile(target + "grmlmain.cfg"):
1366         return
1367     data = open(filename, "w")
1368     data.write(generate_main_syslinux_config())
1369     data.close
1370
1371     filename = target + "hiddens.cfg"
1372     data = open(filename, "w")
1373     data.write("include hidden.cfg\n")
1374     data.close()
1375
1376 def add_entry_if_not_present(filename, entry):
1377     data = open(filename, "a+")
1378     for line in data:
1379         if line == entry:
1380             break
1381     else:
1382         data.write(entry)
1383
1384     data.close()
1385
1386
1387
1388 def adjust_syslinux_bootoptions(src, flavour):
1389     append_re = re.compile("^(\s*append.*)$", re.I)
1390     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1391     # flavour_re = re.compile("(label.*)(grml\w+)")
1392     default_re = re.compile("(default.cfg)")
1393
1394     # do NOT write "None" in kernel cmdline
1395     if options.bootoptions is None:
1396         bootopt = ""
1397     else:
1398         bootopt = options.bootoptions
1399
1400     for line in fileinput.input(src, inplace=1):
1401         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour, line)
1402         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1403         line = default_re.sub(r'%s-\1' % flavour, line)
1404         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1405         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1406         sys.stdout.write(line)
1407     fileinput.close()
1408
1409 def adjust_labels(src, flavour):
1410     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1411     for line in fileinput.input(src, inplace=1):
1412         line = label_re.sub(r'\1 %s-\2' % flavour, line)
1413         sys.stdout.write(line)
1414     fileinput.close()
1415
1416
1417 def add_syslinux_entry(filename, grml_flavour):
1418     entry_filename = "option-%s.cfg" % grml_flavour
1419     entry = "include %s\n" % entry_filename
1420
1421     add_entry_if_not_present(filename, entry)
1422     path = os.path.dirname(filename)
1423
1424     data = open(path + "/" + entry_filename, "w")
1425     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1426     data.close()
1427
1428 def modify_filenames(grml_flavour, target, filenames):
1429     for filename in filenames:
1430         old_filename = "%s/%s" % (target, filename)
1431         new_filename = "%s/%s-%s" % (target, grml_flavour, filename)
1432         os.rename(old_filename, new_filename)
1433         adjust_syslinux_bootoptions(new_filename, grml_flavour)
1434
1435
1436 def handle_syslinux_config(grml_flavour, target):
1437     """Main handler for generating syslinux configuration
1438
1439     @grml_flavour: name of grml flavour the configuration should be generated for
1440     @target: path of syslinux's configuration files"""
1441
1442     # do NOT write "None" in kernel cmdline
1443     if options.bootoptions is None:
1444         bootopt = ""
1445     else:
1446         bootopt = options.bootoptions
1447
1448     logging.debug("Generating syslinux configuration")
1449     syslinux_target = target + '/boot/syslinux/'
1450     # should be present via  copy_bootloader_files(), but make sure it exits:
1451     execute(mkdir, syslinux_target)
1452     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1453
1454     global GRML_DEFAULT
1455
1456     # install main configuration only *once*, no matter how many ISOs we have:
1457     syslinux_flavour_is_default = False
1458     syslinux_config_file = open(syslinux_cfg, 'w')
1459     syslinux_config_file.write("include vesamenu.cfg")
1460     syslinux_config_file.close()
1461
1462     initial_syslinux_config(syslinux_target)
1463     modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1464
1465     filename = search_file("new-hidden.cfg", syslinux_target)
1466     # process hidden file
1467     if not search_file("hidden.cfg", syslinux_target):
1468         new_hidden = syslinux_target + "hidden.cfg"
1469         os.rename(filename, new_hidden)
1470         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1471     else:
1472         new_hidden =  "%s-hidden.cfg" % (grml_flavour)
1473         new_hidden_file =  "%s/%s" % (syslinux_target, new_hidden)
1474         os.rename(filename, new_hidden_file)
1475         adjust_labels(new_hidden_file, grml_flavour)
1476         adjust_syslinux_bootoptions(new_hidden_file, grml_flavour)
1477         entry = 'include %s\n' % new_hidden
1478         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1479
1480
1481
1482     new_default = "%s-default.cfg" % (grml_flavour)
1483     entry = 'include %s\n' % new_default
1484     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1485
1486     add_syslinux_entry("%s/additional.cfg" % syslinux_target, grml_flavour)
1487
1488
1489 def handle_bootloader_config(grml_flavour, device, target):
1490     """Main handler for generating bootloader's configuration
1491
1492     @grml_flavour: name of grml flavour the configuration should be generated for
1493     @device: device/partition where bootloader should be installed to
1494     @target: path of bootloader's configuration files"""
1495
1496     if options.skipsyslinuxconfig:
1497         logging.info("Skipping generation of syslinux configuration as requested.")
1498     else:
1499         try:
1500             handle_syslinux_config(grml_flavour, target)
1501         except CriticalException, error:
1502             logging.critical("Fatal: %s", error)
1503             sys.exit(1)
1504
1505     if options.skipgrubconfig:
1506         logging.info("Skipping generation of grub configuration as requested.")
1507     else:
1508         try:
1509             handle_grub_config(grml_flavour, device, target)
1510         except CriticalException, error:
1511             logging.critical("Fatal: %s", error)
1512             sys.exit(1)
1513
1514
1515 def handle_dir(live_image, device):
1516     """Main logic for copying files of the currently running grml system.
1517
1518     @live_image: directory where currently running live system resides (usually /live/image)
1519     @device: partition where the specified ISO should be installed to"""
1520
1521     logging.info("Using %s as install base", live_image)
1522
1523     if os.path.isdir(device):
1524         logging.info("Specified target is a directory, therefore not mounting.")
1525         device_mountpoint = device
1526         remove_device_mountpoint = False
1527     else:
1528         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1529         register_tmpfile(device_mountpoint)
1530         remove_device_mountpoint = True
1531         try:
1532             mount(device, device_mountpoint, "")
1533         except CriticalException, error:
1534             logging.critical("Fatal: %s", error)
1535             cleanup()
1536             sys.exit(1)
1537
1538     try:
1539         try:
1540             grml_flavour = identify_grml_flavour(live_image)
1541             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1542             install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1543         except TypeError:
1544             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1545             sys.exit(1)
1546     finally:
1547         if remove_device_mountpoint:
1548             try:
1549                 unmount(device_mountpoint, "")
1550                 if os.path.isdir(device_mountpoint):
1551                     os.rmdir(device_mountpoint)
1552                     unregister_tmpfile(device_mountpoint)
1553             except CriticalException, error:
1554                 logging.critical("Fatal: %s", error)
1555                 cleanup()
1556
1557
1558 def handle_iso(iso, device):
1559     """Main logic for mounting ISOs and copying files.
1560
1561     @iso: full path to the ISO that should be installed to the specified device
1562     @device: partition where the specified ISO should be installed to"""
1563
1564     logging.info("Using ISO %s", iso)
1565
1566     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1567     register_tmpfile(iso_mountpoint)
1568     remove_iso_mountpoint = True
1569
1570     if not os.path.isfile(iso):
1571         logging.critical("Fatal: specified ISO %s could not be read", iso)
1572         cleanup()
1573         sys.exit(1)
1574
1575     try:
1576         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1577     except CriticalException, error:
1578         logging.critical("Fatal: %s", error)
1579         sys.exit(1)
1580
1581     if os.path.isdir(device):
1582         logging.info("Specified target is a directory, therefore not mounting.")
1583         device_mountpoint = device
1584         remove_device_mountpoint = False
1585         # skip_mbr = True
1586     else:
1587         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1588         register_tmpfile(device_mountpoint)
1589         remove_device_mountpoint = True
1590         try:
1591             mount(device, device_mountpoint, "")
1592         except CriticalException, error:
1593             logging.critical("Fatal: %s", error)
1594             cleanup()
1595             sys.exit(1)
1596
1597     try:
1598         try:
1599             grml_flavour = identify_grml_flavour(iso_mountpoint)
1600             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1601             install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1602         except TypeError:
1603             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1604             sys.exit(1)
1605     finally:
1606         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1607             unmount(iso_mountpoint, "")
1608             os.rmdir(iso_mountpoint)
1609             unregister_tmpfile(iso_mountpoint)
1610         if remove_device_mountpoint:
1611             try:
1612                 unmount(device_mountpoint, "")
1613                 if os.path.isdir(device_mountpoint):
1614                     os.rmdir(device_mountpoint)
1615                     unregister_tmpfile(device_mountpoint)
1616             except CriticalException, error:
1617                 logging.critical("Fatal: %s", error)
1618                 cleanup()
1619
1620
1621 def handle_mbr(device):
1622     """Main handler for installing master boot record (MBR)
1623
1624     @device: device where the MBR should be installed to"""
1625
1626     if options.dryrun:
1627         logging.info("Would install MBR")
1628         return 0
1629
1630     if device[-1:].isdigit():
1631         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1632         partition_number = int(device[-1:]) - 1
1633         skip_install_mir_mbr = False
1634
1635     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1636     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1637     if mbr_device == "/dev/loop":
1638         mbr_device = device
1639         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1640         skip_install_mir_mbr = True
1641
1642     try:
1643         if options.syslinuxmbr:
1644             handle_syslinux_mbr(mbr_device)
1645         elif not skip_install_mir_mbr:
1646             if options.mbrmenu:
1647                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True)
1648             else:
1649                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False)
1650     except IOError, error:
1651         logging.critical("Execution failed: %s", error)
1652         sys.exit(1)
1653     except Exception, error:
1654         logging.critical("Execution failed: %s", error)
1655         sys.exit(1)
1656
1657
1658 def handle_vfat(device):
1659     """Check for FAT specific settings and options
1660
1661     @device: device that should checked / formated"""
1662
1663     # make sure we have mkfs.vfat available
1664     if options.fat16:
1665         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1666             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1667             logging.critical('Please make sure to install dosfstools.')
1668             sys.exit(1)
1669
1670         exec_mkfs = False
1671         if options.force:
1672             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1673             exec_mkfs = True
1674         else:
1675             # make sure the user is aware of what he is doing
1676             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1677             if f == "y" or f == "Y":
1678                 logging.info("Note: you can skip this question using the option --force")
1679                 exec_mkfs = True
1680
1681         if exec_mkfs:
1682             try:
1683                 mkfs_fat16(device)
1684             except CriticalException, error:
1685                 logging.critical("Execution failed: %s", error)
1686                 sys.exit(1)
1687         else:
1688             sys.exit(1)
1689
1690     # check for vfat filesystem
1691     if device is not None and not os.path.isdir(device):
1692         try:
1693             check_for_fat(device)
1694         except CriticalException, error:
1695             logging.critical("Execution failed: %s", error)
1696             sys.exit(1)
1697
1698     if not os.path.isdir(device) and not check_for_usbdevice(device):
1699         print "Warning: the specified device %s does not look like a removable usb device." % device
1700         f = raw_input("Do you really want to continue? y/N ")
1701         if f == "y" or f == "Y":
1702             pass
1703         else:
1704             sys.exit(1)
1705
1706
1707 def handle_compat_warning(device):
1708     """Backwards compatible checks
1709
1710     @device: device that should be checked"""
1711
1712     # make sure we can replace old grml2usb script and warn user when using old way of life:
1713     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1714         print "Warning: the semantics of grml2usb has changed."
1715         print "Instead of using grml2usb /path/to/iso %s you might" % device
1716         print "want to use grml2usb /path/to/iso /dev/... instead."
1717         print "Please check out the grml2usb manpage for details."
1718         f = raw_input("Do you really want to continue? y/N ")
1719         if f == "y" or f == "Y":
1720             pass
1721         else:
1722             sys.exit(1)
1723
1724
1725 def handle_logging():
1726     """Log handling and configuration"""
1727
1728     if options.verbose and options.quiet:
1729         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1730
1731     if options.verbose:
1732         FORMAT = "Debug: %(asctime)-15s %(message)s"
1733         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1734     elif options.quiet:
1735         FORMAT = "Critical: %(message)s"
1736         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1737     else:
1738         FORMAT = "%(message)s"
1739         logging.basicConfig(level=logging.INFO, format=FORMAT)
1740
1741
1742 def handle_bootloader(device):
1743     """wrapper for installing bootloader
1744
1745     @device: device where bootloader should be installed to"""
1746
1747     # Install bootloader only if not using the --copy-only option
1748     if options.copyonly:
1749         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1750     elif os.path.isdir(device):
1751         logging.info("Not installing bootloader as %s is a directory.", device)
1752     else:
1753         install_bootloader(device)
1754
1755
1756 def main():
1757     """Main function [make pylint happy :)]"""
1758
1759     if options.version:
1760         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1761         sys.exit(0)
1762
1763     if len(args) < 2:
1764         parser.error("invalid usage")
1765
1766     # log handling
1767     handle_logging()
1768
1769     # make sure we have the appropriate permissions
1770     check_uid_root()
1771
1772     logging.info("Executing grml2usb version %s", PROG_VERSION)
1773
1774     if options.dryrun:
1775         logging.info("Running in simulation mode as requested via option dry-run.")
1776
1777     # specified arguments
1778     device = args[len(args) - 1]
1779     isos = args[0:len(args) - 1]
1780
1781     if not os.path.isdir(device):
1782         if device[-1:].isdigit():
1783             if int(device[-1:]) > 4 or device[-2:].isdigit():
1784                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1785                 sys.exit(1)
1786         else:
1787             if os.path.exists(device):
1788                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1789                 sys.exit(1)
1790
1791     if not which("rsync"):
1792         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1793         sys.exit(1)
1794
1795     # provide upgrade path
1796     handle_compat_warning(device)
1797
1798     # check for vfat partition
1799     handle_vfat(device)
1800
1801     # main operation (like installing files)
1802     for iso in isos:
1803         if os.path.isdir(iso):
1804             handle_dir(iso, device)
1805         else:
1806             handle_iso(iso, device)
1807
1808     # install mbr
1809     if not os.path.isdir(device):
1810         if not options.skipmbr:
1811             handle_mbr(device)
1812
1813     handle_bootloader(device)
1814
1815     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
1816
1817     for flavour in GRML_FLAVOURS:
1818         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
1819
1820     # finally be politely :)
1821     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
1822
1823 if __name__ == "__main__":
1824     try:
1825         main()
1826     except KeyboardInterrupt:
1827         logging.info("Received KeyboardInterrupt")
1828         cleanup()
1829
1830 ## END OF FILE #################################################################
1831 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8