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