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