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