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