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