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