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