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