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