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