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