Also set GRML_DEFAULT when running with --dry-run. Thanks to Moritz
[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 import fileinput
20 import glob
21
22 # global variables
23 PROG_VERSION = "0.9.17-pre1"
24 MOUNTED = set()  # register mountpoints
25 TMPFILES = set() # register tmpfiles
26 DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
27 GRML_FLAVOURS = set() # which flavours are being installed?
28 global GRML_DEFAULT
29
30 def syslinux_warning(option, opt, value, parser):
31     sys.stderr.write("Note: the --syslinux option is deprecated as syslinux " +
32             "is grml2usb's default. Continuing anyway.\n")
33     setattr(parser.values, option.dest, True)
34
35 # cmdline parsing
36 USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/sdX#>\n\
37 \n\
38 %prog installs grml ISO[s] to an USB device to be able to boot from it.\n\
39 Make sure you have at least one grml ISO or a running grml system (/live/image),\n\
40 grub or syslinux and root access.\n\
41 \n\
42 Run %prog --help for usage hints, further information via: man grml2usb"
43
44 # pylint: disable-msg=C0103
45 parser = OptionParser(usage=USAGE)
46 parser.add_option("--bootoptions", dest="bootoptions",
47                   action="store", type="string",
48                   help="use specified bootoptions as default")
49 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
50                   help="do not copy files but just install a bootloader")
51 parser.add_option("--copy-only", dest="copyonly", action="store_true",
52                   help="copy files only but do not install bootloader")
53 parser.add_option("--dry-run", dest="dryrun", action="store_true",
54                   help="avoid executing commands")
55 parser.add_option("--fat16", dest="fat16", action="store_true",
56                   help="format specified partition with FAT16")
57 parser.add_option("--force", dest="force", action="store_true",
58                   help="force any actions requiring manual interaction")
59 parser.add_option("--grub", dest="grub", action="store_true",
60                   help="install grub bootloader instead of (default) syslinux")
61 parser.add_option("--grub-mbr", dest="grubmbr", action="store_true",
62                   help="install grub into MBR instead of (default) PBR")
63 parser.add_option("--lilo-binary", dest="lilobin",  action="store", type="string",
64                   help="lilo executable to be used for installing MBR")
65 parser.add_option("--mbr-menu", dest="mbrmenu", action="store_true",
66                   help="enable interactive boot menu in MBR")
67 parser.add_option("--quiet", dest="quiet", action="store_true",
68                   help="do not output anything but just errors on console")
69 parser.add_option("--remove-bootoption", dest="removeoption", action="append",
70                   help="regex for removing existing bootoptions")
71 parser.add_option("--skip-addons", dest="skipaddons", action="store_true",
72                   help="do not install /boot/addons/ files")
73 parser.add_option("--skip-grub-config", dest="skipgrubconfig", action="store_true",
74                   help="skip generation of grub configuration files")
75 parser.add_option("--skip-mbr", dest="skipmbr", action="store_true",
76                   help="do not install a master boot record (MBR) on the device")
77 parser.add_option("--skip-syslinux-config", dest="skipsyslinuxconfig", action="store_true",
78                   help="skip generation of syslinux configuration files")
79 parser.add_option("--syslinux", dest="syslinux", action="callback", default=True,
80                   callback=syslinux_warning,
81                   help="install syslinux bootloader (deprecated as it's the default)")
82 parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true",
83                   help="install syslinux master boot record (MBR) instead of default")
84 parser.add_option("--verbose", dest="verbose", action="store_true",
85                   help="enable verbose mode")
86 parser.add_option("-v", "--version", dest="version", action="store_true",
87                   help="display version and exit")
88 (options, args) = parser.parse_args()
89
90
91 GRML2USB_BASE = '/usr/share/grml2usb'
92 if not os.path.isdir(GRML2USB_BASE):
93     GRML2USB_BASE = os.path.dirname(os.path.realpath(__file__))
94
95
96 class CriticalException(Exception):
97     """Throw critical exception if the exact error is not known but fatal."
98
99     @Exception: message"""
100     pass
101
102
103 # The following two functions help to operate on strings as
104 # array (list) of bytes (octets). In Python 3000, the bytes
105 # datatype will need to be used. This is intended for using
106 # with manipulation of files on the octet level, like shell
107 # arrays, e.g. in MBR creation.
108
109
110 def array2string(a):
111     """Convert a list of integers [0;255] to a string."""
112     return struct.pack("%sB" % len(a), *a)
113
114
115 def string2array(s):
116     """Convert a (bytes) string into a list of integers."""
117     return struct.unpack("%sB" % len(s), s)
118
119
120 def cleanup():
121     """Cleanup function to make sure there aren't any mounted devices left behind.
122     """
123
124     logging.info("Cleaning up before exiting...")
125     proc = subprocess.Popen(["sync"])
126     proc.wait()
127
128     try:
129         for device in MOUNTED:
130             unmount(device, "")
131     # ignore: RuntimeError: Set changed size during iteration
132     except RuntimeError:
133         logging.debug('caught expection RuntimeError, ignoring')
134
135
136 def register_tmpfile(path):
137     """
138     TODO - not implemented yet
139     """
140
141     TMPFILES.add(path)
142
143
144 def unregister_tmpfile(path):
145     """
146     TODO - not implemented yet
147     """
148
149     if path in TMPFILES:
150         TMPFILES.remove(path)
151
152
153 def register_mountpoint(target):
154     """register specified target in a set() for handling clean exiting
155
156     @target: destination target of mountpoint
157     """
158
159     MOUNTED.add(target)
160
161
162 def unregister_mountpoint(target):
163     """unregister specified target in a set() for handling clean exiting
164
165     @target: destination target of mountpoint
166     """
167
168     if target in MOUNTED:
169         MOUNTED.remove(target)
170
171
172 def get_function_name(obj):
173     """Helper function for use in execute() to retrive name of a function
174
175     @obj: the function object
176     """
177     if not (isroutine(obj) or isclass(obj)):
178         obj = type(obj)
179     return obj.__module__ + '.' + obj.__name__
180
181
182 def execute(f, *exec_arguments):
183     """Wrapper for executing a command. Either really executes
184     the command (default) or when using --dry-run commandline option
185     just displays what would be executed."""
186     # usage: execute(subprocess.Popen, (["ls", "-la"]))
187     # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
188     if options.dryrun:
189         # pylint: disable-msg=W0141
190         logging.debug('dry-run only: %s(%s)', get_function_name(f), ', '.join(map(repr, exec_arguments)))
191     else:
192         # pylint: disable-msg=W0142
193         return f(*exec_arguments)
194
195
196 def is_exe(fpath):
197     """Check whether a given file can be executed
198
199     @fpath: full path to file
200     @return:"""
201     return os.path.exists(fpath) and os.access(fpath, os.X_OK)
202
203
204 def which(program):
205     """Check whether a given program is available in PATH
206
207     @program: name of executable"""
208     fpath = os.path.split(program)[0]
209     if fpath:
210         if is_exe(program):
211             return program
212     else:
213         for path in os.environ["PATH"].split(os.pathsep):
214             exe_file = os.path.join(path, program)
215             if is_exe(exe_file):
216                 return exe_file
217
218     return None
219
220
221 def search_file(filename, search_path='/bin' + os.pathsep + '/usr/bin'):
222     """Given a search path, find file
223
224     @filename: name of file to search for
225     @search_path: path where searching for the specified filename"""
226     file_found = 0
227     paths = search_path.split(os.pathsep)
228     current_dir = '' # make pylint happy :)
229     for path in paths:
230         # pylint: disable-msg=W0612
231         for current_dir, directories, files in os.walk(path):
232             if os.path.exists(os.path.join(current_dir, filename)):
233                 file_found = 1
234                 break
235     if file_found:
236         return os.path.abspath(os.path.join(current_dir, filename))
237     else:
238         return None
239
240
241 def check_uid_root():
242     """Check for root permissions"""
243     if not os.geteuid()==0:
244         sys.exit("Error: please run this script with uid 0 (root).")
245
246
247 def mkfs_fat16(device):
248     """Format specified device with VFAT/FAT16 filesystem.
249
250     @device: partition that should be formated"""
251
252     if options.dryrun:
253         logging.info("Would execute mkfs.vfat -F 16 %s now.", device)
254         return 0
255
256     logging.info("Formating partition with fat16 filesystem")
257     logging.debug("mkfs.vfat -F 16 %s", device)
258     proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
259     proc.wait()
260     if proc.returncode != 0:
261         raise CriticalException("error executing mkfs.vfat")
262
263
264 def generate_main_grub2_config(grml_flavour, bootoptions):
265     """Generate grub2 configuration for use via grub.cfg
266
267     @grml_flavour: name of grml flavour the configuration should be generated for
268     @bootoptions: additional bootoptions that should be used by default"""
269
270     local_datestamp = DATESTAMP
271
272     return("""\
273 ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
274 set default=0
275 set timeout=10
276
277 insmod fat
278
279 if loadfont /boot/grub/ascii.pf2 ; then
280    insmod png
281    set gfxmode=640x480
282    insmod gfxterm
283    insmod vbe
284    if terminal_output.gfxterm ; then true ; else
285     # For backward compatibility with versions of terminal.mod that don't
286     # understand terminal_output
287     terminal gfxterm
288    fi
289 fi
290
291 if background_image /boot/grub/grml.png ; then
292   set color_normal=black/black
293   set color_highlight=red/black
294 else
295   set menu_color_normal=white/black
296   set menu_color_highlight=black/yellow
297 fi
298
299 menuentry "%(grml_flavour)s (default)" {
300     set gfxpayload=1024x768x16,1024x768
301     linux   /boot/release/%(flavour_filename)s/linux26 apm=power-off quiet boot=live nomce live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
302     initrd  /boot/release/%(flavour_filename)s/initrd.gz
303 }
304
305 menuentry "Memory test (memtest86+)" {
306     linux16   /boot/addons/memtest
307 }
308
309 menuentry "Boot Grub (all in one image)" {
310     linux   /boot/addons/memdisk
311     initrd  /boot/addons/allinone.img
312 }
313
314 menuentry "Boot FreeDOS" {
315     linux   /boot/addons/memdisk
316     initrd  /boot/addons/balder10.imz
317 }
318
319 menuentry "Boot MirOS bsd4grml" {
320     multiboot /boot/addons/bsd4grml/ldbsd.com
321     module    /boot/addons/bsd4grml/bsd.rd
322     module    /boot/addons/bsd4grml/boot.1
323     module    /boot/addons/bsd4grml/boot.2
324     module    /boot/addons/bsd4grml/boot.3
325     module    /boot/addons/bsd4grml/boot.4
326     module    /boot/addons/bsd4grml/boot.5
327     module    /boot/addons/bsd4grml/boot.6
328     module    /boot/addons/bsd4grml/boot.cfg
329 }
330
331 menuentry "Boot OS of first partition on first disk" {
332     set root=(hd0,1)
333     chainloader +1
334 }
335
336 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
337         'flavour_filename': grml_flavour.replace('-', ''),
338         'bootoptions': bootoptions } )
339
340
341 def generate_flavour_specific_grub2_config(grml_flavour, bootoptions):
342     """Generate grub2 configuration for use via grub.cfg
343
344     @grml_flavour: name of grml flavour the configuration should be generated for
345     @bootoptions: additional bootoptions that should be used by default"""
346
347     local_datestamp = DATESTAMP
348
349     return("""\
350 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
351 menuentry "%(grml_flavour)s            - boot in default mode" {
352     set gfxpayload=1024x768x16,1024x768
353     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
354     initrd /boot/release/%(flavour_filename)s/initrd.gz
355 }
356
357 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
358 menuentry "%(grml_flavour)s-persistent - enable persistency feature" {
359     set gfxpayload=1024x768x16,1024x768
360     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet persistent live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
361     initrd /boot/release/%(flavour_filename)s/initrd.gz
362 }
363
364 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
365 menuentry "%(grml_flavour)s2ram        - copy compressed grml file to RAM" {
366     set gfxpayload=1024x768x16,1024x768
367     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ toram=%(grml_flavour)s.squashfs %(bootoptions)s
368     initrd /boot/release/%(flavour_filename)s/initrd.gz
369 }
370
371 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
372 menuentry "%(grml_flavour)s-debug      - enable debugging options" {
373     set gfxpayload=1024x768x16,1024x768
374     linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ debug initcall_debug%(bootoptions)s
375     initrd /boot/release/%(flavour_filename)s/initrd.gz
376 }
377
378 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
379 menuentry "%(grml_flavour)s-x          - start X Window System" {
380     set gfxpayload=1024x768x16,1024x768
381     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng %(bootoptions)s
382     initrd /boot/release/%(flavour_filename)s/initrd.gz
383 }
384
385 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
386 menuentry "%(grml_flavour)s-nofb       - disable framebuffer" {
387     linux  /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=ofonly %(bootoptions)s
388     initrd /boot/release/%(flavour_filename)s/initrd.gz
389 }
390
391 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
392 menuentry "%(grml_flavour)s-failsafe   - disable hardware detection" {
393     linux /boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal 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
394     initrd /boot/release/%(flavour_filename)s/initrd.gz
395 }
396
397 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
398 menuentry "%(grml_flavour)s-forensic   - do not touch harddisks during hw recognition" {
399     set gfxpayload=1024x768x16,1024x768
400     linux /boot/release/%(flavour_filename)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
401     initrd /boot/release/%(flavour_filename)s/initrd.gz
402 }
403
404 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
405        'flavour_filename': grml_flavour.replace('-', ''),
406        'bootoptions': bootoptions } )
407
408
409 def generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootoptions):
410     """Generate grub1 configuration for use via menu.lst
411
412     @grml_flavour: name of grml flavour the configuration should be generated for
413     @install_partition: partition number for use in (hd0,X)
414     @bootoptions: additional bootoptions that should be used by default"""
415
416     local_datestamp = DATESTAMP
417
418     return("""\
419 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
420 title %(grml_flavour)s
421 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
422 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
423
424 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
425 title %(grml_flavour)s-persistent
426 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet persistent live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
427 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
428
429 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
430 title %(grml_flavour)s2ram
431 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)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
432 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
433
434 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
435 title %(grml_flavour)s-debug
436 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ debug initcall_debug%(bootoptions)s
437 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
438
439 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
440 title %(grml_flavour)s-x
441 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce vga=791 quiet live-media-path=/live/%(grml_flavour)s/ startx=wm-ng %(bootoptions)s
442 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
443
444 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
445 title %(grml_flavour)s-nofb
446 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal video=ofonly %(bootoptions)s
447 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
448
449 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
450 title %(grml_flavour)s-failsafe
451 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off boot=live nomce quiet live-media-path=/live/%(grml_flavour)s/ vga=normal 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
452 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
453
454 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
455 title %(grml_flavour)s-forensic
456 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)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
457 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
458
459 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
460 title %(grml_flavour)s-serial
461 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)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
462 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
463
464 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
465        'flavour_filename': grml_flavour.replace('-', ''),
466        'bootoptions': bootoptions, 'install_partition': install_partition } )
467
468
469 def generate_main_grub1_config(grml_flavour, install_partition, bootoptions):
470     """Generate grub1 configuration for use via menu.lst
471
472     @grml_flavour: name of grml flavour the configuration should be generated for"""
473
474     local_datestamp = DATESTAMP
475
476     return("""\
477 ## main grub1 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
478 # misc options:
479 timeout 30
480 # color red/blue green/black
481 splashimage=(hd0,%(install_partition)s)/boot/grub/splash.xpm.gz
482 foreground  = 000000
483 background  = FFCC33
484
485 # define entries:
486 title %(grml_flavour)s  - Default boot (using 1024x768 framebuffer)
487 kernel (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/linux26 apm=power-off vga=791 quiet boot=live nomce live-media-path=/live/%(grml_flavour)s/ %(bootoptions)s
488 initrd (hd0,%(install_partition)s)/boot/release/%(flavour_filename)s/initrd.gz
489
490 title Memory test (memtest86+)
491 kernel (hd0,%(install_partition)s)/boot/addons/memtest
492
493 title Grub - all in one image
494 kernel (hd0,%(install_partition)s)/boot/addons/memdisk
495 initrd (hd0,%(install_partition)s)/boot/addons/allinone.img
496
497 title FreeDOS
498 kernel (hd0,%(install_partition)s)/boot/addons/memdisk
499 initrd (hd0,%(install_partition)s)/boot/addons/balder10.imz
500
501 title MirOS BSD
502 kernel (hd0,%(install_partition)s)/boot/addons/bsd4grml/ldbsd.com
503
504 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp,
505        'flavour_filename': grml_flavour.replace('-', ''),
506        'bootoptions': bootoptions, 'install_partition': install_partition } )
507
508
509 def generate_isolinux_splash(grml_flavour):
510     """Generate bootsplash for isolinux/syslinux
511
512     @grml_flavour: name of grml flavour the configuration should be generated for"""
513
514     # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
515
516     grml_name = grml_flavour
517
518     return("""\
519 \ f17\f\18/boot/syslinux/logo.16
520
521 Some information and boot options available via keys F2 - F10. http://grml.org/
522 %(grml_name)s
523 """ % {'grml_name': grml_name} )
524
525
526 def generate_main_syslinux_config(*args):
527     """Generate main configuration for use in syslinux.cfg
528
529     @*args: just for backward compatibility"""
530
531     local_datestamp = DATESTAMP
532
533     return("""\
534 label -
535 menu label Default boot modes:
536 menu disable
537 include defaults.cfg
538
539 menu end
540 menu separator
541
542 # flavours:
543 label -
544 menu label Additional boot entries for:
545 menu disable
546 include additional.cfg
547
548 menu separator
549 include options.cfg
550 include addons.cfg
551
552 label help
553   include promptname.cfg
554   config prompt.cfg
555   text help
556                                         Jump to old style isolinux prompt
557                                         featuring further information
558                                         regarding available boot options.
559   endtext
560
561
562 include hiddens.cfg
563 """)
564
565
566 def generate_flavour_specific_syslinux_config(grml_flavour):
567     """Generate flavour specific configuration for use in syslinux.cfg
568
569     @grml_flavour: name of grml flavour the configuration should be generated for"""
570
571
572     return("""\
573 menu begin grml %(grml_flavour)s
574     menu title %(display_name)s
575     label mainmenu
576     menu label ^Back to main menu...
577     menu exit
578     menu separator
579     # include config for boot parameters from disk
580     include %(grml_flavour)s_grml.cfg
581     menu hide
582 menu end
583 """ % {'grml_flavour': grml_flavour, 'display_name' : grml_flavour.replace('_', '-') } )
584
585
586 def install_grub(device):
587     """Install grub on specified device.
588
589     @mntpoint: mountpoint of device where grub should install its files to
590     @device: partition where grub should be installed to"""
591
592     if options.dryrun:
593         logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device)
594     else:
595         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
596         register_tmpfile(device_mountpoint)
597         try:
598             try:
599                 mount(device, device_mountpoint, "")
600
601                 # If using --grub-mbr then make sure we install grub in MBR instead of PBR
602                 # Thanks to grub2. NOT.
603                 if options.grubmbr:
604                     logging.debug("Using option --grub-mbr ...")
605                     if device[-1:].isdigit():
606                         grub_device = re.match(r'(.*?)\d*$', device).group(1)
607                     else:
608                         grub_device = device
609                 else:
610                     grub_device = device
611
612                 logging.info("Installing grub as bootloader")
613                 logging.debug("grub-install --recheck --no-floppy --root-directory=%s %s",
614                     device_mountpoint, grub_device)
615                 proc = subprocess.Popen(["grub-install", "--recheck", "--no-floppy",
616                     "--root-directory=%s" % device_mountpoint, grub_device], stdout=file(os.devnull, "r+"))
617                 proc.wait()
618                 if proc.returncode != 0:
619                     # raise Exception("error executing grub-install")
620                     logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ or drop the --grub option)")
621                     logging.critical("Note:  if using grub2 consider using the --grub-mbr option because grub2's PBR feature is broken.")
622                     cleanup()
623                     sys.exit(1)
624             except CriticalException, error:
625                 logging.critical("Fatal: %s", error)
626                 cleanup()
627                 sys.exit(1)
628
629         finally:
630             unmount(device_mountpoint, "")
631             os.rmdir(device_mountpoint)
632             unregister_tmpfile(device_mountpoint)
633
634
635 def install_syslinux(device):
636     """Install syslinux on specified device.
637
638     @device: partition where syslinux should be installed to"""
639
640     if options.dryrun:
641         logging.info("Would install syslinux as bootloader on %s", device)
642         return 0
643
644     # syslinux -d boot/isolinux /dev/sdb1
645     logging.info("Installing syslinux as bootloader")
646     logging.debug("syslinux -d boot/syslinux %s", device)
647     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
648     proc.wait()
649     if proc.returncode != 0:
650         raise CriticalException("Error executing syslinux (either try --fat16 or use grub?)")
651
652
653 def install_bootloader(device):
654     """Install bootloader on specified device.
655
656     @device: partition where bootloader should be installed to"""
657
658     # by default we use grub, so install syslinux only on request
659     if options.grub:
660         if not which("grub-install"):
661             logging.critical("Fatal: grub-install not available (please install the grub package or use the --syslinux option)")
662             cleanup()
663             sys.exit(1)
664         else:
665             try:
666                 install_grub(device)
667             except CriticalException, error:
668                 logging.critical("Fatal: %s", error)
669                 cleanup()
670                 sys.exit(1)
671     else:
672         try:
673             install_syslinux(device)
674         except CriticalException, error:
675             logging.critical("Fatal: %s", error)
676             cleanup()
677             sys.exit(1)
678
679
680 def execute_lilo(lilo, device):
681     """execute lilo for activating the partitions in the MBR
682
683     @lilo: path of lilo executable
684     @device: device where lilo should be executed on"""
685
686     # to support -A for extended partitions:
687     logging.info("Activating partitions in MBR via lilo")
688     logging.debug("%s -S /dev/null -M %s ext", lilo, device)
689     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
690     proc.wait()
691     if proc.returncode != 0:
692         raise Exception("error executing lilo")
693
694     # activate partition:
695     logging.debug("%s -S /dev/null -A %s 1", lilo, device)
696     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
697     proc.wait()
698     if proc.returncode != 0:
699         raise Exception("error executing lilo")
700
701
702 def install_syslinux_mbr(device):
703     """install syslinux's master boot record (MBR) on the specified device
704
705     @device: device where MBR of syslinux should be installed to"""
706
707     # make sure we have syslinux available
708     if not which("syslinux") and not options.copyonly:
709         raise Exception("syslinux not available (either install it or consider using the --grub option)")
710
711     # lilo's mbr is broken, use the one from syslinux instead:
712     if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
713         raise Exception("/usr/lib/syslinux/mbr.bin can not be read")
714
715     logging.info("Installing syslinux MBR")
716     logging.debug("cat /usr/lib/syslinux/mbr.bin > %s", device)
717     try:
718         # TODO -> use Popen instead?
719         retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
720         if retcode < 0:
721             logging.critical("Error copying MBR to device (%s)", retcode)
722     except OSError, error:
723         logging.critical("Execution failed: %s", error)
724
725
726 def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
727     """install 'mbr' master boot record (MBR) on a device
728
729     Retrieve the partition table from "device", install an MBR from the
730     "mbrtemplate" file, set the "partition" (0..3) active, and install the
731     result back to "device".
732
733     @mbrtemplate: default MBR file
734
735     @device: name of a file assumed to be a hard disc (or USB stick) image, or
736     something like "/dev/sdb"
737
738     @partition: must be a number between 0 and 3, inclusive
739
740     @mbrtemplate: must be a valid MBR file of at least 440 (or 439 if
741     ismirbsdmbr) bytes.
742
743     @ismirbsdmbr: if true then ignore the active flag, set the mirbsdmbr
744     specific flag to 0/1/2/3 and set the MBR's default value accordingly. If
745     false then leave the mirbsdmbr specific flag set to FFh, set all
746     active flags to 0 and set the active flag of the partition to 80h.  Note:
747     behaviour of mirbsdmbr: if flag = 0/1/2/3 then use it, otherwise search for
748     the active flag."""
749
750     logging.info("Installing default MBR")
751
752     if not os.path.isfile(mbrtemplate):
753         logging.critical("Error: %s can not be read.", mbrtemplate)
754         raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file?)")
755
756     if (partition < 0) or (partition > 3):
757         raise ValueError("partition must be between 0 and 3")
758
759     if ismirbsdmbr:
760         nmbrbytes = 439
761     else:
762         nmbrbytes = 440
763
764     tmpf = tempfile.NamedTemporaryFile()
765
766     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1", device, tmpf.name)
767     proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"], stderr=file(os.devnull, "r+"))
768     proc.wait()
769     if proc.returncode != 0:
770         raise Exception("error executing dd (first run)")
771
772     logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc", mbrtemplate,
773         tmpf.name, nmbrbytes)
774     proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes,
775         "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
776     proc.wait()
777     if proc.returncode != 0:
778         raise Exception("error executing dd (second run)")
779
780     mbrcode = tmpf.file.read(512)
781     if len(mbrcode) < 512:
782         raise EOFError("MBR size (%d) < 512" % len(mbrcode))
783
784     if ismirbsdmbr:
785         mbrcode = mbrcode[0:439] + chr(partition) + \
786           mbrcode[440:510] + "\x55\xAA"
787     else:
788         actives = ["\x00", "\x00", "\x00", "\x00"]
789         actives[partition] = "\x80"
790         mbrcode = mbrcode[0:446] + actives[0] + \
791           mbrcode[447:462] + actives[1] + \
792           mbrcode[463:478] + actives[2] + \
793           mbrcode[479:494] + actives[3] + \
794           mbrcode[495:510] + "\x55\xAA"
795
796     tmpf.file.seek(0)
797     tmpf.file.truncate()
798     tmpf.file.write(mbrcode)
799     tmpf.file.close()
800
801     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc", tmpf.name, device)
802     proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1",
803                             "conv=notrunc"], stderr=file(os.devnull, "r+"))
804     proc.wait()
805     if proc.returncode != 0:
806         raise Exception("error executing dd (third run)")
807     del tmpf
808
809
810 def handle_syslinux_mbr(device):
811     """Install syslinux master boot record on given device
812
813     @device: device where MBR should be installed to"""
814
815     if not is_writeable(device):
816         raise IOError("device not writeable for user")
817
818     # try to use system's lilo
819     if which("lilo"):
820         lilo = which("lilo")
821     else:
822         # otherwise fall back to our static version
823         from platform import architecture
824         if architecture()[0] == '64bit':
825             lilo = GRML2USB_BASE + '/lilo/lilo.static.amd64'
826         else:
827             lilo = GRML2USB_BASE + '/lilo/lilo.static.i386'
828     # finally prefer a specified lilo executable
829     if options.lilobin:
830         lilo = options.lilobin
831
832     if not is_exe(lilo):
833         raise Exception("lilo executable can not be execute")
834
835     if options.dryrun:
836         logging.info("Would install MBR running lilo and using syslinux.")
837         return 0
838
839     execute_lilo(lilo, device)
840     install_syslinux_mbr(device)
841
842
843 def is_writeable(device):
844     """Check if the device is writeable for the current user
845
846     @device: partition where bootloader should be installed to"""
847
848     if not device:
849         return False
850         #raise Exception("no device for checking write permissions")
851
852     if not os.path.exists(device):
853         return False
854
855     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
856
857
858 def mount(source, target, mount_options):
859     """Mount specified source on given target
860
861     @source: name of device/ISO that should be mounted
862     @target: directory where the ISO should be mounted to
863     @options: mount specific options"""
864
865     # note: options.dryrun does not work here, as we have to
866     # locate files and identify the grml flavour
867
868     for x in file('/proc/mounts').readlines():
869         if x.startswith(source):
870             raise CriticalException("Error executing mount: %s already mounted - please unmount before invoking grml2usb" % source)
871
872     if os.path.isdir(source):
873         logging.debug("Source %s is not a device, therefore not mounting.", source)
874         return 0
875
876     logging.debug("mount %s %s %s", mount_options, source, target)
877     proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target])
878     proc.wait()
879     if proc.returncode != 0:
880         raise CriticalException("Error executing mount (no filesystem on the partition?)")
881     else:
882         logging.debug("register_mountpoint(%s)", target)
883         register_mountpoint(target)
884
885
886 def unmount(target, unmount_options):
887     """Unmount specified target
888
889     @target: target where something is mounted on and which should be unmounted
890     @options: options for umount command"""
891
892     # make sure we unmount only already mounted targets
893     target_unmount = False
894     mounts = open('/proc/mounts').readlines()
895     mountstring = re.compile(".*%s.*" % re.escape(os.path.realpath(target)))
896     for line in mounts:
897         if re.match(mountstring, line):
898             target_unmount = True
899
900     if not target_unmount:
901         logging.debug("%s not mounted anymore", target)
902     else:
903         logging.debug("umount %s %s", list(unmount_options), target)
904         proc = subprocess.Popen(["umount"] + list(unmount_options) + [target])
905         proc.wait()
906         if proc.returncode != 0:
907             raise Exception("Error executing umount")
908         else:
909             logging.debug("unregister_mountpoint(%s)", target)
910             unregister_mountpoint(target)
911
912
913 def check_for_usbdevice(device):
914     """Check whether the specified device is a removable USB device
915
916     @device: device name, like /dev/sda1 or /dev/sda
917     """
918
919     usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
920     # newer systems:
921     usbdev = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
922     if not os.path.isfile(usbdev):
923         # Ubuntu with kernel 2.6.24 for example:
924         usbdev = os.path.realpath('/sys/block/' + usbdevice + '/removable')
925
926     if os.path.isfile(usbdev):
927         is_usb = open(usbdev).readline()
928         if is_usb.find("1"):
929             return 0
930
931     return 1
932
933
934 def check_for_fat(partition):
935     """Check whether specified partition is a valid VFAT/FAT16 filesystem
936
937     @partition: device name of partition"""
938
939     try:
940         udev_info = subprocess.Popen(["/sbin/blkid", "-s", "TYPE", "-o", "value", partition],
941                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
942         filesystem = udev_info.communicate()[0].rstrip()
943
944         if udev_info.returncode == 2:
945             raise CriticalException("Failed to read device %s"
946                                     " (wrong UID/permissions or device/directory not present?)" % partition)
947
948         if options.syslinux and filesystem != "vfat":
949             raise CriticalException("Partition %s does not contain a FAT16 filesystem. (Use --fat16 or run mkfs.vfat %s)" % (partition, partition))
950
951     except OSError:
952         raise CriticalException("Sorry, /sbin/blkid not available (install e2fsprogs?)")
953
954
955 def mkdir(directory):
956     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
957
958     # just silently pass as it's just fine it the directory exists
959     if not os.path.isdir(directory):
960         try:
961             os.makedirs(directory)
962         # pylint: disable-msg=W0704
963         except OSError:
964             pass
965
966
967 def exec_rsync(source, target):
968     """Simple wrapper around rsync to install files
969
970     @source: source file/directory
971     @target: target file/directory"""
972     logging.debug("Source: %s / Target: %s", source, target)
973     proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", source, target])
974     proc.wait()
975     if proc.returncode == 12:
976         logging.critical("Fatal: No space left on device")
977         cleanup()
978         sys.exit(1)
979
980     if proc.returncode != 0:
981         logging.critical("Fatal: could not install %s", source)
982         cleanup()
983         sys.exit(1)
984
985
986 def copy_system_files(grml_flavour, iso_mount, target):
987     """copy grml's main files (like squashfs, kernel and initrd) to a given target
988
989     @grml_flavour: name of grml flavour the configuration should be generated for
990     @iso_mount: path where a grml ISO is mounted on
991     @target: path where grml's main files should be copied to"""
992
993     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
994     if squashfs is None:
995         logging.critical("Fatal: squashfs file not found")
996         raise CriticalException("error locating squashfs file")
997     else:
998         squashfs_target = target + '/live/' + grml_flavour + '/'
999         execute(mkdir, squashfs_target)
1000     exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs')
1001
1002     filesystem_module = search_file('filesystem.module', iso_mount)
1003     if filesystem_module is None:
1004         logging.critical("Fatal: filesystem.module not found")
1005         raise CriticalException("error locating filesystem.module file")
1006     else:
1007         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
1008
1009     release_target = target + '/boot/release/' + grml_flavour.replace('-', '')
1010     execute(mkdir, release_target)
1011
1012     kernel = search_file('linux26', iso_mount)
1013     if kernel is None:
1014         logging.critical("Fatal kernel not found")
1015         raise CriticalException("error locating kernel file")
1016     else:
1017         exec_rsync(kernel, release_target + '/linux26')
1018
1019     initrd = search_file('initrd.gz', iso_mount)
1020     if initrd is None:
1021         logging.critical("Fatal: initrd not found")
1022         raise CriticalException("error locating initrd file")
1023     else:
1024         exec_rsync(initrd, release_target + '/initrd.gz')
1025
1026
1027 def copy_grml_files(iso_mount, target):
1028     """copy some minor grml files to a given target
1029
1030     @iso_mount: path where a grml ISO is mounted on
1031     @target: path where grml's main files should be copied to"""
1032
1033     grml_target = target + '/grml/'
1034     execute(mkdir, grml_target)
1035
1036     for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
1037         grml_file = search_file(myfile, iso_mount)
1038         if grml_file is None:
1039             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
1040         else:
1041             exec_rsync(grml_file, grml_target + myfile)
1042
1043     grml_web_target = grml_target + '/web/'
1044     execute(mkdir, grml_web_target)
1045
1046     for myfile in 'index.html', 'style.css':
1047         grml_file = search_file(myfile, iso_mount)
1048         if grml_file is None:
1049             logging.warn("Warning: myfile %s could not be found - can not install it")
1050         else:
1051             exec_rsync(grml_file, grml_web_target + myfile)
1052
1053     grml_webimg_target = grml_web_target + '/images/'
1054     execute(mkdir, grml_webimg_target)
1055
1056     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1057         grml_file = search_file(myfile, iso_mount)
1058         if grml_file is None:
1059             logging.warn("Warning: myfile %s could not be found - can not install it")
1060         else:
1061             exec_rsync(grml_file, grml_webimg_target + myfile)
1062
1063
1064 def copy_addons(iso_mount, target):
1065     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1066
1067     @iso_mount: path where a grml ISO is mounted on
1068     @target: path where grml's main files should be copied to"""
1069
1070     addons = target + '/boot/addons/'
1071     execute(mkdir, addons)
1072
1073     # grub all-in-one image
1074     allinoneimg = search_file('allinone.img', iso_mount)
1075     if allinoneimg is None:
1076         logging.warn("Warning: allinone.img not found (that's fine if you don't need it)")
1077     else:
1078         exec_rsync(allinoneimg, addons + 'allinone.img')
1079
1080     # bsd imag
1081     bsdimg = search_file('bsd4grml', iso_mount)
1082     if bsdimg is None:
1083         logging.warn("Warning: bsd4grml not found (that's fine if you don't need it)")
1084     else:
1085         exec_rsync(bsdimg, addons + '/')
1086
1087     # freedos image
1088     balderimg = search_file('balder10.imz', iso_mount)
1089     if balderimg is None:
1090         logging.warn("Warning: balder10.imz not found (that's fine if you don't need it)")
1091     else:
1092         exec_rsync(balderimg, addons + 'balder10.imz')
1093
1094     # install hdt and pci.ids only when using syslinux (grub doesn't support it)
1095     if options.syslinux:
1096         # hdt (hardware detection tool) image
1097         hdtimg = search_file('hdt.c32', iso_mount)
1098         if hdtimg:
1099             exec_rsync(hdtimg, addons + '/hdt.c32')
1100
1101         # pci.ids file
1102         picids = search_file('pci.ids', iso_mount)
1103         if picids:
1104             exec_rsync(picids, addons + '/pci.ids')
1105
1106     # memdisk image
1107     memdiskimg = search_file('memdisk', iso_mount)
1108     if memdiskimg is None:
1109         logging.warn("Warning: memdisk not found (that's fine if you don't need it)")
1110     else:
1111         exec_rsync(memdiskimg, addons + 'memdisk')
1112
1113     # memtest86+ image
1114     memtestimg = search_file('memtest', iso_mount)
1115     if memtestimg is None:
1116         logging.warn("Warning: memtest not found (that's fine if you don't need it)")
1117     else:
1118         exec_rsync(memtestimg, addons + 'memtest')
1119
1120     # gpxe.lkrn
1121     gpxeimg = search_file('gpxe.lkrn', iso_mount)
1122     if gpxeimg is None:
1123         logging.warn("Warning: gpxe.lkrn not found (that's fine if you don't need it)")
1124     else:
1125         exec_rsync(gpxeimg, addons + 'gpxe.lkrn')
1126
1127 def copy_bootloader_files(iso_mount, target):
1128     """copy grml's bootloader files to a given target
1129
1130     @iso_mount: path where a grml ISO is mounted on
1131     @target: path where grml's main files should be copied to"""
1132
1133     syslinux_target = target + '/boot/syslinux/'
1134     execute(mkdir, syslinux_target)
1135
1136     logo = search_file('logo.16', iso_mount)
1137     exec_rsync(logo, syslinux_target + 'logo.16')
1138
1139     for ffile in ['f%d' % number for number in range(1,11) ]:
1140         bootsplash = search_file(ffile, iso_mount)
1141         if not bootsplash:
1142             continue
1143         exec_rsync(bootsplash, syslinux_target + ffile)
1144
1145     # avoid the "file is read only, overwrite anyway (y/n) ?" question
1146     # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file")
1147     if os.path.isfile(syslinux_target + 'ldlinux.sys'):
1148         os.unlink(syslinux_target + 'ldlinux.sys')
1149
1150     bootloader_dirs = ['/boot/isolinux/', '/boot/syslinux/']
1151     source_dir = None
1152     for dir in bootloader_dirs:
1153         if glob.glob(iso_mount + dir + '*default.cfg'):
1154             source_dir = dir
1155             break
1156     else:
1157         logging.critical("Fatal: file default.cfg could not be found.")
1158         logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
1159         logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
1160         logging.critical("       Please visit http://grml.org/grml2usb/#grml2usb-compat for further information.")
1161         raise
1162
1163     for expr in '*default.cfg', 'distri.cfg', \
1164                     '*grml.cfg', 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \
1165                     'isoprompt.cfg', 'options.cfg', \
1166                     'prompt.cfg', 'vesamenu.c32', 'vesamenu.cfg', 'grml.png':
1167         files = glob.glob(iso_mount + source_dir + expr)
1168         for path in files:
1169             filename = os.path.basename(path)
1170             exec_rsync(path, syslinux_target + filename)
1171
1172     # copy the addons_*.cfg file to the new syslinux directory
1173     for filename in glob.glob(iso_mount + source_dir + 'addon*.cfg'):
1174         exec_rsync(filename, syslinux_target)
1175
1176     path = search_file('hidden.cfg', iso_mount + source_dir)
1177     if path:
1178         exec_rsync(path, syslinux_target + "new_" + 'hidden.cfg')
1179
1180
1181     grub_target = target + '/boot/grub/'
1182     execute(mkdir, grub_target)
1183
1184     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1185         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1186         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1187         raise
1188     else:
1189         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1190
1191     # grml splash in grub
1192     if os.path.isfile(GRML2USB_BASE + "/grub/grml.png"):
1193         exec_rsync(GRML2USB_BASE + '/grub/grml.png', grub_target + 'grml.png')
1194
1195     # font file for graphical bootsplash in grub
1196     if os.path.isfile("/usr/share/grub/ascii.pf2"):
1197         exec_rsync('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1198
1199     # always copy grub content as it might be useful
1200     for file in glob.glob(iso_mount + '/boot/grub/*.mod'):
1201         exec_rsync(file, grub_target)
1202
1203     for file in glob.glob(iso_mount + '/boot/grub/*.img'):
1204         exec_rsync(file, grub_target)
1205
1206     for file in glob.glob(iso_mount + '/boot/grub/stage*'):
1207         exec_rsync(file, grub_target)
1208
1209 def install_iso_files(grml_flavour, iso_mount, device, target):
1210     """Copy files from ISO to given target
1211
1212     @grml_flavour: name of grml flavour the configuration should be generated for
1213     @iso_mount: path where a grml ISO is mounted on
1214     @device: device/partition where bootloader should be installed to
1215     @target: path where grml's main files should be copied to"""
1216
1217     # TODO => several improvements:
1218     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1219     # * provide alternative search_file() if file information is stored in a config.ini file?
1220     # * catch "install: .. No space left on device" & CO
1221
1222     if options.dryrun:
1223         global GRML_DEFAULT
1224         GRML_DEFAULT = grml_flavour
1225         return 0
1226     elif not options.bootloaderonly:
1227         logging.info("Copying files. This might take a while....")
1228         try:
1229             copy_system_files(grml_flavour, iso_mount, target)
1230             copy_grml_files(iso_mount, target)
1231         except CriticalException, error:
1232             logging.critical("Execution failed: %s", error)
1233             sys.exit(1)
1234
1235     if not options.skipaddons:
1236         if not search_file('addons', iso_mount):
1237             logging.info("Could not find addons, therefore not installing.")
1238         else:
1239             copy_addons(iso_mount, target)
1240
1241     if not options.copyonly:
1242         copy_bootloader_files(iso_mount, target)
1243
1244         if not options.dryrun:
1245             handle_bootloader_config(grml_flavour, device, target)
1246
1247     # make sure we sync filesystems before returning
1248     proc = subprocess.Popen(["sync"])
1249     proc.wait()
1250
1251
1252 def uninstall_files(device):
1253     """Get rid of all grml files on specified device
1254
1255     @device: partition where grml2usb files should be removed from"""
1256
1257     # TODO - not implemented yet
1258     logging.critical("TODO: uninstalling files from %s not yet implement, sorry.", device)
1259
1260
1261 def identify_grml_flavour(mountpath):
1262     """Get name of grml flavour
1263
1264     @mountpath: path where the grml ISO is mounted to
1265     @return: name of grml-flavour"""
1266
1267     version_file = search_file('grml-version', mountpath)
1268
1269     if version_file == "":
1270         logging.critical("Error: could not find grml-version file.")
1271         raise
1272
1273     try:
1274         tmpfile = open(version_file, 'r')
1275         grml_info = tmpfile.readline()
1276         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1277     except TypeError:
1278         raise
1279     except Exception, e:
1280         logging.critical("Unexpected error: %s", e)
1281         raise
1282
1283     return grml_flavour
1284
1285
1286 def modify_grub_config(filename):
1287     if options.removeoption:
1288         regexe = []
1289         for regex in options.removeoption:
1290             regexe.append(re.compile(r'%s' % regex))
1291
1292         option_re = re.compile(r'(.*/boot/release/.*linux26.*)')
1293
1294         for line in fileinput.input(filename, inplace=1):
1295             if regexe and option_re.search(line):
1296                 for regex in regexe:
1297                     line = regex.sub(' ', line)
1298
1299             sys.stdout.write(line)
1300
1301         fileinput.close()
1302
1303 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1304     """Main handler for generating grub1 configuration
1305
1306     @grml_flavour: name of grml flavour the configuration should be generated for
1307     @install_partition: partition number for use in (hd0,X)
1308     @grub_target: path of grub's configuration files
1309     @bootoptions: additional bootoptions that should be used by default"""
1310
1311     # grub1 config
1312     grub1_cfg = grub_target + 'menu.lst'
1313     logging.debug("Creating grub1 configuration file (menu.lst)")
1314
1315     # install main configuration only *once*, no matter how many ISOs we have:
1316     if os.path.isfile(grub1_cfg):
1317         string = open(grub1_cfg).readline()
1318         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1319         if not re.match(main_identifier, string):
1320             grub1_config_file = open(grub1_cfg, 'w')
1321             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1322             grub1_config_file.close()
1323     else:
1324         grub1_config_file = open(grub1_cfg, 'w')
1325         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1326         grub1_config_file.close()
1327
1328     grub_flavour_config = True
1329     if os.path.isfile(grub1_cfg):
1330         string = open(grub1_cfg).readlines()
1331         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1332         for line in string:
1333             if flavour.match(line):
1334                 grub_flavour_config = False
1335
1336     if grub_flavour_config:
1337         grub1_config_file = open(grub1_cfg, 'a')
1338         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1339         grub1_config_file.close()
1340
1341     modify_grub_config(grub1_cfg)
1342
1343     # make sure grub.conf isn't a symlink but a plain file instead,
1344     # otherwise it will break on FAT16 filesystems
1345     # this works around grub-install of (at least) Fedora 10
1346     if os.path.isfile(grub1_cfg):
1347         grubconf = grub_target + 'grub.conf'
1348         if not os.path.islink(grubconf):
1349             import shutil
1350             shutil.copyfile(grub1_cfg, grub_target + 'grub.conf')
1351
1352 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1353     """Main handler for generating grub2 configuration
1354
1355     @grml_flavour: name of grml flavour the configuration should be generated for
1356     @grub_target: path of grub's configuration files
1357     @bootoptions: additional bootoptions that should be used by default"""
1358
1359     # grub2 config
1360     grub2_cfg = grub_target + 'grub.cfg'
1361     logging.debug("Creating grub2 configuration file (grub.lst)")
1362
1363     global GRML_DEFAULT
1364
1365     # install main configuration only *once*, no matter how many ISOs we have:
1366     grub_flavour_is_default = False
1367     if os.path.isfile(grub2_cfg):
1368         string = open(grub2_cfg).readline()
1369         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1370         if not re.match(main_identifier, string):
1371             grub2_config_file = open(grub2_cfg, 'w')
1372             GRML_DEFAULT = grml_flavour
1373             grub_flavour_is_default = True
1374             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1375             grub2_config_file.close()
1376     else:
1377         grub2_config_file = open(grub2_cfg, 'w')
1378         GRML_DEFAULT = grml_flavour
1379         grub_flavour_is_default = True
1380         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1381         grub2_config_file.close()
1382
1383     # install flavour specific configuration only *once* as well
1384     grub_flavour_config = True
1385     if os.path.isfile(grub2_cfg):
1386         string = open(grub2_cfg).readlines()
1387         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1388         for line in string:
1389             if flavour.match(line):
1390                 grub_flavour_config = False
1391
1392     if grub_flavour_config:
1393         grub2_config_file = open(grub2_cfg, 'a')
1394         # display only if the grml flavour isn't the default
1395         if not grub_flavour_is_default:
1396             GRML_FLAVOURS.add(grml_flavour)
1397         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1398         grub2_config_file.close()
1399
1400     modify_grub_config(grub2_cfg)
1401
1402
1403 def handle_grub_config(grml_flavour, device, target):
1404     """Main handler for generating grub (v1 and v2) configuration
1405
1406     @grml_flavour: name of grml flavour the configuration should be generated for
1407     @device: device/partition where grub should be installed to
1408     @target: path of grub's configuration files"""
1409
1410     logging.debug("Generating grub configuration")
1411
1412     grub_target = target + '/boot/grub/'
1413     execute(mkdir, grub_target)
1414
1415     if os.path.isdir(device):
1416         install_grub1_partition = None
1417     else:
1418         if device[-1:].isdigit():
1419             install_grub1_partition = int(device[-1:]) - 1
1420         else:
1421             raise CriticalException("error validating partition schema (raw device?)")
1422
1423     # do NOT write "None" in kernel cmdline
1424     if options.bootoptions is None:
1425         bootopt = ""
1426     else:
1427         bootopt = options.bootoptions
1428
1429     # write menu.lst
1430     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1431     # write grub.cfg
1432     handle_grub2_config(grml_flavour, grub_target, bootopt)
1433
1434
1435 def initial_syslinux_config(target):
1436     """Generates intial syslinux configuration
1437
1438     @target path of syslinux's configuration files"""
1439
1440     target = target + "/"
1441     filename = target + "grmlmain.cfg"
1442     if os.path.isfile(target + "grmlmain.cfg"):
1443         return
1444     data = open(filename, "w")
1445     data.write(generate_main_syslinux_config())
1446     data.close
1447
1448     filename = target + "hiddens.cfg"
1449     data = open(filename, "w")
1450     data.write("include hidden.cfg\n")
1451     data.close()
1452
1453 def add_entry_if_not_present(filename, entry):
1454     data = open(filename, "a+")
1455     for line in data:
1456         if line == entry:
1457             break
1458     else:
1459         data.write(entry)
1460
1461     data.close()
1462
1463
1464
1465 def adjust_syslinux_bootoptions(src, flavour):
1466     append_re = re.compile("^(\s*append.*)$", re.I)
1467     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1468     # flavour_re = re.compile("(label.*)(grml\w+)")
1469     default_re = re.compile("(default.cfg)")
1470
1471     # do NOT write "None" in kernel cmdline
1472     if options.bootoptions is None:
1473         bootopt = ""
1474     else:
1475         bootopt = options.bootoptions
1476
1477     regexe = []
1478     option_re = None
1479     if options.removeoption:
1480         option_re = re.compile(r'/boot/release/.*/initrd.gz')
1481
1482         for regex in options.removeoption:
1483             regexe.append(re.compile(r'%s' % regex))
1484
1485     for line in fileinput.input(src, inplace=1):
1486         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour.replace('-', ''), line)
1487         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1488         line = default_re.sub(r'%s-\1' % flavour, line)
1489         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1490         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1491         if option_re and option_re.search(line):
1492             for regex in regexe:
1493                 line = regex.sub(' ', line)
1494         sys.stdout.write(line)
1495     fileinput.close()
1496
1497 def adjust_labels(src, replacement):
1498     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1499     for line in fileinput.input(src, inplace=1):
1500         line = label_re.sub(replacement, line)
1501         sys.stdout.write(line)
1502     fileinput.close()
1503
1504
1505 def add_syslinux_entry(filename, grml_flavour):
1506     entry_filename = "option_%s.cfg" % grml_flavour
1507     entry = "include %s\n" % entry_filename
1508
1509     add_entry_if_not_present(filename, entry)
1510     path = os.path.dirname(filename)
1511
1512     data = open(path + "/" + entry_filename, "w")
1513     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1514     data.close()
1515
1516 def modify_filenames(grml_flavour, target, filenames):
1517     grml_filename = grml_flavour.replace('-', '_')
1518     for filename in filenames:
1519         old_filename = "%s/%s" % (target, filename)
1520         new_filename = "%s/%s_%s" % (target, grml_filename, filename)
1521         os.rename(old_filename, new_filename)
1522         adjust_syslinux_bootoptions(new_filename, grml_flavour)
1523
1524
1525 def remove_default_entry(filename):
1526     default_re = re.compile("^(\s*menu\s*default\s*)$", re.I)
1527     for line in fileinput.input(filename, inplace=1):
1528         if default_re.match(line): continue
1529         sys.stdout.write(line)
1530     fileinput.close()
1531
1532
1533 def handle_syslinux_config(grml_flavour, target):
1534     """Main handler for generating syslinux configuration
1535
1536     @grml_flavour: name of grml flavour the configuration should be generated for
1537     @target: path of syslinux's configuration files"""
1538
1539     # do NOT write "None" in kernel cmdline
1540     if options.bootoptions is None:
1541         bootopt = ""
1542     else:
1543         bootopt = options.bootoptions
1544
1545     logging.debug("Generating syslinux configuration")
1546     syslinux_target = target + '/boot/syslinux/'
1547     # should be present via  copy_bootloader_files(), but make sure it exits:
1548     execute(mkdir, syslinux_target)
1549     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1550
1551     global GRML_DEFAULT
1552
1553     # install main configuration only *once*, no matter how many ISOs we have:
1554     syslinux_flavour_is_default = False
1555     syslinux_config_file = open(syslinux_cfg, 'w')
1556     syslinux_config_file.write("TIMEOUT 300\n")
1557     syslinux_config_file.write("include vesamenu.cfg\n")
1558     syslinux_config_file.close()
1559
1560     prompt_name = open(syslinux_target + 'promptname.cfg', 'w')
1561     prompt_name.write('menu label S^yslinux prompt\n')
1562     prompt_name.close()
1563
1564     initial_syslinux_config(syslinux_target)
1565     if search_file('default.cfg', syslinux_target):
1566         modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1567
1568     filename = search_file("new_hidden.cfg", syslinux_target)
1569
1570
1571     flavour_filename = grml_flavour.replace('-', '_')
1572     # process hidden file
1573     if not search_file("hidden.cfg", syslinux_target):
1574         new_hidden = syslinux_target + "hidden.cfg"
1575         os.rename(filename, new_hidden)
1576         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1577     else:
1578         new_hidden =  "%s_hidden.cfg" % (flavour_filename)
1579         new_hidden_file =  "%s/%s" % (syslinux_target, new_hidden)
1580         os.rename(filename, new_hidden_file)
1581         adjust_labels(new_hidden_file, r'\1 %s-\2' % grml_flavour)
1582         adjust_syslinux_bootoptions(new_hidden_file, grml_flavour)
1583         entry = 'include %s\n' % new_hidden
1584         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1585
1586
1587
1588     new_default = "%s_default.cfg" % (flavour_filename)
1589     entry = 'include %s\n' % new_default
1590     defaults_file = '%s/defaults.cfg' % syslinux_target
1591
1592     if os.path.isfile(defaults_file):
1593         new_default_with_path = "%s/%s" % (syslinux_target, new_default)
1594         new_grml_cfg = "%s/%s_grml.cfg" % ( syslinux_target, flavour_filename)
1595
1596         # remove default menu entry in menu
1597         remove_default_entry(new_default_with_path)
1598
1599         # adjust all labels for additional isos
1600         adjust_labels(new_default_with_path, r'\1 %s' % grml_flavour)
1601         adjust_labels(new_grml_cfg, r'\1 %s-\2' % grml_flavour)
1602
1603
1604     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1605
1606     add_syslinux_entry("%s/additional.cfg" % syslinux_target, flavour_filename)
1607
1608
1609 def handle_bootloader_config(grml_flavour, device, target):
1610     """Main handler for generating bootloader's configuration
1611
1612     @grml_flavour: name of grml flavour the configuration should be generated for
1613     @device: device/partition where bootloader should be installed to
1614     @target: path of bootloader's configuration files"""
1615
1616     if options.skipsyslinuxconfig:
1617         logging.info("Skipping generation of syslinux configuration as requested.")
1618     else:
1619         try:
1620             handle_syslinux_config(grml_flavour, target)
1621         except CriticalException, error:
1622             logging.critical("Fatal: %s", error)
1623             sys.exit(1)
1624
1625     if options.skipgrubconfig:
1626         logging.info("Skipping generation of grub configuration as requested.")
1627     else:
1628         try:
1629             handle_grub_config(grml_flavour, device, target)
1630         except CriticalException, error:
1631             logging.critical("Fatal: %s", error)
1632             sys.exit(1)
1633
1634
1635 def handle_dir(live_image, device):
1636     """Main logic for copying files of the currently running grml system.
1637
1638     @live_image: directory where currently running live system resides (usually /live/image)
1639     @device: partition where the specified ISO should be installed to"""
1640
1641     logging.info("Using %s as install base", live_image)
1642
1643     if os.path.isdir(device):
1644         logging.info("Specified target is a directory, therefore not mounting.")
1645         device_mountpoint = device
1646         remove_device_mountpoint = False
1647     else:
1648         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1649         register_tmpfile(device_mountpoint)
1650         remove_device_mountpoint = True
1651         try:
1652             mount(device, device_mountpoint, "")
1653         except CriticalException, error:
1654             logging.critical("Fatal: %s", error)
1655             cleanup()
1656             sys.exit(1)
1657
1658     try:
1659         try:
1660             grml_flavour = identify_grml_flavour(live_image)
1661             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1662             install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1663         except TypeError:
1664             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1665             sys.exit(1)
1666     finally:
1667         if remove_device_mountpoint:
1668             try:
1669                 unmount(device_mountpoint, "")
1670                 if os.path.isdir(device_mountpoint):
1671                     os.rmdir(device_mountpoint)
1672                     unregister_tmpfile(device_mountpoint)
1673             except CriticalException, error:
1674                 logging.critical("Fatal: %s", error)
1675                 cleanup()
1676
1677
1678 def handle_iso(iso, device):
1679     """Main logic for mounting ISOs and copying files.
1680
1681     @iso: full path to the ISO that should be installed to the specified device
1682     @device: partition where the specified ISO should be installed to"""
1683
1684     logging.info("Using ISO %s", iso)
1685
1686     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1687     register_tmpfile(iso_mountpoint)
1688     remove_iso_mountpoint = True
1689
1690     if not os.path.isfile(iso):
1691         logging.critical("Fatal: specified ISO %s could not be read", iso)
1692         cleanup()
1693         sys.exit(1)
1694
1695     try:
1696         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1697     except CriticalException, error:
1698         logging.critical("Fatal: %s", error)
1699         sys.exit(1)
1700
1701     if os.path.isdir(device):
1702         logging.info("Specified target is a directory, therefore not mounting.")
1703         device_mountpoint = device
1704         remove_device_mountpoint = False
1705         # skip_mbr = True
1706     else:
1707         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1708         register_tmpfile(device_mountpoint)
1709         remove_device_mountpoint = True
1710         try:
1711             mount(device, device_mountpoint, "")
1712         except CriticalException, error:
1713             logging.critical("Fatal: %s", error)
1714             cleanup()
1715             sys.exit(1)
1716
1717     try:
1718         try:
1719             grml_flavour = identify_grml_flavour(iso_mountpoint)
1720             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1721             install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1722         except TypeError:
1723             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1724             sys.exit(1)
1725     finally:
1726         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1727             unmount(iso_mountpoint, "")
1728             os.rmdir(iso_mountpoint)
1729             unregister_tmpfile(iso_mountpoint)
1730         if remove_device_mountpoint:
1731             try:
1732                 unmount(device_mountpoint, "")
1733                 if os.path.isdir(device_mountpoint):
1734                     os.rmdir(device_mountpoint)
1735                     unregister_tmpfile(device_mountpoint)
1736             except CriticalException, error:
1737                 logging.critical("Fatal: %s", error)
1738                 cleanup()
1739
1740
1741 def handle_mbr(device):
1742     """Main handler for installing master boot record (MBR)
1743
1744     @device: device where the MBR should be installed to"""
1745
1746     if options.dryrun:
1747         logging.info("Would install MBR")
1748         return 0
1749
1750     if device[-1:].isdigit():
1751         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1752         partition_number = int(device[-1:]) - 1
1753         skip_install_mir_mbr = False
1754
1755     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1756     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1757     if mbr_device == "/dev/loop":
1758         mbr_device = device
1759         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1760         skip_install_mir_mbr = True
1761
1762     try:
1763         if options.syslinuxmbr:
1764             handle_syslinux_mbr(mbr_device)
1765         elif not skip_install_mir_mbr:
1766             if options.mbrmenu:
1767                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True)
1768             else:
1769                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False)
1770     except IOError, error:
1771         logging.critical("Execution failed: %s", error)
1772         sys.exit(1)
1773     except Exception, error:
1774         logging.critical("Execution failed: %s", error)
1775         sys.exit(1)
1776
1777
1778 def handle_vfat(device):
1779     """Check for FAT specific settings and options
1780
1781     @device: device that should checked / formated"""
1782
1783     # make sure we have mkfs.vfat available
1784     if options.fat16:
1785         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1786             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1787             logging.critical('Please make sure to install dosfstools.')
1788             sys.exit(1)
1789
1790         exec_mkfs = False
1791         if options.force:
1792             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1793             exec_mkfs = True
1794         else:
1795             # make sure the user is aware of what he is doing
1796             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1797             if f == "y" or f == "Y":
1798                 logging.info("Note: you can skip this question using the option --force")
1799                 exec_mkfs = True
1800
1801         if exec_mkfs:
1802             try:
1803                 mkfs_fat16(device)
1804             except CriticalException, error:
1805                 logging.critical("Execution failed: %s", error)
1806                 sys.exit(1)
1807         else:
1808             sys.exit(1)
1809
1810     # check for vfat filesystem
1811     if device is not None and not os.path.isdir(device):
1812         try:
1813             check_for_fat(device)
1814         except CriticalException, error:
1815             logging.critical("Execution failed: %s", error)
1816             sys.exit(1)
1817
1818     if not os.path.isdir(device) and not check_for_usbdevice(device):
1819         print "Warning: the specified device %s does not look like a removable usb device." % device
1820         f = raw_input("Do you really want to continue? y/N ")
1821         if f == "y" or f == "Y":
1822             pass
1823         else:
1824             sys.exit(1)
1825
1826
1827 def handle_compat_warning(device):
1828     """Backwards compatible checks
1829
1830     @device: device that should be checked"""
1831
1832     # make sure we can replace old grml2usb script and warn user when using old way of life:
1833     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1834         print "Warning: the semantics of grml2usb has changed."
1835         print "Instead of using grml2usb /path/to/iso %s you might" % device
1836         print "want to use grml2usb /path/to/iso /dev/... instead."
1837         print "Please check out the grml2usb manpage for details."
1838         f = raw_input("Do you really want to continue? y/N ")
1839         if f == "y" or f == "Y":
1840             pass
1841         else:
1842             sys.exit(1)
1843
1844
1845 def handle_logging():
1846     """Log handling and configuration"""
1847
1848     if options.verbose and options.quiet:
1849         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1850
1851     if options.verbose:
1852         FORMAT = "Debug: %(asctime)-15s %(message)s"
1853         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1854     elif options.quiet:
1855         FORMAT = "Critical: %(message)s"
1856         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1857     else:
1858         FORMAT = "%(message)s"
1859         logging.basicConfig(level=logging.INFO, format=FORMAT)
1860
1861
1862 def handle_bootloader(device):
1863     """wrapper for installing bootloader
1864
1865     @device: device where bootloader should be installed to"""
1866
1867     # Install bootloader only if not using the --copy-only option
1868     if options.copyonly:
1869         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1870     elif os.path.isdir(device):
1871         logging.info("Not installing bootloader as %s is a directory.", device)
1872     else:
1873         install_bootloader(device)
1874
1875
1876 def main():
1877     """Main function [make pylint happy :)]"""
1878
1879     if options.version:
1880         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1881         sys.exit(0)
1882
1883     if len(args) < 2:
1884         parser.error("invalid usage")
1885
1886     # log handling
1887     handle_logging()
1888
1889     # make sure we have the appropriate permissions
1890     check_uid_root()
1891
1892     logging.info("Executing grml2usb version %s", PROG_VERSION)
1893
1894     if options.dryrun:
1895         logging.info("Running in simulation mode as requested via option dry-run.")
1896
1897     # specified arguments
1898     device = args[len(args) - 1]
1899     isos = args[0:len(args) - 1]
1900
1901     if not os.path.isdir(device):
1902         if device[-1:].isdigit():
1903             if int(device[-1:]) > 4 or device[-2:].isdigit():
1904                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1905                 sys.exit(1)
1906         else:
1907             if os.path.exists(device):
1908                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1909                 sys.exit(1)
1910
1911     if not which("rsync"):
1912         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1913         sys.exit(1)
1914
1915     # provide upgrade path
1916     handle_compat_warning(device)
1917
1918     # check for vfat partition
1919     handle_vfat(device)
1920
1921     # main operation (like installing files)
1922     for iso in isos:
1923         if os.path.isdir(iso):
1924             handle_dir(iso, device)
1925         else:
1926             handle_iso(iso, device)
1927
1928     # install mbr
1929     if not os.path.isdir(device):
1930         if not options.skipmbr:
1931             handle_mbr(device)
1932
1933     handle_bootloader(device)
1934
1935     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
1936
1937     for flavour in GRML_FLAVOURS:
1938         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
1939
1940     # finally be politely :)
1941     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
1942
1943
1944 if __name__ == "__main__":
1945     try:
1946         main()
1947     except KeyboardInterrupt:
1948         logging.info("Received KeyboardInterrupt")
1949         cleanup()
1950
1951 ## END OF FILE #################################################################
1952 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8