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