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