6ecc0aa6d006f577e6581d54693eb01f108f81d6
[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 include options.cfg
536 include addons.cfg
537
538 label help
539   include promptname.cfg
540   config prompt.cfg
541   text help
542                                         Jump to old style isolinux prompt
543                                         featuring further information
544                                         regarding available boot options.
545   endtext
546
547
548 include hiddens.cfg
549 """)
550
551
552 def generate_flavour_specific_syslinux_config(grml_flavour):
553     """Generate flavour specific configuration for use in syslinux.cfg
554
555     @grml_flavour: name of grml flavour the configuration should be generated for"""
556
557
558     return("""\
559 menu begin grml %(grml_flavour)s
560     menu title %(grml_flavour)s
561     label mainmenu
562     menu label ^Back to main menu...
563     menu exit
564     menu separator
565     # include config for boot parameters from disk
566     include %(grml_flavour)s-grml.cfg
567     menu hide
568 menu end
569 """ % {'grml_flavour': grml_flavour } )
570
571
572 def install_grub(device):
573     """Install grub on specified device.
574
575     @mntpoint: mountpoint of device where grub should install its files to
576     @device: partition where grub should be installed to"""
577
578     if options.dryrun:
579         logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device)
580     else:
581         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
582         register_tmpfile(device_mountpoint)
583         try:
584             try:
585                 mount(device, device_mountpoint, "")
586
587                 # If using --grub-mbr then make sure we install grub in MBR instead of PBR
588                 # Thanks to grub2. NOT.
589                 if options.grubmbr:
590                     logging.debug("Using option --grub-mbr ...")
591                     if device[-1:].isdigit():
592                         grub_device = re.match(r'(.*?)\d*$', device).group(1)
593                     else:
594                         grub_device = device
595                 else:
596                     grub_device = device
597
598                 logging.info("Installing grub as bootloader")
599                 logging.debug("grub-install --recheck --no-floppy --root-directory=%s %s",
600                     device_mountpoint, grub_device)
601                 proc = subprocess.Popen(["grub-install", "--recheck", "--no-floppy",
602                     "--root-directory=%s" % device_mountpoint, grub_device], stdout=file(os.devnull, "r+"))
603                 proc.wait()
604                 if proc.returncode != 0:
605                     # raise Exception("error executing grub-install")
606                     logging.critical("Fatal: error executing grub-install (please check the grml2usb FAQ or drop the --grub option)")
607                     logging.critical("Note:  if using grub2 consider using the --grub-mbr option because grub2's PBR feature is broken.")
608                     cleanup()
609                     sys.exit(1)
610             except CriticalException, error:
611                 logging.critical("Fatal: %s", error)
612                 cleanup()
613                 sys.exit(1)
614
615         finally:
616             unmount(device_mountpoint, "")
617             os.rmdir(device_mountpoint)
618             unregister_tmpfile(device_mountpoint)
619
620
621 def install_syslinux(device):
622     """Install syslinux on specified device.
623
624     @device: partition where syslinux should be installed to"""
625
626     if options.dryrun:
627         logging.info("Would install syslinux as bootloader on %s", device)
628         return 0
629
630     # syslinux -d boot/isolinux /dev/sdb1
631     logging.info("Installing syslinux as bootloader")
632     logging.debug("syslinux -d boot/syslinux %s", device)
633     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
634     proc.wait()
635     if proc.returncode != 0:
636         raise CriticalException("Error executing syslinux (either try --fat16 or use grub?)")
637
638
639 def install_bootloader(device):
640     """Install bootloader on specified device.
641
642     @device: partition where bootloader should be installed to"""
643
644     # by default we use grub, so install syslinux only on request
645     if options.syslinux:
646         logging.info("Note: the --syslinux option is deprecated as syslinux is grml2usb's default. Continuing anyway.")
647
648     if options.grub:
649         if not which("grub-install"):
650             logging.critical("Fatal: grub-install not available (please install the grub package or use the --syslinux option)")
651             cleanup()
652             sys.exit(1)
653         else:
654             try:
655                 install_grub(device)
656             except CriticalException, error:
657                 logging.critical("Fatal: %s", error)
658                 cleanup()
659                 sys.exit(1)
660     else:
661         try:
662             install_syslinux(device)
663         except CriticalException, error:
664             logging.critical("Fatal: %s", error)
665             cleanup()
666             sys.exit(1)
667
668
669 def execute_lilo(lilo, device):
670     """execute lilo for activating the partitions in the MBR
671
672     @lilo: path of lilo executable
673     @device: device where lilo should be executed on"""
674
675     # to support -A for extended partitions:
676     logging.info("Activating partitions in MBR via lilo")
677     logging.debug("%s -S /dev/null -M %s ext", lilo, device)
678     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
679     proc.wait()
680     if proc.returncode != 0:
681         raise Exception("error executing lilo")
682
683     # activate partition:
684     logging.debug("%s -S /dev/null -A %s 1", lilo, device)
685     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
686     proc.wait()
687     if proc.returncode != 0:
688         raise Exception("error executing lilo")
689
690
691 def install_syslinux_mbr(device):
692     """install syslinux's master boot record (MBR) on the specified device
693
694     @device: device where MBR of syslinux should be installed to"""
695
696     # make sure we have syslinux available
697     if not which("syslinux") and not options.copyonly:
698         raise Exception("syslinux not available (either install it or consider using the --grub option)")
699
700     # lilo's mbr is broken, use the one from syslinux instead:
701     if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
702         raise Exception("/usr/lib/syslinux/mbr.bin can not be read")
703
704     logging.info("Installing syslinux MBR")
705     logging.debug("cat /usr/lib/syslinux/mbr.bin > %s", device)
706     try:
707         # TODO -> use Popen instead?
708         retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
709         if retcode < 0:
710             logging.critical("Error copying MBR to device (%s)", retcode)
711     except OSError, error:
712         logging.critical("Execution failed: %s", error)
713
714
715 def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
716     """install 'mbr' master boot record (MBR) on a device
717
718     Retrieve the partition table from "device", install an MBR from the
719     "mbrtemplate" file, set the "partition" (0..3) active, and install the
720     result back to "device".
721
722     @mbrtemplate: default MBR file
723
724     @device: name of a file assumed to be a hard disc (or USB stick) image, or
725     something like "/dev/sdb"
726
727     @partition: must be a number between 0 and 3, inclusive
728
729     @mbrtemplate: must be a valid MBR file of at least 440 (or 439 if
730     ismirbsdmbr) bytes.
731
732     @ismirbsdmbr: if true then ignore the active flag, set the mirbsdmbr
733     specific flag to 0/1/2/3 and set the MBR's default value accordingly. If
734     false then leave the mirbsdmbr specific flag set to FFh, set all
735     active flags to 0 and set the active flag of the partition to 80h.  Note:
736     behaviour of mirbsdmbr: if flag = 0/1/2/3 then use it, otherwise search for
737     the active flag."""
738
739     logging.info("Installing default MBR")
740
741     if not os.path.isfile(mbrtemplate):
742         logging.critical("Error: %s can not be read.", mbrtemplate)
743         raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file?)")
744
745     if (partition < 0) or (partition > 3):
746         raise ValueError("partition must be between 0 and 3")
747
748     if ismirbsdmbr:
749         nmbrbytes = 439
750     else:
751         nmbrbytes = 440
752
753     tmpf = tempfile.NamedTemporaryFile()
754
755     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1", device, tmpf.name)
756     proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"], stderr=file(os.devnull, "r+"))
757     proc.wait()
758     if proc.returncode != 0:
759         raise Exception("error executing dd (first run)")
760
761     logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc", mbrtemplate,
762         tmpf.name, nmbrbytes)
763     proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes,
764         "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
765     proc.wait()
766     if proc.returncode != 0:
767         raise Exception("error executing dd (second run)")
768
769     mbrcode = tmpf.file.read(512)
770     if len(mbrcode) < 512:
771         raise EOFError("MBR size (%d) < 512" % len(mbrcode))
772
773     if ismirbsdmbr:
774         mbrcode = mbrcode[0:439] + chr(partition) + \
775           mbrcode[440:510] + "\x55\xAA"
776     else:
777         actives = ["\x00", "\x00", "\x00", "\x00"]
778         actives[partition] = "\x80"
779         mbrcode = mbrcode[0:446] + actives[0] + \
780           mbrcode[447:462] + actives[1] + \
781           mbrcode[463:478] + actives[2] + \
782           mbrcode[479:494] + actives[3] + \
783           mbrcode[495:510] + "\x55\xAA"
784
785     tmpf.file.seek(0)
786     tmpf.file.truncate()
787     tmpf.file.write(mbrcode)
788     tmpf.file.close()
789
790     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc", tmpf.name, device)
791     proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1",
792                             "conv=notrunc"], stderr=file(os.devnull, "r+"))
793     proc.wait()
794     if proc.returncode != 0:
795         raise Exception("error executing dd (third run)")
796     del tmpf
797
798
799 def handle_syslinux_mbr(device):
800     """Install syslinux master boot record on given device
801
802     @device: device where MBR should be installed to"""
803
804     if not is_writeable(device):
805         raise IOError("device not writeable for user")
806
807     # try to use system's lilo
808     if which("lilo"):
809         lilo = which("lilo")
810     else:
811         # otherwise fall back to our static version
812         from platform import architecture
813         if architecture()[0] == '64bit':
814             lilo = GRML2USB_BASE + '/lilo/lilo.static.amd64'
815         else:
816             lilo = GRML2USB_BASE + '/lilo/lilo.static.i386'
817     # finally prefer a specified lilo executable
818     if options.lilobin:
819         lilo = options.lilobin
820
821     if not is_exe(lilo):
822         raise Exception("lilo executable can not be execute")
823
824     if options.dryrun:
825         logging.info("Would install MBR running lilo and using syslinux.")
826         return 0
827
828     execute_lilo(lilo, device)
829     install_syslinux_mbr(device)
830
831
832 def is_writeable(device):
833     """Check if the device is writeable for the current user
834
835     @device: partition where bootloader should be installed to"""
836
837     if not device:
838         return False
839         #raise Exception("no device for checking write permissions")
840
841     if not os.path.exists(device):
842         return False
843
844     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
845
846
847 def mount(source, target, mount_options):
848     """Mount specified source on given target
849
850     @source: name of device/ISO that should be mounted
851     @target: directory where the ISO should be mounted to
852     @options: mount specific options"""
853
854     # note: options.dryrun does not work here, as we have to
855     # locate files and identify the grml flavour
856
857     for x in file('/proc/mounts').readlines():
858         if x.startswith(source):
859             raise CriticalException("Error executing mount: %s already mounted - please unmount before invoking grml2usb" % source)
860
861     if os.path.isdir(source):
862         logging.debug("Source %s is not a device, therefore not mounting.", source)
863         return 0
864
865     logging.debug("mount %s %s %s", mount_options, source, target)
866     proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target])
867     proc.wait()
868     if proc.returncode != 0:
869         raise CriticalException("Error executing mount (no filesystem on the partition?)")
870     else:
871         logging.debug("register_mountpoint(%s)", target)
872         register_mountpoint(target)
873
874
875 def unmount(target, unmount_options):
876     """Unmount specified target
877
878     @target: target where something is mounted on and which should be unmounted
879     @options: options for umount command"""
880
881     # make sure we unmount only already mounted targets
882     target_unmount = False
883     mounts = open('/proc/mounts').readlines()
884     mountstring = re.compile(".*%s.*" % re.escape(os.path.realpath(target)))
885     for line in mounts:
886         if re.match(mountstring, line):
887             target_unmount = True
888
889     if not target_unmount:
890         logging.debug("%s not mounted anymore", target)
891     else:
892         logging.debug("umount %s %s", list(unmount_options), target)
893         proc = subprocess.Popen(["umount"] + list(unmount_options) + [target])
894         proc.wait()
895         if proc.returncode != 0:
896             raise Exception("Error executing umount")
897         else:
898             logging.debug("unregister_mountpoint(%s)", target)
899             unregister_mountpoint(target)
900
901
902 def check_for_usbdevice(device):
903     """Check whether the specified device is a removable USB device
904
905     @device: device name, like /dev/sda1 or /dev/sda
906     """
907
908     usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
909     # newer systems:
910     usbdev = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
911     if not os.path.isfile(usbdev):
912         # Ubuntu with kernel 2.6.24 for example:
913         usbdev = os.path.realpath('/sys/block/' + usbdevice + '/removable')
914
915     if os.path.isfile(usbdev):
916         is_usb = open(usbdev).readline()
917         if is_usb.find("1"):
918             return 0
919
920     return 1
921
922
923 def check_for_fat(partition):
924     """Check whether specified partition is a valid VFAT/FAT16 filesystem
925
926     @partition: device name of partition"""
927
928     try:
929         udev_info = subprocess.Popen(["/sbin/blkid", "-s", "TYPE", "-o", "value", partition],
930                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
931         filesystem = udev_info.communicate()[0].rstrip()
932
933         if udev_info.returncode == 2:
934             raise CriticalException("Failed to read device %s"
935                                     " (wrong UID/permissions or device/directory not present?)" % partition)
936
937         if options.syslinux and filesystem != "vfat":
938             raise CriticalException("Partition %s does not contain a FAT16 filesystem. (Use --fat16 or run mkfs.vfat %s)" % (partition, partition))
939
940     except OSError:
941         raise CriticalException("Sorry, /sbin/blkid not available (install e2fsprogs?)")
942
943
944 def mkdir(directory):
945     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
946
947     # just silently pass as it's just fine it the directory exists
948     if not os.path.isdir(directory):
949         try:
950             os.makedirs(directory)
951         # pylint: disable-msg=W0704
952         except OSError:
953             pass
954
955
956 def exec_rsync(source, target):
957     """Simple wrapper around rsync to install files
958
959     @source: source file/directory
960     @target: target file/directory"""
961     logging.debug("Source: %s / Target: %s", source, target)
962     proc = subprocess.Popen(["rsync", "-rlptDH", "--inplace", source, target])
963     proc.wait()
964     if proc.returncode == 12:
965         logging.critical("Fatal: No space left on device")
966         cleanup()
967         sys.exit(1)
968
969     if proc.returncode != 0:
970         logging.critical("Fatal: could not install %s", source)
971         cleanup()
972         sys.exit(1)
973
974
975 def copy_system_files(grml_flavour, iso_mount, target):
976     """copy grml's main files (like squashfs, kernel and initrd) to a given target
977
978     @grml_flavour: name of grml flavour the configuration should be generated for
979     @iso_mount: path where a grml ISO is mounted on
980     @target: path where grml's main files should be copied to"""
981
982     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
983     if squashfs is None:
984         logging.critical("Fatal: squashfs file not found")
985         raise CriticalException("error locating squashfs file")
986     else:
987         squashfs_target = target + '/live/' + grml_flavour + '/'
988         execute(mkdir, squashfs_target)
989     exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs')
990
991     filesystem_module = search_file('filesystem.module', iso_mount)
992     if filesystem_module is None:
993         logging.critical("Fatal: filesystem.module not found")
994         raise CriticalException("error locating filesystem.module file")
995     else:
996         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
997
998     release_target = target + '/boot/release/' + grml_flavour
999     execute(mkdir, release_target)
1000
1001     kernel = search_file('linux26', iso_mount)
1002     if kernel is None:
1003         logging.critical("Fatal kernel not found")
1004         raise CriticalException("error locating kernel file")
1005     else:
1006         exec_rsync(kernel, release_target + '/linux26')
1007
1008     initrd = search_file('initrd.gz', iso_mount)
1009     if initrd is None:
1010         logging.critical("Fatal: initrd not found")
1011         raise CriticalException("error locating initrd file")
1012     else:
1013         exec_rsync(initrd, release_target + '/initrd.gz')
1014
1015
1016 def copy_grml_files(iso_mount, target):
1017     """copy some minor grml files to a given target
1018
1019     @iso_mount: path where a grml ISO is mounted on
1020     @target: path where grml's main files should be copied to"""
1021
1022     grml_target = target + '/grml/'
1023     execute(mkdir, grml_target)
1024
1025     for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
1026         grml_file = search_file(myfile, iso_mount)
1027         if grml_file is None:
1028             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
1029         else:
1030             exec_rsync(grml_file, grml_target + myfile)
1031
1032     grml_web_target = grml_target + '/web/'
1033     execute(mkdir, grml_web_target)
1034
1035     for myfile in 'index.html', 'style.css':
1036         grml_file = search_file(myfile, iso_mount)
1037         if grml_file is None:
1038             logging.warn("Warning: myfile %s could not be found - can not install it")
1039         else:
1040             exec_rsync(grml_file, grml_web_target + myfile)
1041
1042     grml_webimg_target = grml_web_target + '/images/'
1043     execute(mkdir, grml_webimg_target)
1044
1045     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1046         grml_file = search_file(myfile, iso_mount)
1047         if grml_file is None:
1048             logging.warn("Warning: myfile %s could not be found - can not install it")
1049         else:
1050             exec_rsync(grml_file, grml_webimg_target + myfile)
1051
1052
1053 def copy_addons(iso_mount, target):
1054     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1055
1056     @iso_mount: path where a grml ISO is mounted on
1057     @target: path where grml's main files should be copied to"""
1058
1059     addons = target + '/boot/addons/'
1060     execute(mkdir, addons)
1061
1062     # grub all-in-one image
1063     allinoneimg = search_file('allinone.img', iso_mount)
1064     if allinoneimg is None:
1065         logging.warn("Warning: allinone.img not found (that's fine if you don't need it)")
1066     else:
1067         exec_rsync(allinoneimg, addons + 'allinone.img')
1068
1069     # bsd imag
1070     bsdimg = search_file('bsd4grml', iso_mount)
1071     if bsdimg is None:
1072         logging.warn("Warning: bsd4grml not found (that's fine if you don't need it)")
1073     else:
1074         exec_rsync(bsdimg, addons + '/')
1075
1076     # freedos image
1077     balderimg = search_file('balder10.imz', iso_mount)
1078     if balderimg is None:
1079         logging.warn("Warning: balder10.imz not found (that's fine if you don't need it)")
1080     else:
1081         exec_rsync(balderimg, addons + 'balder10.imz')
1082
1083     # install hdt and pci.ids only when using syslinux (grub doesn't support it)
1084     if options.syslinux:
1085         # hdt (hardware detection tool) image
1086         hdtimg = search_file('hdt.c32', iso_mount)
1087         if hdtimg:
1088             exec_rsync(hdtimg, addons + '/hdt.c32')
1089
1090         # pci.ids file
1091         picids = search_file('pci.ids', iso_mount)
1092         if picids:
1093             exec_rsync(picids, addons + '/pci.ids')
1094
1095     # memdisk image
1096     memdiskimg = search_file('memdisk', iso_mount)
1097     if memdiskimg is None:
1098         logging.warn("Warning: memdisk not found (that's fine if you don't need it)")
1099     else:
1100         exec_rsync(memdiskimg, addons + 'memdisk')
1101
1102     # memtest86+ image
1103     memtestimg = search_file('memtest', iso_mount)
1104     if memtestimg is None:
1105         logging.warn("Warning: memtest not found (that's fine if you don't need it)")
1106     else:
1107         exec_rsync(memtestimg, addons + 'memtest')
1108
1109
1110 def copy_bootloader_files(iso_mount, target):
1111     """copy grml's bootloader files to a given target
1112
1113     @iso_mount: path where a grml ISO is mounted on
1114     @target: path where grml's main files should be copied to"""
1115
1116     syslinux_target = target + '/boot/syslinux/'
1117     execute(mkdir, syslinux_target)
1118
1119     logo = search_file('logo.16', iso_mount)
1120     exec_rsync(logo, syslinux_target + 'logo.16')
1121
1122     for ffile in ['f%d' % number for number in range(1,11) ]:
1123         bootsplash = search_file(ffile, iso_mount)
1124         exec_rsync(bootsplash, syslinux_target + ffile)
1125
1126     # avoid the "file is read only, overwrite anyway (y/n) ?" question
1127     # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file")
1128     if os.path.isfile(syslinux_target + 'ldlinux.sys'):
1129         os.unlink(syslinux_target + 'ldlinux.sys')
1130
1131     if not search_file('default.cfg', iso_mount + '/boot/isolinux/'):
1132         logging.critical("Fatal: file default.cfg could not be found.")
1133         logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
1134         logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
1135         raise
1136
1137     for filename in 'addons.cfg', 'default.cfg', 'distri.cfg', \
1138                     'grml.cfg', 'grml.png', 'hd.cfg', 'isoprompt.cfg', 'options.cfg', \
1139                     'prompt.cfg', 'vesamenu.c32', 'vesamenu.cfg', 'grml.png':
1140         path = search_file(filename, iso_mount + '/boot/isolinux/')
1141         exec_rsync(path, syslinux_target + filename)
1142
1143     path = search_file('hidden.cfg', iso_mount + '/boot/isolinux/')
1144     exec_rsync(path, syslinux_target + "new-" + 'hidden.cfg')
1145
1146
1147     grub_target = target + '/boot/grub/'
1148     execute(mkdir, grub_target)
1149
1150     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1151         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1152         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1153         raise
1154     else:
1155         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1156
1157     # grml splash in grub
1158     if os.path.isfile(GRML2USB_BASE + "/grub/grml.png"):
1159         exec_rsync(GRML2USB_BASE + '/grub/grml.png', grub_target + 'grml.png')
1160
1161     # font file for graphical bootsplash in grub
1162     if os.path.isfile("/usr/share/grub/ascii.pf2"):
1163         exec_rsync('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1164
1165
1166 def install_iso_files(grml_flavour, iso_mount, device, target):
1167     """Copy files from ISO to given target
1168
1169     @grml_flavour: name of grml flavour the configuration should be generated for
1170     @iso_mount: path where a grml ISO is mounted on
1171     @device: device/partition where bootloader should be installed to
1172     @target: path where grml's main files should be copied to"""
1173
1174     # TODO => several improvements:
1175     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1176     # * provide alternative search_file() if file information is stored in a config.ini file?
1177     # * catch "install: .. No space left on device" & CO
1178
1179     if options.dryrun:
1180         return 0
1181     elif not options.bootloaderonly:
1182         logging.info("Copying files. This might take a while....")
1183         try:
1184             copy_system_files(grml_flavour, iso_mount, target)
1185             copy_grml_files(iso_mount, target)
1186         except CriticalException, error:
1187             logging.critical("Execution failed: %s", error)
1188             sys.exit(1)
1189
1190     if not options.skipaddons:
1191         if grml_flavour.endswith('-small'):
1192             logging.info("Note: grml-small doesn't provide any addons, not installing them therefore.")
1193         else:
1194             copy_addons(iso_mount, target)
1195
1196     if not options.copyonly:
1197         copy_bootloader_files(iso_mount, target)
1198
1199         if not options.dryrun:
1200             handle_bootloader_config(grml_flavour, device, target)
1201
1202     # make sure we sync filesystems before returning
1203     proc = subprocess.Popen(["sync"])
1204     proc.wait()
1205
1206
1207 def uninstall_files(device):
1208     """Get rid of all grml files on specified device
1209
1210     @device: partition where grml2usb files should be removed from"""
1211
1212     # TODO - not implemented yet
1213     logging.critical("TODO: uninstalling files from %s not yet implement, sorry.", device)
1214
1215
1216 def identify_grml_flavour(mountpath):
1217     """Get name of grml flavour
1218
1219     @mountpath: path where the grml ISO is mounted to
1220     @return: name of grml-flavour"""
1221
1222     version_file = search_file('grml-version', mountpath)
1223
1224     if version_file == "":
1225         logging.critical("Error: could not find grml-version file.")
1226         raise
1227
1228     try:
1229         tmpfile = open(version_file, 'r')
1230         grml_info = tmpfile.readline()
1231         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1232     except TypeError:
1233         raise
1234     except Exception, e:
1235         logging.critical("Unexpected error: %s", e)
1236         raise
1237
1238     return grml_flavour
1239
1240
1241 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1242     """Main handler for generating grub1 configuration
1243
1244     @grml_flavour: name of grml flavour the configuration should be generated for
1245     @install_partition: partition number for use in (hd0,X)
1246     @grub_target: path of grub's configuration files
1247     @bootoptions: additional bootoptions that should be used by default"""
1248
1249     # grub1 config
1250     grub1_cfg = grub_target + 'menu.lst'
1251     logging.debug("Creating grub1 configuration file (menu.lst)")
1252
1253     # install main configuration only *once*, no matter how many ISOs we have:
1254     if os.path.isfile(grub1_cfg):
1255         string = open(grub1_cfg).readline()
1256         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1257         if not re.match(main_identifier, string):
1258             grub1_config_file = open(grub1_cfg, 'w')
1259             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1260             grub1_config_file.close()
1261     else:
1262         grub1_config_file = open(grub1_cfg, 'w')
1263         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1264         grub1_config_file.close()
1265
1266     grub_flavour_config = True
1267     if os.path.isfile(grub1_cfg):
1268         string = open(grub1_cfg).readlines()
1269         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1270         for line in string:
1271             if flavour.match(line):
1272                 grub_flavour_config = False
1273
1274     if grub_flavour_config:
1275         grub1_config_file = open(grub1_cfg, 'a')
1276         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1277         grub1_config_file.close()
1278
1279     # make sure grub.conf isn't a symlink but a plain file instead,
1280     # otherwise it will break on FAT16 filesystems
1281     # this works around grub-install of (at least) Fedora 10
1282     if os.path.isfile(grub1_cfg):
1283         grubconf = grub_target + 'grub.conf'
1284         if not os.path.islink(grubconf):
1285             import shutil
1286             shutil.copyfile(grub1_cfg, grub_target + 'grub.conf')
1287
1288 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1289     """Main handler for generating grub2 configuration
1290
1291     @grml_flavour: name of grml flavour the configuration should be generated for
1292     @grub_target: path of grub's configuration files
1293     @bootoptions: additional bootoptions that should be used by default"""
1294
1295     # grub2 config
1296     grub2_cfg = grub_target + 'grub.cfg'
1297     logging.debug("Creating grub2 configuration file (grub.lst)")
1298
1299     global GRML_DEFAULT
1300
1301     # install main configuration only *once*, no matter how many ISOs we have:
1302     grub_flavour_is_default = False
1303     if os.path.isfile(grub2_cfg):
1304         string = open(grub2_cfg).readline()
1305         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1306         if not re.match(main_identifier, string):
1307             grub2_config_file = open(grub2_cfg, 'w')
1308             GRML_DEFAULT = grml_flavour
1309             grub_flavour_is_default = True
1310             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1311             grub2_config_file.close()
1312     else:
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
1319     # install flavour specific configuration only *once* as well
1320     grub_flavour_config = True
1321     if os.path.isfile(grub2_cfg):
1322         string = open(grub2_cfg).readlines()
1323         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1324         for line in string:
1325             if flavour.match(line):
1326                 grub_flavour_config = False
1327
1328     if grub_flavour_config:
1329         grub2_config_file = open(grub2_cfg, 'a')
1330         # display only if the grml flavour isn't the default
1331         if not grub_flavour_is_default:
1332             GRML_FLAVOURS.add(grml_flavour)
1333         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1334         grub2_config_file.close()
1335
1336
1337 def handle_grub_config(grml_flavour, device, target):
1338     """Main handler for generating grub (v1 and v2) configuration
1339
1340     @grml_flavour: name of grml flavour the configuration should be generated for
1341     @device: device/partition where grub should be installed to
1342     @target: path of grub's configuration files"""
1343
1344     logging.debug("Generating grub configuration")
1345
1346     grub_target = target + '/boot/grub/'
1347     execute(mkdir, grub_target)
1348
1349     if os.path.isdir(device):
1350         install_grub1_partition = None
1351     else:
1352         if device[-1:].isdigit():
1353             install_grub1_partition = int(device[-1:]) - 1
1354         else:
1355             raise CriticalException("error validating partition schema (raw device?)")
1356
1357     # do NOT write "None" in kernel cmdline
1358     if options.bootoptions is None:
1359         bootopt = ""
1360     else:
1361         bootopt = options.bootoptions
1362
1363     # write menu.lst
1364     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1365     # write grub.cfg
1366     handle_grub2_config(grml_flavour, grub_target, bootopt)
1367
1368
1369 def initial_syslinux_config(target):
1370     """Generates intial syslinux configuration
1371
1372     @target path of syslinux's configuration files"""
1373
1374     target = target + "/"
1375     filename = target + "grmlmain.cfg"
1376     if os.path.isfile(target + "grmlmain.cfg"):
1377         return
1378     data = open(filename, "w")
1379     data.write(generate_main_syslinux_config())
1380     data.close
1381
1382     filename = target + "hiddens.cfg"
1383     data = open(filename, "w")
1384     data.write("include hidden.cfg\n")
1385     data.close()
1386
1387 def add_entry_if_not_present(filename, entry):
1388     data = open(filename, "a+")
1389     for line in data:
1390         if line == entry:
1391             break
1392     else:
1393         data.write(entry)
1394
1395     data.close()
1396
1397
1398
1399 def adjust_syslinux_bootoptions(src, flavour):
1400     append_re = re.compile("^(\s*append.*)$", re.I)
1401     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1402     # flavour_re = re.compile("(label.*)(grml\w+)")
1403     default_re = re.compile("(default.cfg)")
1404
1405     # do NOT write "None" in kernel cmdline
1406     if options.bootoptions is None:
1407         bootopt = ""
1408     else:
1409         bootopt = options.bootoptions
1410
1411     for line in fileinput.input(src, inplace=1):
1412         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour, line)
1413         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1414         line = default_re.sub(r'%s-\1' % flavour, line)
1415         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1416         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1417         sys.stdout.write(line)
1418     fileinput.close()
1419
1420 def adjust_labels(src, flavour):
1421     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1422     for line in fileinput.input(src, inplace=1):
1423         line = label_re.sub(r'\1 %s-\2' % flavour, line)
1424         sys.stdout.write(line)
1425     fileinput.close()
1426
1427
1428 def add_syslinux_entry(filename, grml_flavour):
1429     entry_filename = "option-%s.cfg" % grml_flavour
1430     entry = "include %s\n" % entry_filename
1431
1432     add_entry_if_not_present(filename, entry)
1433     path = os.path.dirname(filename)
1434
1435     data = open(path + "/" + entry_filename, "w")
1436     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1437     data.close()
1438
1439 def modify_filenames(grml_flavour, target, filenames):
1440     for filename in filenames:
1441         old_filename = "%s/%s" % (target, filename)
1442         new_filename = "%s/%s-%s" % (target, grml_flavour, filename)
1443         os.rename(old_filename, new_filename)
1444         adjust_syslinux_bootoptions(new_filename, grml_flavour)
1445
1446
1447 def remove_default_entry(filename):
1448     default_re = re.compile("^(\s*menu\s*default\s*)$", re.I)
1449     for line in fileinput.input(filename, inplace=1):
1450         if default_re.match(line): continue
1451         sys.stdout.write(line)
1452     fileinput.close()
1453
1454
1455 def handle_syslinux_config(grml_flavour, target):
1456     """Main handler for generating syslinux configuration
1457
1458     @grml_flavour: name of grml flavour the configuration should be generated for
1459     @target: path of syslinux's configuration files"""
1460
1461     # do NOT write "None" in kernel cmdline
1462     if options.bootoptions is None:
1463         bootopt = ""
1464     else:
1465         bootopt = options.bootoptions
1466
1467     logging.debug("Generating syslinux configuration")
1468     syslinux_target = target + '/boot/syslinux/'
1469     # should be present via  copy_bootloader_files(), but make sure it exits:
1470     execute(mkdir, syslinux_target)
1471     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1472
1473     global GRML_DEFAULT
1474
1475     # install main configuration only *once*, no matter how many ISOs we have:
1476     syslinux_flavour_is_default = False
1477     syslinux_config_file = open(syslinux_cfg, 'w')
1478     syslinux_config_file.write("TIMEOUT 300\n")
1479     syslinux_config_file.write("include vesamenu.cfg\n")
1480     syslinux_config_file.close()
1481
1482     prompt_name = open(syslinux_target + 'promptname.cfg', 'w')
1483     prompt_name.write('menu label S^yslinux prompt\n')
1484     prompt_name.close()
1485
1486     initial_syslinux_config(syslinux_target)
1487     modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1488
1489     filename = search_file("new-hidden.cfg", syslinux_target)
1490     # process hidden file
1491     if not search_file("hidden.cfg", syslinux_target):
1492         new_hidden = syslinux_target + "hidden.cfg"
1493         os.rename(filename, new_hidden)
1494         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1495     else:
1496         new_hidden =  "%s-hidden.cfg" % (grml_flavour)
1497         new_hidden_file =  "%s/%s" % (syslinux_target, new_hidden)
1498         os.rename(filename, new_hidden_file)
1499         adjust_labels(new_hidden_file, grml_flavour)
1500         adjust_syslinux_bootoptions(new_hidden_file, grml_flavour)
1501         entry = 'include %s\n' % new_hidden
1502         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1503
1504
1505
1506     new_default = "%s-default.cfg" % (grml_flavour)
1507     entry = 'include %s\n' % new_default
1508     defaults_file = '%s/defaults.cfg' % syslinux_target
1509
1510     if os.path.isfile(defaults_file):
1511         remove_default_entry('%s/%s-default.cfg' % (syslinux_target, grml_flavour))
1512
1513     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1514
1515     add_syslinux_entry("%s/additional.cfg" % syslinux_target, grml_flavour)
1516
1517
1518 def handle_bootloader_config(grml_flavour, device, target):
1519     """Main handler for generating bootloader's configuration
1520
1521     @grml_flavour: name of grml flavour the configuration should be generated for
1522     @device: device/partition where bootloader should be installed to
1523     @target: path of bootloader's configuration files"""
1524
1525     if options.skipsyslinuxconfig:
1526         logging.info("Skipping generation of syslinux configuration as requested.")
1527     else:
1528         try:
1529             handle_syslinux_config(grml_flavour, target)
1530         except CriticalException, error:
1531             logging.critical("Fatal: %s", error)
1532             sys.exit(1)
1533
1534     if options.skipgrubconfig:
1535         logging.info("Skipping generation of grub configuration as requested.")
1536     else:
1537         try:
1538             handle_grub_config(grml_flavour, device, target)
1539         except CriticalException, error:
1540             logging.critical("Fatal: %s", error)
1541             sys.exit(1)
1542
1543
1544 def handle_dir(live_image, device):
1545     """Main logic for copying files of the currently running grml system.
1546
1547     @live_image: directory where currently running live system resides (usually /live/image)
1548     @device: partition where the specified ISO should be installed to"""
1549
1550     logging.info("Using %s as install base", live_image)
1551
1552     if os.path.isdir(device):
1553         logging.info("Specified target is a directory, therefore not mounting.")
1554         device_mountpoint = device
1555         remove_device_mountpoint = False
1556     else:
1557         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1558         register_tmpfile(device_mountpoint)
1559         remove_device_mountpoint = True
1560         try:
1561             mount(device, device_mountpoint, "")
1562         except CriticalException, error:
1563             logging.critical("Fatal: %s", error)
1564             cleanup()
1565             sys.exit(1)
1566
1567     try:
1568         try:
1569             grml_flavour = identify_grml_flavour(live_image)
1570             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1571             install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1572         except TypeError:
1573             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1574             sys.exit(1)
1575     finally:
1576         if remove_device_mountpoint:
1577             try:
1578                 unmount(device_mountpoint, "")
1579                 if os.path.isdir(device_mountpoint):
1580                     os.rmdir(device_mountpoint)
1581                     unregister_tmpfile(device_mountpoint)
1582             except CriticalException, error:
1583                 logging.critical("Fatal: %s", error)
1584                 cleanup()
1585
1586
1587 def handle_iso(iso, device):
1588     """Main logic for mounting ISOs and copying files.
1589
1590     @iso: full path to the ISO that should be installed to the specified device
1591     @device: partition where the specified ISO should be installed to"""
1592
1593     logging.info("Using ISO %s", iso)
1594
1595     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1596     register_tmpfile(iso_mountpoint)
1597     remove_iso_mountpoint = True
1598
1599     if not os.path.isfile(iso):
1600         logging.critical("Fatal: specified ISO %s could not be read", iso)
1601         cleanup()
1602         sys.exit(1)
1603
1604     try:
1605         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1606     except CriticalException, error:
1607         logging.critical("Fatal: %s", error)
1608         sys.exit(1)
1609
1610     if os.path.isdir(device):
1611         logging.info("Specified target is a directory, therefore not mounting.")
1612         device_mountpoint = device
1613         remove_device_mountpoint = False
1614         # skip_mbr = True
1615     else:
1616         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1617         register_tmpfile(device_mountpoint)
1618         remove_device_mountpoint = True
1619         try:
1620             mount(device, device_mountpoint, "")
1621         except CriticalException, error:
1622             logging.critical("Fatal: %s", error)
1623             cleanup()
1624             sys.exit(1)
1625
1626     try:
1627         try:
1628             grml_flavour = identify_grml_flavour(iso_mountpoint)
1629             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1630             install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1631         except TypeError:
1632             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1633             sys.exit(1)
1634     finally:
1635         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1636             unmount(iso_mountpoint, "")
1637             os.rmdir(iso_mountpoint)
1638             unregister_tmpfile(iso_mountpoint)
1639         if remove_device_mountpoint:
1640             try:
1641                 unmount(device_mountpoint, "")
1642                 if os.path.isdir(device_mountpoint):
1643                     os.rmdir(device_mountpoint)
1644                     unregister_tmpfile(device_mountpoint)
1645             except CriticalException, error:
1646                 logging.critical("Fatal: %s", error)
1647                 cleanup()
1648
1649
1650 def handle_mbr(device):
1651     """Main handler for installing master boot record (MBR)
1652
1653     @device: device where the MBR should be installed to"""
1654
1655     if options.dryrun:
1656         logging.info("Would install MBR")
1657         return 0
1658
1659     if device[-1:].isdigit():
1660         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1661         partition_number = int(device[-1:]) - 1
1662         skip_install_mir_mbr = False
1663
1664     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1665     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1666     if mbr_device == "/dev/loop":
1667         mbr_device = device
1668         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1669         skip_install_mir_mbr = True
1670
1671     try:
1672         if options.syslinuxmbr:
1673             handle_syslinux_mbr(mbr_device)
1674         elif not skip_install_mir_mbr:
1675             if options.mbrmenu:
1676                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True)
1677             else:
1678                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False)
1679     except IOError, error:
1680         logging.critical("Execution failed: %s", error)
1681         sys.exit(1)
1682     except Exception, error:
1683         logging.critical("Execution failed: %s", error)
1684         sys.exit(1)
1685
1686
1687 def handle_vfat(device):
1688     """Check for FAT specific settings and options
1689
1690     @device: device that should checked / formated"""
1691
1692     # make sure we have mkfs.vfat available
1693     if options.fat16:
1694         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1695             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1696             logging.critical('Please make sure to install dosfstools.')
1697             sys.exit(1)
1698
1699         exec_mkfs = False
1700         if options.force:
1701             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1702             exec_mkfs = True
1703         else:
1704             # make sure the user is aware of what he is doing
1705             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1706             if f == "y" or f == "Y":
1707                 logging.info("Note: you can skip this question using the option --force")
1708                 exec_mkfs = True
1709
1710         if exec_mkfs:
1711             try:
1712                 mkfs_fat16(device)
1713             except CriticalException, error:
1714                 logging.critical("Execution failed: %s", error)
1715                 sys.exit(1)
1716         else:
1717             sys.exit(1)
1718
1719     # check for vfat filesystem
1720     if device is not None and not os.path.isdir(device):
1721         try:
1722             check_for_fat(device)
1723         except CriticalException, error:
1724             logging.critical("Execution failed: %s", error)
1725             sys.exit(1)
1726
1727     if not os.path.isdir(device) and not check_for_usbdevice(device):
1728         print "Warning: the specified device %s does not look like a removable usb device." % device
1729         f = raw_input("Do you really want to continue? y/N ")
1730         if f == "y" or f == "Y":
1731             pass
1732         else:
1733             sys.exit(1)
1734
1735
1736 def handle_compat_warning(device):
1737     """Backwards compatible checks
1738
1739     @device: device that should be checked"""
1740
1741     # make sure we can replace old grml2usb script and warn user when using old way of life:
1742     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1743         print "Warning: the semantics of grml2usb has changed."
1744         print "Instead of using grml2usb /path/to/iso %s you might" % device
1745         print "want to use grml2usb /path/to/iso /dev/... instead."
1746         print "Please check out the grml2usb manpage for details."
1747         f = raw_input("Do you really want to continue? y/N ")
1748         if f == "y" or f == "Y":
1749             pass
1750         else:
1751             sys.exit(1)
1752
1753
1754 def handle_logging():
1755     """Log handling and configuration"""
1756
1757     if options.verbose and options.quiet:
1758         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1759
1760     if options.verbose:
1761         FORMAT = "Debug: %(asctime)-15s %(message)s"
1762         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1763     elif options.quiet:
1764         FORMAT = "Critical: %(message)s"
1765         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1766     else:
1767         FORMAT = "%(message)s"
1768         logging.basicConfig(level=logging.INFO, format=FORMAT)
1769
1770
1771 def handle_bootloader(device):
1772     """wrapper for installing bootloader
1773
1774     @device: device where bootloader should be installed to"""
1775
1776     # Install bootloader only if not using the --copy-only option
1777     if options.copyonly:
1778         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1779     elif os.path.isdir(device):
1780         logging.info("Not installing bootloader as %s is a directory.", device)
1781     else:
1782         install_bootloader(device)
1783
1784
1785 def main():
1786     """Main function [make pylint happy :)]"""
1787
1788     if options.version:
1789         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1790         sys.exit(0)
1791
1792     if len(args) < 2:
1793         parser.error("invalid usage")
1794
1795     # log handling
1796     handle_logging()
1797
1798     # make sure we have the appropriate permissions
1799     check_uid_root()
1800
1801     logging.info("Executing grml2usb version %s", PROG_VERSION)
1802
1803     if options.dryrun:
1804         logging.info("Running in simulation mode as requested via option dry-run.")
1805
1806     # specified arguments
1807     device = args[len(args) - 1]
1808     isos = args[0:len(args) - 1]
1809
1810     if not os.path.isdir(device):
1811         if device[-1:].isdigit():
1812             if int(device[-1:]) > 4 or device[-2:].isdigit():
1813                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1814                 sys.exit(1)
1815         else:
1816             if os.path.exists(device):
1817                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1818                 sys.exit(1)
1819
1820     if not which("rsync"):
1821         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1822         sys.exit(1)
1823
1824     # provide upgrade path
1825     handle_compat_warning(device)
1826
1827     # check for vfat partition
1828     handle_vfat(device)
1829
1830     # main operation (like installing files)
1831     for iso in isos:
1832         if os.path.isdir(iso):
1833             handle_dir(iso, device)
1834         else:
1835             handle_iso(iso, device)
1836
1837     # install mbr
1838     if not os.path.isdir(device):
1839         if not options.skipmbr:
1840             handle_mbr(device)
1841
1842     handle_bootloader(device)
1843
1844     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
1845
1846     for flavour in GRML_FLAVOURS:
1847         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
1848
1849     # finally be politely :)
1850     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
1851
1852 if __name__ == "__main__":
1853     try:
1854         main()
1855     except KeyboardInterrupt:
1856         logging.info("Received KeyboardInterrupt")
1857         cleanup()
1858
1859 ## END OF FILE #################################################################
1860 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8