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