Update changelog and docs for new release (0.9.13)
[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"
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         logging.critical("       Please visit http://grml.org/grml2usb/#grml2usb-compat for further information.")
1137         raise
1138
1139     for filename in 'addons.cfg', 'default.cfg', 'distri.cfg', \
1140                     'grml.cfg', 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \
1141                     'isoprompt.cfg', 'options.cfg', \
1142                     'prompt.cfg', 'vesamenu.c32', 'vesamenu.cfg', 'grml.png':
1143         path = search_file(filename, iso_mount + '/boot/isolinux/')
1144         if not path:
1145             print filename
1146             continue
1147         exec_rsync(path, syslinux_target + filename)
1148
1149     path = search_file('hidden.cfg', iso_mount + '/boot/isolinux/')
1150     exec_rsync(path, syslinux_target + "new_" + 'hidden.cfg')
1151
1152
1153     grub_target = target + '/boot/grub/'
1154     execute(mkdir, grub_target)
1155
1156     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1157         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1158         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1159         raise
1160     else:
1161         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1162
1163     # grml splash in grub
1164     if os.path.isfile(GRML2USB_BASE + "/grub/grml.png"):
1165         exec_rsync(GRML2USB_BASE + '/grub/grml.png', grub_target + 'grml.png')
1166
1167     # font file for graphical bootsplash in grub
1168     if os.path.isfile("/usr/share/grub/ascii.pf2"):
1169         exec_rsync('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1170
1171
1172 def install_iso_files(grml_flavour, iso_mount, device, target):
1173     """Copy files from ISO to given target
1174
1175     @grml_flavour: name of grml flavour the configuration should be generated for
1176     @iso_mount: path where a grml ISO is mounted on
1177     @device: device/partition where bootloader should be installed to
1178     @target: path where grml's main files should be copied to"""
1179
1180     # TODO => several improvements:
1181     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1182     # * provide alternative search_file() if file information is stored in a config.ini file?
1183     # * catch "install: .. No space left on device" & CO
1184
1185     if options.dryrun:
1186         return 0
1187     elif not options.bootloaderonly:
1188         logging.info("Copying files. This might take a while....")
1189         try:
1190             copy_system_files(grml_flavour, iso_mount, target)
1191             copy_grml_files(iso_mount, target)
1192         except CriticalException, error:
1193             logging.critical("Execution failed: %s", error)
1194             sys.exit(1)
1195
1196     if not options.skipaddons:
1197         if grml_flavour.endswith('-small'):
1198             logging.info("Note: grml-small doesn't provide any addons, not installing them therefore.")
1199         else:
1200             copy_addons(iso_mount, target)
1201
1202     if not options.copyonly:
1203         copy_bootloader_files(iso_mount, target)
1204
1205         if not options.dryrun:
1206             handle_bootloader_config(grml_flavour, device, target)
1207
1208     # make sure we sync filesystems before returning
1209     proc = subprocess.Popen(["sync"])
1210     proc.wait()
1211
1212
1213 def uninstall_files(device):
1214     """Get rid of all grml files on specified device
1215
1216     @device: partition where grml2usb files should be removed from"""
1217
1218     # TODO - not implemented yet
1219     logging.critical("TODO: uninstalling files from %s not yet implement, sorry.", device)
1220
1221
1222 def identify_grml_flavour(mountpath):
1223     """Get name of grml flavour
1224
1225     @mountpath: path where the grml ISO is mounted to
1226     @return: name of grml-flavour"""
1227
1228     version_file = search_file('grml-version', mountpath)
1229
1230     if version_file == "":
1231         logging.critical("Error: could not find grml-version file.")
1232         raise
1233
1234     try:
1235         tmpfile = open(version_file, 'r')
1236         grml_info = tmpfile.readline()
1237         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1238     except TypeError:
1239         raise
1240     except Exception, e:
1241         logging.critical("Unexpected error: %s", e)
1242         raise
1243
1244     return grml_flavour
1245
1246
1247 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1248     """Main handler for generating grub1 configuration
1249
1250     @grml_flavour: name of grml flavour the configuration should be generated for
1251     @install_partition: partition number for use in (hd0,X)
1252     @grub_target: path of grub's configuration files
1253     @bootoptions: additional bootoptions that should be used by default"""
1254
1255     # grub1 config
1256     grub1_cfg = grub_target + 'menu.lst'
1257     logging.debug("Creating grub1 configuration file (menu.lst)")
1258
1259     # install main configuration only *once*, no matter how many ISOs we have:
1260     if os.path.isfile(grub1_cfg):
1261         string = open(grub1_cfg).readline()
1262         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1263         if not re.match(main_identifier, string):
1264             grub1_config_file = open(grub1_cfg, 'w')
1265             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1266             grub1_config_file.close()
1267     else:
1268         grub1_config_file = open(grub1_cfg, 'w')
1269         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1270         grub1_config_file.close()
1271
1272     grub_flavour_config = True
1273     if os.path.isfile(grub1_cfg):
1274         string = open(grub1_cfg).readlines()
1275         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1276         for line in string:
1277             if flavour.match(line):
1278                 grub_flavour_config = False
1279
1280     if grub_flavour_config:
1281         grub1_config_file = open(grub1_cfg, 'a')
1282         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1283         grub1_config_file.close()
1284
1285     # make sure grub.conf isn't a symlink but a plain file instead,
1286     # otherwise it will break on FAT16 filesystems
1287     # this works around grub-install of (at least) Fedora 10
1288     if os.path.isfile(grub1_cfg):
1289         grubconf = grub_target + 'grub.conf'
1290         if not os.path.islink(grubconf):
1291             import shutil
1292             shutil.copyfile(grub1_cfg, grub_target + 'grub.conf')
1293
1294 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1295     """Main handler for generating grub2 configuration
1296
1297     @grml_flavour: name of grml flavour the configuration should be generated for
1298     @grub_target: path of grub's configuration files
1299     @bootoptions: additional bootoptions that should be used by default"""
1300
1301     # grub2 config
1302     grub2_cfg = grub_target + 'grub.cfg'
1303     logging.debug("Creating grub2 configuration file (grub.lst)")
1304
1305     global GRML_DEFAULT
1306
1307     # install main configuration only *once*, no matter how many ISOs we have:
1308     grub_flavour_is_default = False
1309     if os.path.isfile(grub2_cfg):
1310         string = open(grub2_cfg).readline()
1311         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1312         if not re.match(main_identifier, string):
1313             grub2_config_file = open(grub2_cfg, 'w')
1314             GRML_DEFAULT = grml_flavour
1315             grub_flavour_is_default = True
1316             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1317             grub2_config_file.close()
1318     else:
1319         grub2_config_file = open(grub2_cfg, 'w')
1320         GRML_DEFAULT = grml_flavour
1321         grub_flavour_is_default = True
1322         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1323         grub2_config_file.close()
1324
1325     # install flavour specific configuration only *once* as well
1326     grub_flavour_config = True
1327     if os.path.isfile(grub2_cfg):
1328         string = open(grub2_cfg).readlines()
1329         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1330         for line in string:
1331             if flavour.match(line):
1332                 grub_flavour_config = False
1333
1334     if grub_flavour_config:
1335         grub2_config_file = open(grub2_cfg, 'a')
1336         # display only if the grml flavour isn't the default
1337         if not grub_flavour_is_default:
1338             GRML_FLAVOURS.add(grml_flavour)
1339         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1340         grub2_config_file.close()
1341
1342
1343 def handle_grub_config(grml_flavour, device, target):
1344     """Main handler for generating grub (v1 and v2) configuration
1345
1346     @grml_flavour: name of grml flavour the configuration should be generated for
1347     @device: device/partition where grub should be installed to
1348     @target: path of grub's configuration files"""
1349
1350     logging.debug("Generating grub configuration")
1351
1352     grub_target = target + '/boot/grub/'
1353     execute(mkdir, grub_target)
1354
1355     if os.path.isdir(device):
1356         install_grub1_partition = None
1357     else:
1358         if device[-1:].isdigit():
1359             install_grub1_partition = int(device[-1:]) - 1
1360         else:
1361             raise CriticalException("error validating partition schema (raw device?)")
1362
1363     # do NOT write "None" in kernel cmdline
1364     if options.bootoptions is None:
1365         bootopt = ""
1366     else:
1367         bootopt = options.bootoptions
1368
1369     # write menu.lst
1370     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1371     # write grub.cfg
1372     handle_grub2_config(grml_flavour, grub_target, bootopt)
1373
1374
1375 def initial_syslinux_config(target):
1376     """Generates intial syslinux configuration
1377
1378     @target path of syslinux's configuration files"""
1379
1380     target = target + "/"
1381     filename = target + "grmlmain.cfg"
1382     if os.path.isfile(target + "grmlmain.cfg"):
1383         return
1384     data = open(filename, "w")
1385     data.write(generate_main_syslinux_config())
1386     data.close
1387
1388     filename = target + "hiddens.cfg"
1389     data = open(filename, "w")
1390     data.write("include hidden.cfg\n")
1391     data.close()
1392
1393 def add_entry_if_not_present(filename, entry):
1394     data = open(filename, "a+")
1395     for line in data:
1396         if line == entry:
1397             break
1398     else:
1399         data.write(entry)
1400
1401     data.close()
1402
1403
1404
1405 def adjust_syslinux_bootoptions(src, flavour):
1406     append_re = re.compile("^(\s*append.*)$", re.I)
1407     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1408     # flavour_re = re.compile("(label.*)(grml\w+)")
1409     default_re = re.compile("(default.cfg)")
1410
1411     # do NOT write "None" in kernel cmdline
1412     if options.bootoptions is None:
1413         bootopt = ""
1414     else:
1415         bootopt = options.bootoptions
1416
1417     for line in fileinput.input(src, inplace=1):
1418         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour, line)
1419         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1420         line = default_re.sub(r'%s-\1' % flavour, line)
1421         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1422         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1423         sys.stdout.write(line)
1424     fileinput.close()
1425
1426 def adjust_labels(src, flavour):
1427     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1428     for line in fileinput.input(src, inplace=1):
1429         line = label_re.sub(r'\1 %s-\2' % flavour, line)
1430         sys.stdout.write(line)
1431     fileinput.close()
1432
1433
1434 def add_syslinux_entry(filename, grml_flavour):
1435     entry_filename = "option_%s.cfg" % grml_flavour
1436     entry = "include %s\n" % entry_filename
1437
1438     add_entry_if_not_present(filename, entry)
1439     path = os.path.dirname(filename)
1440
1441     data = open(path + "/" + entry_filename, "w")
1442     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1443     data.close()
1444
1445 def modify_filenames(grml_flavour, target, filenames):
1446     grml_flavour = grml_flavour.replace('-', '_')
1447     for filename in filenames:
1448         old_filename = "%s/%s" % (target, filename)
1449         new_filename = "%s/%s_%s" % (target, grml_flavour, filename)
1450         os.rename(old_filename, new_filename)
1451         adjust_syslinux_bootoptions(new_filename, grml_flavour)
1452
1453
1454 def remove_default_entry(filename):
1455     default_re = re.compile("^(\s*menu\s*default\s*)$", re.I)
1456     for line in fileinput.input(filename, inplace=1):
1457         if default_re.match(line): continue
1458         sys.stdout.write(line)
1459     fileinput.close()
1460
1461
1462 def handle_syslinux_config(grml_flavour, target):
1463     """Main handler for generating syslinux configuration
1464
1465     @grml_flavour: name of grml flavour the configuration should be generated for
1466     @target: path of syslinux's configuration files"""
1467
1468     # do NOT write "None" in kernel cmdline
1469     if options.bootoptions is None:
1470         bootopt = ""
1471     else:
1472         bootopt = options.bootoptions
1473
1474     logging.debug("Generating syslinux configuration")
1475     syslinux_target = target + '/boot/syslinux/'
1476     # should be present via  copy_bootloader_files(), but make sure it exits:
1477     execute(mkdir, syslinux_target)
1478     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1479
1480     global GRML_DEFAULT
1481
1482     # install main configuration only *once*, no matter how many ISOs we have:
1483     syslinux_flavour_is_default = False
1484     syslinux_config_file = open(syslinux_cfg, 'w')
1485     syslinux_config_file.write("TIMEOUT 300\n")
1486     syslinux_config_file.write("include vesamenu.cfg\n")
1487     syslinux_config_file.close()
1488
1489     prompt_name = open(syslinux_target + 'promptname.cfg', 'w')
1490     prompt_name.write('menu label S^yslinux prompt\n')
1491     prompt_name.close()
1492
1493     initial_syslinux_config(syslinux_target)
1494     modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1495
1496     filename = search_file("new_hidden.cfg", syslinux_target)
1497
1498
1499     flavour_filename = grml_flavour.replace('-', '_')
1500     # process hidden file
1501     if not search_file("hidden.cfg", syslinux_target):
1502         new_hidden = syslinux_target + "hidden.cfg"
1503         os.rename(filename, new_hidden)
1504         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1505     else:
1506         new_hidden =  "%s_hidden.cfg" % (flavour_filename)
1507         new_hidden_file =  "%s/%s" % (syslinux_target, new_hidden)
1508         os.rename(filename, new_hidden_file)
1509         adjust_labels(new_hidden_file, flavour_filename)
1510         adjust_syslinux_bootoptions(new_hidden_file, flavour_filename)
1511         entry = 'include %s\n' % new_hidden
1512         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1513
1514
1515
1516     new_default = "%s_default.cfg" % (flavour_filename)
1517     entry = 'include %s\n' % new_default
1518     defaults_file = '%s/defaults.cfg' % syslinux_target
1519
1520     if os.path.isfile(defaults_file):
1521         remove_default_entry('%s/%s_default.cfg' % (syslinux_target, flavour_filename))
1522
1523     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1524
1525     add_syslinux_entry("%s/additional.cfg" % syslinux_target, flavour_filename)
1526
1527
1528 def handle_bootloader_config(grml_flavour, device, target):
1529     """Main handler for generating bootloader's configuration
1530
1531     @grml_flavour: name of grml flavour the configuration should be generated for
1532     @device: device/partition where bootloader should be installed to
1533     @target: path of bootloader's configuration files"""
1534
1535     if options.skipsyslinuxconfig:
1536         logging.info("Skipping generation of syslinux configuration as requested.")
1537     else:
1538         try:
1539             handle_syslinux_config(grml_flavour, target)
1540         except CriticalException, error:
1541             logging.critical("Fatal: %s", error)
1542             sys.exit(1)
1543
1544     if options.skipgrubconfig:
1545         logging.info("Skipping generation of grub configuration as requested.")
1546     else:
1547         try:
1548             handle_grub_config(grml_flavour, device, target)
1549         except CriticalException, error:
1550             logging.critical("Fatal: %s", error)
1551             sys.exit(1)
1552
1553
1554 def handle_dir(live_image, device):
1555     """Main logic for copying files of the currently running grml system.
1556
1557     @live_image: directory where currently running live system resides (usually /live/image)
1558     @device: partition where the specified ISO should be installed to"""
1559
1560     logging.info("Using %s as install base", live_image)
1561
1562     if os.path.isdir(device):
1563         logging.info("Specified target is a directory, therefore not mounting.")
1564         device_mountpoint = device
1565         remove_device_mountpoint = False
1566     else:
1567         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1568         register_tmpfile(device_mountpoint)
1569         remove_device_mountpoint = True
1570         try:
1571             mount(device, device_mountpoint, "")
1572         except CriticalException, error:
1573             logging.critical("Fatal: %s", error)
1574             cleanup()
1575             sys.exit(1)
1576
1577     try:
1578         try:
1579             grml_flavour = identify_grml_flavour(live_image)
1580             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1581             install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1582         except TypeError:
1583             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1584             sys.exit(1)
1585     finally:
1586         if remove_device_mountpoint:
1587             try:
1588                 unmount(device_mountpoint, "")
1589                 if os.path.isdir(device_mountpoint):
1590                     os.rmdir(device_mountpoint)
1591                     unregister_tmpfile(device_mountpoint)
1592             except CriticalException, error:
1593                 logging.critical("Fatal: %s", error)
1594                 cleanup()
1595
1596
1597 def handle_iso(iso, device):
1598     """Main logic for mounting ISOs and copying files.
1599
1600     @iso: full path to the ISO that should be installed to the specified device
1601     @device: partition where the specified ISO should be installed to"""
1602
1603     logging.info("Using ISO %s", iso)
1604
1605     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1606     register_tmpfile(iso_mountpoint)
1607     remove_iso_mountpoint = True
1608
1609     if not os.path.isfile(iso):
1610         logging.critical("Fatal: specified ISO %s could not be read", iso)
1611         cleanup()
1612         sys.exit(1)
1613
1614     try:
1615         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1616     except CriticalException, error:
1617         logging.critical("Fatal: %s", error)
1618         sys.exit(1)
1619
1620     if os.path.isdir(device):
1621         logging.info("Specified target is a directory, therefore not mounting.")
1622         device_mountpoint = device
1623         remove_device_mountpoint = False
1624         # skip_mbr = True
1625     else:
1626         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1627         register_tmpfile(device_mountpoint)
1628         remove_device_mountpoint = True
1629         try:
1630             mount(device, device_mountpoint, "")
1631         except CriticalException, error:
1632             logging.critical("Fatal: %s", error)
1633             cleanup()
1634             sys.exit(1)
1635
1636     try:
1637         try:
1638             grml_flavour = identify_grml_flavour(iso_mountpoint)
1639             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1640             install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1641         except TypeError:
1642             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1643             sys.exit(1)
1644     finally:
1645         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1646             unmount(iso_mountpoint, "")
1647             os.rmdir(iso_mountpoint)
1648             unregister_tmpfile(iso_mountpoint)
1649         if remove_device_mountpoint:
1650             try:
1651                 unmount(device_mountpoint, "")
1652                 if os.path.isdir(device_mountpoint):
1653                     os.rmdir(device_mountpoint)
1654                     unregister_tmpfile(device_mountpoint)
1655             except CriticalException, error:
1656                 logging.critical("Fatal: %s", error)
1657                 cleanup()
1658
1659
1660 def handle_mbr(device):
1661     """Main handler for installing master boot record (MBR)
1662
1663     @device: device where the MBR should be installed to"""
1664
1665     if options.dryrun:
1666         logging.info("Would install MBR")
1667         return 0
1668
1669     if device[-1:].isdigit():
1670         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1671         partition_number = int(device[-1:]) - 1
1672         skip_install_mir_mbr = False
1673
1674     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1675     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1676     if mbr_device == "/dev/loop":
1677         mbr_device = device
1678         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1679         skip_install_mir_mbr = True
1680
1681     try:
1682         if options.syslinuxmbr:
1683             handle_syslinux_mbr(mbr_device)
1684         elif not skip_install_mir_mbr:
1685             if options.mbrmenu:
1686                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True)
1687             else:
1688                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False)
1689     except IOError, error:
1690         logging.critical("Execution failed: %s", error)
1691         sys.exit(1)
1692     except Exception, error:
1693         logging.critical("Execution failed: %s", error)
1694         sys.exit(1)
1695
1696
1697 def handle_vfat(device):
1698     """Check for FAT specific settings and options
1699
1700     @device: device that should checked / formated"""
1701
1702     # make sure we have mkfs.vfat available
1703     if options.fat16:
1704         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1705             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1706             logging.critical('Please make sure to install dosfstools.')
1707             sys.exit(1)
1708
1709         exec_mkfs = False
1710         if options.force:
1711             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1712             exec_mkfs = True
1713         else:
1714             # make sure the user is aware of what he is doing
1715             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1716             if f == "y" or f == "Y":
1717                 logging.info("Note: you can skip this question using the option --force")
1718                 exec_mkfs = True
1719
1720         if exec_mkfs:
1721             try:
1722                 mkfs_fat16(device)
1723             except CriticalException, error:
1724                 logging.critical("Execution failed: %s", error)
1725                 sys.exit(1)
1726         else:
1727             sys.exit(1)
1728
1729     # check for vfat filesystem
1730     if device is not None and not os.path.isdir(device):
1731         try:
1732             check_for_fat(device)
1733         except CriticalException, error:
1734             logging.critical("Execution failed: %s", error)
1735             sys.exit(1)
1736
1737     if not os.path.isdir(device) and not check_for_usbdevice(device):
1738         print "Warning: the specified device %s does not look like a removable usb device." % device
1739         f = raw_input("Do you really want to continue? y/N ")
1740         if f == "y" or f == "Y":
1741             pass
1742         else:
1743             sys.exit(1)
1744
1745
1746 def handle_compat_warning(device):
1747     """Backwards compatible checks
1748
1749     @device: device that should be checked"""
1750
1751     # make sure we can replace old grml2usb script and warn user when using old way of life:
1752     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1753         print "Warning: the semantics of grml2usb has changed."
1754         print "Instead of using grml2usb /path/to/iso %s you might" % device
1755         print "want to use grml2usb /path/to/iso /dev/... instead."
1756         print "Please check out the grml2usb manpage for details."
1757         f = raw_input("Do you really want to continue? y/N ")
1758         if f == "y" or f == "Y":
1759             pass
1760         else:
1761             sys.exit(1)
1762
1763
1764 def handle_logging():
1765     """Log handling and configuration"""
1766
1767     if options.verbose and options.quiet:
1768         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1769
1770     if options.verbose:
1771         FORMAT = "Debug: %(asctime)-15s %(message)s"
1772         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1773     elif options.quiet:
1774         FORMAT = "Critical: %(message)s"
1775         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1776     else:
1777         FORMAT = "%(message)s"
1778         logging.basicConfig(level=logging.INFO, format=FORMAT)
1779
1780
1781 def handle_bootloader(device):
1782     """wrapper for installing bootloader
1783
1784     @device: device where bootloader should be installed to"""
1785
1786     # Install bootloader only if not using the --copy-only option
1787     if options.copyonly:
1788         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1789     elif os.path.isdir(device):
1790         logging.info("Not installing bootloader as %s is a directory.", device)
1791     else:
1792         install_bootloader(device)
1793
1794
1795 def main():
1796     """Main function [make pylint happy :)]"""
1797
1798     if options.version:
1799         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1800         sys.exit(0)
1801
1802     if len(args) < 2:
1803         parser.error("invalid usage")
1804
1805     # log handling
1806     handle_logging()
1807
1808     # make sure we have the appropriate permissions
1809     check_uid_root()
1810
1811     logging.info("Executing grml2usb version %s", PROG_VERSION)
1812
1813     if options.dryrun:
1814         logging.info("Running in simulation mode as requested via option dry-run.")
1815
1816     # specified arguments
1817     device = args[len(args) - 1]
1818     isos = args[0:len(args) - 1]
1819
1820     if not os.path.isdir(device):
1821         if device[-1:].isdigit():
1822             if int(device[-1:]) > 4 or device[-2:].isdigit():
1823                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1824                 sys.exit(1)
1825         else:
1826             if os.path.exists(device):
1827                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1828                 sys.exit(1)
1829
1830     if not which("rsync"):
1831         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1832         sys.exit(1)
1833
1834     # provide upgrade path
1835     handle_compat_warning(device)
1836
1837     # check for vfat partition
1838     handle_vfat(device)
1839
1840     # main operation (like installing files)
1841     for iso in isos:
1842         if os.path.isdir(iso):
1843             handle_dir(iso, device)
1844         else:
1845             handle_iso(iso, device)
1846
1847     # install mbr
1848     if not os.path.isdir(device):
1849         if not options.skipmbr:
1850             handle_mbr(device)
1851
1852     handle_bootloader(device)
1853
1854     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
1855
1856     for flavour in GRML_FLAVOURS:
1857         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
1858
1859     # finally be politely :)
1860     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
1861
1862 if __name__ == "__main__":
1863     try:
1864         main()
1865     except KeyboardInterrupt:
1866         logging.info("Received KeyboardInterrupt")
1867         cleanup()
1868
1869 ## END OF FILE #################################################################
1870 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8