Load memtest86+ binary via 'linux16' in grub2 (otherwise it fails)
[grml2usb.git] / grml2usb
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 grml2usb
5 ~~~~~~~~
6
7 This script installs a grml system (either a running system or ISO[s]) to a USB device
8
9 :copyright: (c) 2009 by Michael Prokop <mika@grml.org>
10 :license: GPL v2 or any later version
11 :bugreports: http://grml.org/bugs/
12
13 """
14
15 # from __future__ import with_statement
16 from optparse import OptionParser
17 from inspect import isroutine, isclass
18 import datetime, logging, os, re, subprocess, sys, tempfile, time
19
20 # global variables
21 PROG_VERSION = "0.9.9"
22 MOUNTED = set()  # register mountpoints
23 TMPFILES = set() # register tmpfiles
24 DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
25 GRML_FLAVOURS = set() # which flavours are being installed?
26 global GRML_DEFAULT
27
28 # cmdline parsing
29 USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/sdX#>\n\
30 \n\
31 %prog installs grml ISO[s] to an USB device to be able to boot from it.\n\
32 Make sure you have at least one grml ISO or a running grml system (/live/image),\n\
33 grub or syslinux and root access.\n\
34 \n\
35 Run %prog --help for usage hints, further information via: man grml2usb"
36
37 # pylint: disable-msg=C0103
38 parser = OptionParser(usage=USAGE)
39 parser.add_option("--bootoptions", dest="bootoptions",
40                   action="store", type="string",
41                   help="use specified bootoptions as default")
42 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
43                   help="do not copy files but just install a bootloader")
44 parser.add_option("--copy-only", dest="copyonly", action="store_true",
45                   help="copy files only but do not install bootloader")
46 parser.add_option("--dry-run", dest="dryrun", action="store_true",
47                   help="avoid executing commands")
48 parser.add_option("--fat16", dest="fat16", action="store_true",
49                   help="format specified partition with FAT16")
50 parser.add_option("--force", dest="force", action="store_true",
51                   help="force any actions requiring manual interaction")
52 #parser.add_option("--initrd", dest="initrd", action="store", type="string",
53 #                  help="install specified initrd instead of the default [TODO - not implemented yet]")
54 #parser.add_option("--kernel", dest="kernel", action="store", type="string",
55 #                  help="install specified kernel instead of the default [TODO - not implemented yet]")
56 parser.add_option("--lilo-binary", dest="lilobin",  action="store", type="string",
57                   help="lilo executable to be used for installing MBR")
58 parser.add_option("--mbr-menu", dest="mbrmenu", action="store_true",
59                   help="enable interactive boot menu in MBR")
60 parser.add_option("--quiet", dest="quiet", action="store_true",
61                   help="do not output anything but just errors on console")
62 parser.add_option("--skip-addons", dest="skipaddons", action="store_true",
63                   help="do not install /boot/addons/ files")
64 parser.add_option("--skip-grub-config", dest="skipgrubconfig", action="store_true",
65                   help="skip generation of grub configuration files")
66 parser.add_option("--skip-mbr", dest="skipmbr", action="store_true",
67                   help="do not install a master boot record (MBR) on the device")
68 parser.add_option("--skip-syslinux-config", dest="skipsyslinuxconfig", action="store_true",
69                   help="skip generation of syslinux configuration files")
70 parser.add_option("--syslinux", dest="syslinux", action="store_true",
71                   help="install syslinux bootloader instead of grub")
72 parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true",
73                   help="install syslinux master boot record (MBR) instead of default")
74 #parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
75 #                  help="install specified squashfs file instead of the default [TODO - not implemented yet]")
76 #parser.add_option("--uninstall", dest="uninstall", action="store_true",
77 #                  help="remove grml ISO files from specified device [TODO - not implemented yet]")
78 parser.add_option("--verbose", dest="verbose", action="store_true",
79                   help="enable verbose mode")
80 parser.add_option("-v", "--version", dest="version", action="store_true",
81                   help="display version and exit")
82 (options, args) = parser.parse_args()
83
84
85 class CriticalException(Exception):
86     """Throw critical exception if the exact error is not known but fatal."
87
88     @Exception: message"""
89     pass
90
91
92 def cleanup():
93     """Cleanup function to make sure there aren't any mounted devices left behind.
94     """
95
96     logging.info("Cleaning up before exiting...")
97     proc = subprocess.Popen(["sync"])
98     proc.wait()
99
100     try:
101         for device in MOUNTED:
102             unmount(device, "")
103     # ignore: RuntimeError: Set changed size during iteration
104     except RuntimeError:
105         logging.debug('caught expection RuntimeError, ignoring')
106
107
108 def register_tmpfile(path):
109     """
110     TODO - not implemented yet
111     """
112
113     TMPFILES.add(path)
114
115
116 def unregister_tmpfile(path):
117     """
118     TODO - not implemented yet
119     """
120
121     if path in TMPFILES:
122         TMPFILES.remove(path)
123
124
125 def register_mountpoint(target):
126     """register specified target in a set() for handling clean exiting
127
128     @target: destination target of mountpoint
129     """
130
131     MOUNTED.add(target)
132
133
134 def unregister_mountpoint(target):
135     """unregister specified target in a set() for handling clean exiting
136
137     @target: destination target of mountpoint
138     """
139
140     if target in MOUNTED:
141         MOUNTED.remove(target)
142
143
144 def get_function_name(obj):
145     """Helper function for use in execute() to retrive name of a function
146
147     @obj: the function object
148     """
149     if not (isroutine(obj) or isclass(obj)):
150         obj = type(obj)
151     return obj.__module__ + '.' + obj.__name__
152
153
154 def execute(f, *exec_arguments):
155     """Wrapper for executing a command. Either really executes
156     the command (default) or when using --dry-run commandline option
157     just displays what would be executed."""
158     # usage: execute(subprocess.Popen, (["ls", "-la"]))
159     # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
160     if options.dryrun:
161         # pylint: disable-msg=W0141
162         logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, exec_arguments))))
163     else:
164         # pylint: disable-msg=W0142
165         return f(*exec_arguments)
166
167
168 def is_exe(fpath):
169     """Check whether a given file can be executed
170
171     @fpath: full path to file
172     @return:"""
173     return os.path.exists(fpath) and os.access(fpath, os.X_OK)
174
175
176 def which(program):
177     """Check whether a given program is available in PATH
178
179     @program: name of executable"""
180     fpath = os.path.split(program)[0]
181     if fpath:
182         if is_exe(program):
183             return program
184     else:
185         for path in os.environ["PATH"].split(os.pathsep):
186             exe_file = os.path.join(path, program)
187             if is_exe(exe_file):
188                 return exe_file
189
190     return None
191
192
193 def search_file(filename, search_path='/bin' + os.pathsep + '/usr/bin'):
194     """Given a search path, find file
195
196     @filename: name of file to search for
197     @search_path: path where searching for the specified filename"""
198     file_found = 0
199     paths = search_path.split(os.pathsep)
200     current_dir = '' # make pylint happy :)
201     for path in paths:
202         # pylint: disable-msg=W0612
203         for current_dir, directories, files in os.walk(path):
204             if os.path.exists(os.path.join(current_dir, filename)):
205                 file_found = 1
206                 break
207     if file_found:
208         return os.path.abspath(os.path.join(current_dir, filename))
209     else:
210         return None
211
212
213 def check_uid_root():
214     """Check for root permissions"""
215     if not os.geteuid()==0:
216         sys.exit("Error: please run this script with uid 0 (root).")
217
218
219 def mkfs_fat16(device):
220     """Format specified device with VFAT/FAT16 filesystem.
221
222     @device: partition that should be formated"""
223
224     # syslinux -d boot/isolinux /dev/sdb1
225     if options.dryrun:
226         logging.info("Would execute mkfs.vfat -F 16 %s now.", device)
227         return 0
228
229     logging.info("Formating partition with fat16 filesystem")
230     logging.debug("mkfs.vfat -F 16 %s" % device)
231     proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
232     proc.wait()
233     if proc.returncode != 0:
234         raise CriticalException("error executing mkfs.vfat")
235
236
237 def generate_main_grub2_config(grml_flavour, bootoptions):
238     """Generate grub2 configuration for use via grub.cfg
239
240     @grml_flavour: name of grml flavour the configuration should be generated for
241     @bootoptions: additional bootoptions that should be used by default"""
242
243     local_datestamp = DATESTAMP
244
245     return("""\
246 ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
247 set default=0
248 set timeout=10
249
250 insmod fat
251
252 if loadfont /boot/grub/ascii.pf2 ; then
253    insmod png
254    set gfxmode=640x480
255    insmod gfxterm
256    insmod vbe
257    if terminal_output.gfxterm ; then true ; else
258     # For backward compatibility with versions of terminal.mod that don't
259     # understand terminal_output
260     terminal gfxterm
261    fi
262 fi
263
264 if background_image /boot/grub/grml.png ; then
265   set color_normal=black/black
266   set color_highlight=red/black
267 else
268   set menu_color_normal=white/black
269   set menu_color_highlight=black/yellow
270 fi
271
272 menuentry "%(grml_flavour)s (default)" {
273     set gfxpayload=1024x768x16,1024x768
274     linux   /boot/release/%(grml_flavour)s/linux26 apm=power-off quiet boot=live nomce live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
275     initrd  /boot/release/%(grml_flavour)s/initrd.gz
276 }
277
278 menuentry "Memory test (memtest86+)" {
279     linux16   /boot/addons/memtest
280 }
281
282 menuentry "Grub - all in one image" {
283     linux   /boot/addons/memdisk
284     initrd  /boot/addons/allinone.img
285 }
286
287 menuentry "FreeDOS" {
288     linux   /boot/addons/memdisk
289     initrd  /boot/addons/balder10.imz
290 }
291
292 menuentry "MirOS BSD" {
293     multiboot   /boot/addons/bsd4grml/ldbsd.com
294 }
295
296 menuentry "Boot OS of first partition on first disk" {
297     set root=(hd0,1)
298     chainloader +1
299 }
300
301 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
302         'bootoptions': bootoptions } )
303
304
305 def generate_flavour_specific_grub2_config(grml_flavour, bootoptions):
306     """Generate grub2 configuration for use via grub.cfg
307
308     @grml_flavour: name of grml flavour the configuration should be generated for
309     @bootoptions: additional bootoptions that should be used by default"""
310
311     local_datestamp = DATESTAMP
312
313     return("""\
314 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
315 menuentry "%(grml_flavour)s" {
316     set gfxpayload=1024x768x16,1024x768
317     linux  /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce quiet 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)s-persistent" {
323     set gfxpayload=1024x768x16,1024x768
324     linux  /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce quiet persistent live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
325     initrd /boot/release/%(grml_flavour)s/initrd.gz
326 }
327
328 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
329 menuentry "%(grml_flavour)s2ram" {
330     set gfxpayload=1024x768x16,1024x768
331     linux  /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs %(bootoptions)s
332     initrd /boot/release/%(grml_flavour)s/initrd.gz
333 }
334
335 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
336 menuentry "%(grml_flavour)s-debug" {
337     set gfxpayload=1024x768x16,1024x768
338     linux /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ debug boot=live initcall_debug%(bootoptions)s
339     initrd /boot/release/%(grml_flavour)s/initrd.gz
340 }
341
342 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
343 menuentry "%(grml_flavour)s-x" {
344     set gfxpayload=1024x768x16,1024x768
345     linux  /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng %(bootoptions)s
346     initrd /boot/release/%(grml_flavour)s/initrd.gz
347 }
348
349 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
350 menuentry "%(grml_flavour)s-nofb" {
351     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
352     initrd /boot/release/%(grml_flavour)s/initrd.gz
353 }
354
355 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
356 menuentry "%(grml_flavour)s-failsafe" {
357     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
358     initrd /boot/release/%(grml_flavour)s/initrd.gz
359 }
360
361 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
362 menuentry "%(grml_flavour)s-forensic" {
363     set gfxpayload=1024x768x16,1024x768
364     linux /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ nofstab noraid nolvm noautoconfig noswap raid=noautodetect forensic readonly %(bootoptions)s
365     initrd /boot/release/%(grml_flavour)s/initrd.gz
366 }
367
368 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
369 menuentry "%(grml_flavour)s-serial" {
370     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
371     initrd /boot/release/%(grml_flavour)s/initrd.gz
372 }
373
374 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
375        'bootoptions': bootoptions } )
376
377
378 def generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootoptions):
379     """Generate grub1 configuration for use via menu.lst
380
381     @grml_flavour: name of grml flavour the configuration should be generated for
382     @install_partition: partition number for use in (hd0,X)
383     @bootoptions: additional bootoptions that should be used by default"""
384
385     local_datestamp = DATESTAMP
386
387     return("""\
388 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
389 title %(grml_flavour)s
390 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
391 initrd (hd0,%(install_partition)s)/boot/release/%(grml_flavour)s/initrd.gz
392
393 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
394 title %(grml_flavour)s-persistent
395 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
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)s2ram
400 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
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)s-debug
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/ debug boot=live initcall_debug%(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-x
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/ startx=wm-ng %(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-nofb
415 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
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-failsafe
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 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
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-forensic
425 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
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-serial
430 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
431 initrd (hd0,%(install_partition)s)/boot/release/%(grml_flavour)s/initrd.gz
432
433 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
434        'bootoptions': bootoptions, 'install_partition': install_partition } )
435
436
437 def generate_main_grub1_config(grml_flavour, install_partition, bootoptions):
438     """Generate grub1 configuration for use via menu.lst
439
440     @grml_flavour: name of grml flavour the configuration should be generated for"""
441
442     local_datestamp = DATESTAMP
443
444     return("""\
445 ## main grub1 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
446 # misc options:
447 timeout 30
448 # color red/blue green/black
449 splashimage=(hd0,%(install_partition)s)/boot/grub/splash.xpm.gz
450 foreground  = 000000
451 background  = FFCC33
452
453 # define entries:
454 title %(grml_flavour)s  - Default boot (using 1024x768 framebuffer)
455 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
456 initrd (hd0,%(install_partition)s)/boot/release/%(grml_flavour)s/initrd.gz
457
458 title Memory test (memtest86+)
459 kernel (hd0,%(install_partition)s)/boot/addons/memtest
460
461 title Grub - all in one image
462 kernel (hd0,%(install_partition)s)/boot/addons/memdisk
463 initrd (hd0,%(install_partition)s)/boot/addons/allinone.img
464
465 title FreeDOS
466 kernel (hd0,%(install_partition)s)/boot/addons/memdisk
467 initrd (hd0,%(install_partition)s)/boot/addons/balder10.imz
468
469 title MirOS BSD
470 kernel (hd0,%(install_partition)s)/boot/addons/bsd4grml/ldbsd.com
471
472 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
473        'bootoptions': bootoptions, 'install_partition': install_partition } )
474
475
476 def generate_isolinux_splash(grml_flavour):
477     """Generate bootsplash for isolinux/syslinux
478
479     @grml_flavour: name of grml flavour the configuration should be generated for"""
480
481     # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
482
483     grml_name = grml_flavour
484
485     return("""\
486 \ f17\f\18/boot/syslinux/logo.16
487
488 Some information and boot options available via keys F2 - F10. http://grml.org/
489 %(grml_name)s
490 """ % {'grml_name': grml_name} )
491
492
493 def generate_main_syslinux_config(grml_flavour, bootoptions):
494     """Generate main configuration for use in syslinux.cfg
495
496     @grml_flavour: name of grml flavour the configuration should be generated for
497     @bootoptions: additional bootoptions that should be used by default"""
498
499     local_datestamp = DATESTAMP
500
501     return("""\
502 ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
503 # use this to control the bootup via a serial port
504 # SERIAL 0 9600
505 DEFAULT grml
506 TIMEOUT 300
507 PROMPT 1
508 DISPLAY /boot/syslinux/boot.msg
509 F1 /boot/syslinux/boot.msg
510 F2 /boot/syslinux/f2
511 F3 /boot/syslinux/f3
512 F4 /boot/syslinux/f4
513 F5 /boot/syslinux/f5
514 F6 /boot/syslinux/f6
515 F7 /boot/syslinux/f7
516 F8 /boot/syslinux/f8
517 F9 /boot/syslinux/f9
518 F10 /boot/syslinux/f10
519 ## end of main configuration
520
521 ## global configuration
522 # the default option (using %(grml_flavour)s)
523 LABEL  grml
524 KERNEL /boot/release/%(grml_flavour)s/linux26
525 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
526
527 # memtest
528 LABEL  memtest
529 KERNEL /boot/addons/memtest
530
531 # grub
532 LABEL grub
533 MENU LABEL grub
534 KERNEL /boot/addons/memdisk
535 APPEND initrd=/boot/addons/allinone.img
536
537 # dos
538 LABEL dos
539 MENU LABEL dos
540 KERNEL /boot/addons/memdisk
541 APPEND initrd=/boot/addons/balder10.imz
542
543 # bsd
544 LABEL bsd
545 MENU LABEL bsd
546 KERNEL /boot/addons/bsd4grml/ldbsd.com
547
548 # hardware detection tool
549 MENU LABEL hdt
550 KERNEL /boot/addons/hdt.c32
551 APPEND pciids=/boot/addons/pci.ids
552
553 ## end of global configuration
554 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
555
556
557 def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
558     """Generate flavour specific configuration for use in syslinux.cfg
559
560     @grml_flavour: name of grml flavour the configuration should be generated for
561     @bootoptions: bootoptions that should be used as a default"""
562
563     local_datestamp = DATESTAMP
564
565     return("""\
566
567 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
568 LABEL  %(grml_flavour)s
569 KERNEL /boot/release/%(grml_flavour)s/linux26
570 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
571
572 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
573 LABEL  %(grml_flavour)s-persistent
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 persistent 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)s2ram
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 live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs %(bootoptions)s
581
582 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
583 LABEL  %(grml_flavour)s-debug
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/ debug boot=live initcall_debug%(bootoptions)s
586
587 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
588 LABEL  %(grml_flavour)s-x
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/ startx=wm-ng %(bootoptions)s
591
592 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
593 LABEL  %(grml_flavour)s-nofb
594 KERNEL /boot/release/%(grml_flavour)s/linux26
595 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
596
597 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
598 LABEL  %(grml_flavour)s-failsafe
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 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
601
602 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
603 LABEL  %(grml_flavour)s-forensic
604 KERNEL /boot/release/%(grml_flavour)s/linux26
605 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
606
607 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
608 LABEL  %(grml_flavour)s-serial
609 KERNEL /boot/release/%(grml_flavour)s/linux26
610 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
611 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
612
613
614 def install_grub(device):
615     """Install grub on specified device.
616
617     @mntpoint: mountpoint of device where grub should install its files to
618     @device: partition where grub should be installed to"""
619
620     if options.dryrun:
621         logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device)
622     else:
623         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
624         register_tmpfile(device_mountpoint)
625         try:
626             try:
627                 mount(device, device_mountpoint, "")
628                 logging.debug("grub-install --recheck --no-floppy --root-directory=%s %s", device_mountpoint, device)
629                 proc = subprocess.Popen(["grub-install", "--recheck", "--no-floppy",
630                     "--root-directory=%s" % device_mountpoint, device], stdout=file(os.devnull, "r+"))
631                 proc.wait()
632                 if proc.returncode != 0:
633                     # raise Exception("error executing grub-install")
634                     logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ)")
635                     cleanup()
636                     sys.exit(1)
637             except CriticalException, error:
638                 logging.critical("Fatal: %s" % error)
639                 cleanup()
640                 sys.exit(1)
641
642         finally:
643             unmount(device_mountpoint, "")
644             os.rmdir(device_mountpoint)
645             unregister_tmpfile(device_mountpoint)
646
647
648 def install_syslinux(device):
649     """Install syslinux on specified device.
650
651     @device: partition where syslinux should be installed to"""
652
653     if options.dryrun:
654         logging.info("Would install syslinux as bootloader on %s", device)
655         return 0
656
657     # syslinux -d boot/isolinux /dev/sdb1
658     logging.info("Installing syslinux as bootloader")
659     logging.debug("syslinux -d boot/syslinux %s" % device)
660     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
661     proc.wait()
662     if proc.returncode != 0:
663         raise CriticalException("Error executing syslinux (either try --fat16 or --grub?)")
664
665
666 def install_bootloader(device):
667     """Install bootloader on specified device.
668
669     @device: partition where bootloader should be installed to"""
670
671     # by default we use grub, so install syslinux only on request
672     if options.syslinux:
673         try:
674             install_syslinux(device)
675         except CriticalException, error:
676             logging.critical("Fatal: %s" % error)
677             cleanup()
678             sys.exit(1)
679     else:
680         if not which("grub-install"):
681             logging.critical("Fatal: grub-install not available (please install the grub package or use the --syslinux option)")
682             cleanup()
683             sys.exit(1)
684         else:
685             try:
686                 install_grub(device)
687             except CriticalException, error:
688                 logging.critical("Fatal: %s" % error)
689                 cleanup()
690                 sys.exit(1)
691
692
693 def execute_lilo(lilo, device):
694     """execute lilo for activating the partitions in the MBR
695
696     @lilo: path of lilo executable
697     @device: device where lilo should be executed on"""
698
699     # to support -A for extended partitions:
700     logging.info("Activating partitions in MBR via lilo")
701     logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
702     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
703     proc.wait()
704     if proc.returncode != 0:
705         raise Exception("error executing lilo")
706
707     # activate partition:
708     logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
709     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
710     proc.wait()
711     if proc.returncode != 0:
712         raise Exception("error executing lilo")
713
714
715 def install_syslinux_mbr(device):
716     """install syslinux's master boot record (MBR) on the specified device
717
718     @device: device where MBR of syslinux should be installed to"""
719
720     # make sure we have syslinux available
721     if not which("syslinux") and not options.copyonly:
722         raise Exception("syslinux not available (either install it or consider dropping the --syslinux option)")
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, therefore not mounting." % 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(["/sbin/blkid", "-s", "TYPE", "-o", "value", 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, /sbin/blkid not available (install e2fsprogs?)")
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("rsync -rlptDH --inplace %s %s" % (squashfs, squashfs_target + grml_flavour + '.squashfs'))
995         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (filesystem_module, squashfs_target + 'filesystem.module'))
1003         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (kernel, release_target + '/linux26'))
1014         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (initrd, release_target + '/initrd.gz'))
1022         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (grml_file, grml_target + grml_file))
1041             proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (grml_file, grml_web_target + grml_file))
1053             proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (grml_file, grml_webimg_target + grml_file))
1065             proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (allinoneimg, addons + '/allinone.img'))
1084         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (bsdimg, addons + '/'))
1093         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (balderimg, addons + '/balder10.imz'))
1102         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (hdtimg, addons + '/hdt.c32'))
1111             proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (picids, addons + '/pci.ids'))
1118             proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (memdiskimg, addons + '/memdisk'))
1127         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (memtestimg, addons + '/memtest'))
1136         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (logo, syslinux_target + 'logo.16'))
1151     proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace %s %s" % (bootsplash, syslinux_target + ffile))
1157         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", 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("rsync -rlptDH --inplace /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
1169         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", '/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("rsync -rlptDH --inplace /usr/share/grml2usb/grub/grml.png to %s" % grub_target + 'grml.png')
1176         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", '/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.pf2"):
1182         logging.debug("rsync -rlptDH --inplace /usr/share/grub/ascii.pf2 to %s" % grub_target + 'ascii.pf2')
1183         proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", '/usr/share/grub/ascii.pf2',
1184                                 grub_target + 'ascii.pf2'])
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 therefore.")
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:
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, 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     @grub_target: path of grub's configuration files
1307     @bootoptions: additional bootoptions that should be used by default"""
1308
1309     # grub2 config
1310     grub2_cfg = grub_target + 'grub.cfg'
1311     logging.debug("Creating grub2 configuration file (grub.lst)")
1312
1313     global GRML_DEFAULT
1314
1315     # install main configuration only *once*, no matter how many ISOs we have:
1316     grub_flavour_is_default = False
1317     if os.path.isfile(grub2_cfg):
1318         string = open(grub2_cfg).readline()
1319         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1320         if not re.match(main_identifier, string):
1321             grub2_config_file = open(grub2_cfg, 'w')
1322             GRML_DEFAULT = grml_flavour
1323             grub_flavour_is_default = True
1324             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1325             grub2_config_file.close()
1326     else:
1327         grub2_config_file = open(grub2_cfg, 'w')
1328         GRML_DEFAULT = grml_flavour
1329         grub_flavour_is_default = True
1330         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1331         grub2_config_file.close()
1332
1333     # install flavour specific configuration only *once* as well
1334     grub_flavour_config = True
1335     if os.path.isfile(grub2_cfg):
1336         string = open(grub2_cfg).readlines()
1337         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1338         for line in string:
1339             if flavour.match(line):
1340                 grub_flavour_config = False
1341
1342     if grub_flavour_config:
1343         grub2_config_file = open(grub2_cfg, 'a')
1344         # display only if the grml flavour isn't the default
1345         if not grub_flavour_is_default:
1346             GRML_FLAVOURS.add(grml_flavour)
1347         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1348         grub2_config_file.close()
1349
1350
1351 def handle_grub_config(grml_flavour, device, target):
1352     """Main handler for generating grub (v1 and v2) configuration
1353
1354     @grml_flavour: name of grml flavour the configuration should be generated for
1355     @device: device/partition where grub should be installed to
1356     @target: path of grub's configuration files"""
1357
1358     logging.debug("Generating grub configuration")
1359
1360     grub_target = target + '/boot/grub/'
1361     execute(mkdir, grub_target)
1362
1363     if os.path.isdir(device):
1364         install_grub1_partition = None
1365     else:
1366         if device[-1:].isdigit():
1367             install_grub1_partition = int(device[-1:]) - 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     if install_grub1_partition:
1379         handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1380     # write grub.cfg
1381     handle_grub2_config(grml_flavour, grub_target, bootopt)
1382
1383
1384 def handle_syslinux_config(grml_flavour, target):
1385     """Main handler for generating syslinux configuration
1386
1387     @grml_flavour: name of grml flavour the configuration should be generated for
1388     @target: path of syslinux's configuration files"""
1389
1390     # do NOT write "None" in kernel cmdline
1391     if options.bootoptions is None:
1392         bootopt = ""
1393     else:
1394         bootopt = options.bootoptions
1395
1396     logging.debug("Generating syslinux configuration")
1397     syslinux_target = target + '/boot/syslinux/'
1398     # should be present via  copy_bootloader_files(), but make sure it exits:
1399     execute(mkdir, syslinux_target)
1400     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1401
1402     global GRML_DEFAULT
1403
1404     # install main configuration only *once*, no matter how many ISOs we have:
1405     syslinux_flavour_is_default = False
1406     if os.path.isfile(syslinux_cfg):
1407         string = open(syslinux_cfg).readline()
1408         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1409         if not re.match(main_identifier, string):
1410             syslinux_config_file = open(syslinux_cfg, 'w')
1411             GRML_DEFAULT = grml_flavour
1412             syslinux_flavour_is_default = True
1413             syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1414             syslinux_config_file.close()
1415     else:
1416         syslinux_config_file = open(syslinux_cfg, 'w')
1417         GRML_DEFAULT = grml_flavour
1418         syslinux_flavour_is_default = True
1419         syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1420         syslinux_config_file.close()
1421
1422     # install flavour specific configuration only *once* as well
1423     syslinux_flavour_config = True
1424     if os.path.isfile(syslinux_cfg):
1425         string = open(syslinux_cfg).readlines()
1426         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1427         for line in string:
1428             if flavour.match(line):
1429                 syslinux_flavour_config = False
1430
1431     if syslinux_flavour_config:
1432         syslinux_config_file = open(syslinux_cfg, 'a')
1433         # display only if the grml flavour isn't the default
1434         if not syslinux_flavour_is_default:
1435             GRML_FLAVOURS.add(grml_flavour)
1436         syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
1437         syslinux_config_file.close()
1438
1439     logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
1440     isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
1441     isolinux_splash.write(generate_isolinux_splash(grml_flavour))
1442     isolinux_splash.close()
1443
1444
1445 def handle_bootloader_config(grml_flavour, device, target):
1446     """Main handler for generating bootloader's configuration
1447
1448     @grml_flavour: name of grml flavour the configuration should be generated for
1449     @device: device/partition where bootloader should be installed to
1450     @target: path of bootloader's configuration files"""
1451
1452     if options.skipsyslinuxconfig:
1453         logging.info("Skipping generation of syslinux configuration as requested.")
1454     else:
1455         try:
1456             handle_syslinux_config(grml_flavour, target)
1457         except CriticalException, error:
1458             logging.critical("Fatal: %s" % error)
1459             sys.exit(1)
1460
1461     if options.skipgrubconfig:
1462         logging.info("Skipping generation of grub configuration as requested.")
1463     else:
1464         try:
1465             handle_grub_config(grml_flavour, device, target)
1466         except CriticalException, error:
1467             logging.critical("Fatal: %s" % error)
1468             sys.exit(1)
1469
1470 def handle_dir(live_image, device):
1471     """Main logic for copying files of the currently running grml system.
1472
1473     @live_image: directory where currently running live system resides (usually /live/image)
1474     @device: partition where the specified ISO should be installed to"""
1475
1476     logging.info("Using %s as install base" % live_image)
1477
1478     if os.path.isdir(device):
1479         logging.info("Specified target is a directory, therefore not mounting.")
1480         device_mountpoint = device
1481         remove_device_mountpoint = False
1482     else:
1483         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1484         register_tmpfile(device_mountpoint)
1485         remove_device_mountpoint = True
1486         try:
1487             mount(device, device_mountpoint, "")
1488         except CriticalException, error:
1489             logging.critical("Fatal: %s" % error)
1490             cleanup()
1491             sys.exit(1)
1492
1493     try:
1494         try:
1495             grml_flavour = identify_grml_flavour(live_image)
1496             logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1497             install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1498         except TypeError:
1499             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1500             sys.exit(1)
1501     finally:
1502         if remove_device_mountpoint:
1503             try:
1504                 unmount(device_mountpoint, "")
1505                 if os.path.isdir(device_mountpoint):
1506                     os.rmdir(device_mountpoint)
1507                     unregister_tmpfile(device_mountpoint)
1508             except CriticalException, error:
1509                 logging.critical("Fatal: %s" % error)
1510                 cleanup()
1511
1512
1513 def handle_iso(iso, device):
1514     """Main logic for mounting ISOs and copying files.
1515
1516     @iso: full path to the ISO that should be installed to the specified device
1517     @device: partition where the specified ISO should be installed to"""
1518
1519     logging.info("Using ISO %s" % iso)
1520
1521     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1522     register_tmpfile(iso_mountpoint)
1523     remove_iso_mountpoint = True
1524
1525     if not os.path.isfile(iso):
1526         logging.critical("Fatal: specified ISO %s could not be read" % iso)
1527         cleanup()
1528         sys.exit(1)
1529
1530     try:
1531         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1532     except CriticalException, error:
1533         logging.critical("Fatal: %s" % error)
1534         sys.exit(1)
1535
1536     if os.path.isdir(device):
1537         logging.info("Specified target is a directory, therefore not mounting.")
1538         device_mountpoint = device
1539         remove_device_mountpoint = False
1540         # skip_mbr = True
1541     else:
1542         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1543         register_tmpfile(device_mountpoint)
1544         remove_device_mountpoint = True
1545         try:
1546             mount(device, device_mountpoint, "")
1547         except CriticalException, error:
1548             logging.critical("Fatal: %s" % error)
1549             cleanup()
1550             sys.exit(1)
1551
1552     try:
1553         try:
1554             grml_flavour = identify_grml_flavour(iso_mountpoint)
1555             logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1556             install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1557         except TypeError:
1558             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1559             sys.exit(1)
1560     finally:
1561         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1562             unmount(iso_mountpoint, "")
1563             os.rmdir(iso_mountpoint)
1564             unregister_tmpfile(iso_mountpoint)
1565         if remove_device_mountpoint:
1566             try:
1567                 unmount(device_mountpoint, "")
1568                 if os.path.isdir(device_mountpoint):
1569                     os.rmdir(device_mountpoint)
1570                     unregister_tmpfile(device_mountpoint)
1571             except CriticalException, error:
1572                 logging.critical("Fatal: %s" % error)
1573                 cleanup()
1574
1575
1576 def handle_mbr(device):
1577     """Main handler for installing master boot record (MBR)
1578
1579     @device: device where the MBR should be installed to"""
1580
1581     if options.dryrun:
1582         logging.info("Would install MBR")
1583         return 0
1584
1585     if device[-1:].isdigit():
1586         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1587         partition_number = int(device[-1:]) - 1
1588         skip_install_mir_mbr = False
1589
1590     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1591     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1592     if mbr_device == "/dev/loop":
1593         mbr_device = device
1594         logging.info("Detected loop device - using %s as MBR device therefore" % mbr_device)
1595         skip_install_mir_mbr = True
1596
1597     try:
1598         if options.syslinuxmbr:
1599             handle_syslinux_mbr(mbr_device)
1600         elif not skip_install_mir_mbr:
1601             if options.mbrmenu:
1602                 install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True)
1603             else:
1604                 install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, False)
1605     except IOError, error:
1606         logging.critical("Execution failed: %s", error)
1607         sys.exit(1)
1608     except Exception, error:
1609         logging.critical("Execution failed: %s", error)
1610         sys.exit(1)
1611
1612
1613 def handle_vfat(device):
1614     """Check for FAT specific settings and options
1615
1616     @device: device that should checked / formated"""
1617
1618     # make sure we have mkfs.vfat available
1619     if options.fat16 and not options.force:
1620         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1621             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1622             logging.critical('Please make sure to install dosfstools.')
1623             sys.exit(1)
1624
1625         # make sure the user is aware of what he is doing
1626         f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1627         if f == "y" or f == "Y":
1628             logging.info("Note: you can skip this question using the option --force")
1629             try:
1630                 mkfs_fat16(device)
1631             except CriticalException, error:
1632                 logging.critical("Execution failed: %s", error)
1633                 sys.exit(1)
1634         else:
1635             sys.exit(1)
1636
1637     # check for vfat filesystem
1638     if device is not None and not os.path.isdir(device):
1639         try:
1640             check_for_fat(device)
1641         except CriticalException, error:
1642             logging.critical("Execution failed: %s", error)
1643             sys.exit(1)
1644
1645     if not os.path.isdir(device) and not check_for_usbdevice(device):
1646         print "Warning: the specified device %s does not look like a removable usb device." % device
1647         f = raw_input("Do you really want to continue? y/N ")
1648         if f == "y" or f == "Y":
1649             pass
1650         else:
1651             sys.exit(1)
1652
1653
1654 def handle_compat_warning(device):
1655     """Backwards compatible checks
1656
1657     @device: device that should be checked"""
1658
1659     # make sure we can replace old grml2usb script and warn user when using old way of life:
1660     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1661         print "Warning: the semantics of grml2usb has changed."
1662         print "Instead of using grml2usb /path/to/iso %s you might" % device
1663         print "want to use grml2usb /path/to/iso /dev/... instead."
1664         print "Please check out the grml2usb manpage for details."
1665         f = raw_input("Do you really want to continue? y/N ")
1666         if f == "y" or f == "Y":
1667             pass
1668         else:
1669             sys.exit(1)
1670
1671
1672 def handle_logging():
1673     """Log handling and configuration"""
1674
1675     if options.verbose and options.quiet:
1676         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1677
1678     if options.verbose:
1679         FORMAT = "Debug: %(asctime)-15s %(message)s"
1680         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1681     elif options.quiet:
1682         FORMAT = "Critical: %(message)s"
1683         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1684     else:
1685         FORMAT = "%(message)s"
1686         logging.basicConfig(level=logging.INFO, format=FORMAT)
1687
1688
1689 def handle_bootloader(device):
1690     """wrapper for installing bootloader
1691
1692     @device: device where bootloader should be installed to"""
1693
1694     # Install bootloader only if not using the --copy-only option
1695     if options.copyonly:
1696         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1697     elif os.path.isdir(device):
1698         logging.info("Not installing bootloader as %s is a directory." % device)
1699     else:
1700         install_bootloader(device)
1701
1702
1703 def main():
1704     """Main function [make pylint happy :)]"""
1705
1706     if options.version:
1707         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1708         sys.exit(0)
1709
1710     if len(args) < 2:
1711         parser.error("invalid usage")
1712
1713     # log handling
1714     handle_logging()
1715
1716     # make sure we have the appropriate permissions
1717     check_uid_root()
1718
1719     logging.info("Executing grml2usb version %s", PROG_VERSION)
1720
1721     if options.dryrun:
1722         logging.info("Running in simulation mode as requested via option dry-run.")
1723
1724     # specified arguments
1725     device = args[len(args) - 1]
1726     isos = args[0:len(args) - 1]
1727
1728     if not os.path.isdir(device):
1729         if device[-1:].isdigit():
1730             if int(device[-1:]) > 4 or device[-2:].isdigit():
1731                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1732                 sys.exit(1)
1733         else:
1734             if os.path.exists(device):
1735                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1736                 sys.exit(1)
1737
1738     if not which("rsync"):
1739         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1740         sys.exit(1)
1741
1742     # provide upgrade path
1743     handle_compat_warning(device)
1744
1745     # check for vfat partition
1746     handle_vfat(device)
1747
1748     # main operation (like installing files)
1749     for iso in isos:
1750         if os.path.isdir(iso):
1751             handle_dir(iso, device)
1752         else:
1753             handle_iso(iso, device)
1754
1755     # install mbr
1756     if not os.path.isdir(device):
1757         if not options.skipmbr:
1758             handle_mbr(device)
1759
1760     handle_bootloader(device)
1761
1762     logging.info("Note: grml flavour %s was installed as the default booting system." % GRML_DEFAULT)
1763
1764     for flavour in GRML_FLAVOURS:
1765         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (flavour, flavour))
1766
1767     # finally be politely :)
1768     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
1769
1770
1771 if __name__ == "__main__":
1772     try:
1773         main()
1774     except KeyboardInterrupt:
1775         logging.info("Received KeyboardInterrupt")
1776         cleanup()
1777
1778 ## END OF FILE #################################################################
1779 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8