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