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