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