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