fix bsd4grml boot (loopback and regular)
[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"
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         raise CriticalException("error locating squashfs file")
1107     else:
1108         squashfs_target = target + '/live/' + grml_flavour + '/'
1109         execute(mkdir, squashfs_target)
1110     exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs')
1111
1112     for prefix in grml_flavour + "/", "":
1113         filesystem_module = search_file(prefix + 'filesystem.module', iso_mount)
1114         if filesystem_module:
1115             break
1116     if filesystem_module is None:
1117         logging.critical("Fatal: filesystem.module not found")
1118         raise CriticalException("error locating filesystem.module file")
1119     else:
1120         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
1121
1122
1123     release_path = 'boot/release/' + grml_flavour.replace('-', '')
1124     release_target = target + "/" + release_path
1125     execute(mkdir, release_target)
1126
1127     prefix = ""
1128     if os.path.isdir(iso_mount + '/boot/release'):
1129         prefix = release_path + '/'
1130
1131     kernel = search_file(prefix + 'linux26', iso_mount)
1132     if kernel is None:
1133         logging.critical("Fatal kernel not found")
1134         raise CriticalException("error locating kernel file")
1135     else:
1136         exec_rsync(kernel, release_target + '/linux26')
1137
1138     initrd = search_file(prefix + 'initrd.gz', iso_mount)
1139     if initrd is None:
1140         logging.critical("Fatal: initrd not found")
1141         raise CriticalException("error locating initrd file")
1142     else:
1143         exec_rsync(initrd, release_target + '/initrd.gz')
1144
1145
1146 def update_grml_versions(iso_mount, target):
1147     """Update the grml version file on a cd
1148     Returns true if version was updated successfully,
1149     False if grml-version does not exist yet on the mountpoint
1150
1151     @iso_mount: string of the iso mount point
1152     @target: path of the target mount point
1153     """
1154     grml_target = target + '/grml/'
1155     target_grml_version_file = search_file('grml-version', grml_target)
1156     if target_grml_version_file:
1157         iso_grml_version_file = search_file('grml-version', iso_mount)
1158         if not iso_grml_version_file:
1159             logging.warn("Warning: %s could not be found - can not install it", iso_grml_version_file)
1160             return False
1161         try:
1162             iso_versions = {}
1163             iso_file = open(iso_grml_version_file, 'r')
1164             for line in iso_file:
1165                 iso_versions[get_flavour(line)] = line.strip()
1166
1167             for line in fileinput.input([target_grml_version_file], inplace=1):
1168                 flavour = get_flavour(line)
1169                 if flavour in iso_versions.keys():
1170                     print iso_versions[flavour]
1171                 else:
1172                     print line.strip()
1173         except IOError:
1174             logging.warn("Warning: Could not write file")
1175         finally:
1176             iso_file.close()
1177             fileinput.close()
1178         return True
1179     else:
1180         return False
1181
1182 def copy_grml_files(iso_mount, target):
1183     """copy some minor grml files to a given target
1184
1185     @iso_mount: path where a grml ISO is mounted on
1186     @target: path where grml's main files should be copied to"""
1187
1188     grml_target = target + '/grml/'
1189     execute(mkdir, grml_target)
1190
1191     copy_files = [ 'grml-cheatcodes.txt', 'LICENSE.txt', 'md5sums', 'README.txt' ]
1192     # handle grml-version
1193     if not update_grml_versions(iso_mount, target):
1194         copy_files.append('grml-version')
1195
1196     for myfile in copy_files:
1197         grml_file = search_file(myfile, iso_mount)
1198         if grml_file is None:
1199             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
1200         else:
1201             exec_rsync(grml_file, grml_target + myfile)
1202
1203     grml_web_target = grml_target + '/web/'
1204     execute(mkdir, grml_web_target)
1205
1206     for myfile in 'index.html', 'style.css':
1207         grml_file = search_file(myfile, iso_mount)
1208         if grml_file is None:
1209             logging.warn("Warning: myfile %s could not be found - can not install it")
1210         else:
1211             exec_rsync(grml_file, grml_web_target + myfile)
1212
1213     grml_webimg_target = grml_web_target + '/images/'
1214     execute(mkdir, grml_webimg_target)
1215
1216     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1217         grml_file = search_file(myfile, iso_mount)
1218         if grml_file is None:
1219             logging.warn("Warning: myfile %s could not be found - can not install it")
1220         else:
1221             exec_rsync(grml_file, grml_webimg_target + myfile)
1222
1223
1224 def handle_addon_copy(filename, dst, iso_mount):
1225     """handle copy of optional addons
1226
1227     @filename: filename of the addon
1228     @dst: destination directory
1229     @iso_mount: location of the iso mount
1230     """
1231     file_location = search_file(filename, iso_mount)
1232     if file_location is None:
1233         logging.warn("Warning: %s not found (that's fine if you don't need it)",  filename)
1234     else:
1235         exec_rsync(file_location, dst)
1236
1237
1238 def copy_addons(iso_mount, target):
1239     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1240
1241     @iso_mount: path where a grml ISO is mounted on
1242     @target: path where grml's main files should be copied to"""
1243
1244     addons = target + '/boot/addons/'
1245     execute(mkdir, addons)
1246
1247     # grub all-in-one image
1248     handle_addon_copy('allinone.img', addons, iso_mount)
1249
1250     # bsd imag
1251     handle_addon_copy('bsd4grml', addons, iso_mount)
1252
1253     handle_addon_copy('balder10.imz', addons, iso_mount)
1254
1255     # install hdt and pci.ids only when using syslinux (grub doesn't support it)
1256     if options.syslinux:
1257         # hdt (hardware detection tool) image
1258         hdtimg = search_file('hdt.c32', iso_mount)
1259         if hdtimg:
1260             exec_rsync(hdtimg, addons + '/hdt.c32')
1261
1262         # pci.ids file
1263         picids = search_file('pci.ids', iso_mount)
1264         if picids:
1265             exec_rsync(picids, addons + '/pci.ids')
1266
1267     # memdisk image
1268     handle_addon_copy('memdisk', addons, iso_mount)
1269
1270     # memtest86+ image
1271     handle_addon_copy('memtest', addons, iso_mount)
1272
1273     # gpxe.lkrn
1274     handle_addon_copy('gpxe.lkrn', addons, iso_mount)
1275
1276
1277 def glob_and_copy(filepattern, dst):
1278     """Glob on specified filepattern and copy the result to dst
1279
1280     @filepattern: globbing pattern
1281     @dst: target directory
1282     """
1283     for name in glob.glob(filepattern):
1284         copy_if_exist(name, dst)
1285
1286 def search_and_copy(filename, search_path, dst):
1287     """Search for the specified filename at searchpath and copy it to dst
1288
1289     @filename: filename to look for
1290     @search_path: base search file
1291     @dst: destionation to copy the file to
1292     """
1293     file_location = search_file(filename, search_path)
1294     copy_if_exist(file_location, dst)
1295
1296 def copy_if_exist(filename, dst):
1297     """Copy filename to dst if filename is set.
1298
1299     @filename: a filename
1300     @dst: dst file
1301     """
1302     if filename and (os.path.isfile(filename) or os.path.isdir(filename)):
1303         exec_rsync(filename, dst)
1304
1305 def copy_bootloader_files(iso_mount, target, grml_flavour):
1306     """Copy grml's bootloader files to a given target
1307
1308     @iso_mount: path where a grml ISO is mounted on
1309     @target: path where grml's main files should be copied to
1310     @grml_flavour: name of the current processed grml_flavour
1311     """
1312
1313     syslinux_target = target + '/boot/syslinux/'
1314     execute(mkdir, syslinux_target)
1315
1316     grub_target = target + '/boot/grub/'
1317     execute(mkdir, grub_target)
1318
1319
1320     logo = search_file('logo.16', iso_mount)
1321     exec_rsync(logo, syslinux_target + 'logo.16')
1322
1323
1324     for ffile in ['f%d' % number for number in range(1, 11) ]:
1325         search_and_copy(ffile, iso_mount, syslinux_target + ffile)
1326
1327     loopback_cfg = search_file("loopback.cfg", iso_mount)
1328     if loopback_cfg:
1329         directory = os.path.dirname(loopback_cfg)
1330         directory = directory.replace(iso_mount, "")
1331         mkdir(os.path.join(target, directory))
1332         exec_rsync(loopback_cfg, target + os.path.sep + directory)
1333
1334     # avoid the "file is read only, overwrite anyway (y/n) ?" question
1335     # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file")
1336     if os.path.isfile(syslinux_target + 'ldlinux.sys'):
1337         os.unlink(syslinux_target + 'ldlinux.sys')
1338
1339     (source_dir, name) = get_defaults_file(iso_mount, grml_flavour, "default.cfg")
1340     (source_dir, defaults_file) = get_defaults_file(iso_mount, grml_flavour, "grml.cfg")
1341
1342     if not source_dir:
1343         logging.critical("Fatal: file default.cfg could not be found.")
1344         logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
1345         logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
1346         logging.critical("       Please visit http://grml.org/grml2usb/#grml2usb-compat for further information.")
1347         raise
1348
1349     for expr in name, 'distri.cfg', \
1350         defaults_file, 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \
1351         'isoprompt.cfg', 'options.cfg', \
1352         'prompt.cfg', 'vesamenu.cfg', 'grml.png', '*.c32':
1353         glob_and_copy(iso_mount + source_dir + expr, syslinux_target)
1354
1355     for filename in glob.glob1(syslinux_target, "*.c32"):
1356         copy_if_exist(os.path.join(SYSLINUX_LIBS, filename), syslinux_target)
1357
1358
1359     # copy the addons_*.cfg file to the new syslinux directory
1360     glob_and_copy(iso_mount + source_dir + 'addon*.cfg', syslinux_target)
1361
1362     search_and_copy('hidden.cfg', iso_mount + source_dir, syslinux_target + "new_" + 'hidden.cfg')
1363
1364
1365     grub_target = target + '/boot/grub/'
1366     execute(mkdir, grub_target)
1367
1368     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1369         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1370         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1371         raise
1372     else:
1373         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1374
1375     # grml splash in grub
1376     copy_if_exist(GRML2USB_BASE + "/grub/grml.png", grub_target + 'grml.png')
1377
1378     # font file for graphical bootsplash in grub
1379     copy_if_exist('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1380
1381     # always copy grub content as it might be useful
1382
1383     glob_and_copy(iso_mount + '/boot/grub/*.mod', grub_target)
1384     glob_and_copy(iso_mount + '/boot/grub/*.img', grub_target)
1385     glob_and_copy(iso_mount + '/boot/grub/stage*', grub_target)
1386
1387 def install_iso_files(grml_flavour, iso_mount, device, target):
1388     """Copy files from ISO to given target
1389
1390     @grml_flavour: name of grml flavour the configuration should be generated for
1391     @iso_mount: path where a grml ISO is mounted on
1392     @device: device/partition where bootloader should be installed to
1393     @target: path where grml's main files should be copied to"""
1394
1395     global GRML_DEFAULT
1396     GRML_DEFAULT = GRML_DEFAULT or grml_flavour
1397     if options.dryrun:
1398         return 0
1399     elif not options.bootloaderonly:
1400         logging.info("Copying files. This might take a while....")
1401         try:
1402             copy_system_files(grml_flavour, iso_mount, target)
1403             copy_grml_files(iso_mount, target)
1404         except CriticalException, error:
1405             logging.critical("Execution failed: %s", error)
1406             sys.exit(1)
1407
1408     if not options.skipaddons:
1409         if not search_file('addons', iso_mount):
1410             logging.info("Could not find addons, therefore not installing.")
1411         else:
1412             copy_addons(iso_mount, target)
1413
1414     if not options.copyonly:
1415         copy_bootloader_files(iso_mount, target, grml_flavour)
1416
1417         if not options.dryrun:
1418             handle_bootloader_config(grml_flavour, device, target)
1419
1420     # make sure we sync filesystems before returning
1421     proc = subprocess.Popen(["sync"])
1422     proc.wait()
1423
1424
1425 def get_flavour(flavour_str):
1426     """Returns the flavour of a grml version string
1427     """
1428     return re.match(r'[\w-]*', flavour_str).group()
1429
1430 def identify_grml_flavour(mountpath):
1431     """Get name of grml flavour
1432
1433     @mountpath: path where the grml ISO is mounted to
1434     @return: name of grml-flavour"""
1435
1436     version_file = search_file('grml-version', mountpath)
1437
1438     if version_file == "":
1439         logging.critical("Error: could not find grml-version file.")
1440         raise
1441
1442     flavours = []
1443     tmpfile = None
1444     try:
1445         tmpfile = open(version_file, 'r')
1446         for line in tmpfile.readlines():
1447             flavours.append(get_flavour(line))
1448     except TypeError, e:
1449         raise
1450     except Exception, e:
1451         logging.critical("Unexpected error: %s", e)
1452         raise
1453     finally:
1454         if tmpfile:
1455             tmpfile.close()
1456
1457     return flavours
1458
1459
1460 def modify_grub_config(filename):
1461     """Adjust bootoptions for a grub file
1462
1463     @filename: filename to modify
1464     """
1465     if options.removeoption:
1466         regexe = []
1467         for regex in options.removeoption:
1468             regexe.append(re.compile(r'%s' % regex))
1469
1470         option_re = re.compile(r'(.*/boot/release/.*linux26.*)')
1471
1472         for line in fileinput.input(filename, inplace=1):
1473             if regexe and option_re.search(line):
1474                 for regex in regexe:
1475                     line = regex.sub(' ', line)
1476
1477             sys.stdout.write(line)
1478
1479         fileinput.close()
1480
1481 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1482     """Main handler for generating grub1 configuration
1483
1484     @grml_flavour: name of grml flavour the configuration should be generated for
1485     @install_partition: partition number for use in (hd0,X)
1486     @grub_target: path of grub's configuration files
1487     @bootoptions: additional bootoptions that should be used by default"""
1488
1489     # grub1 config
1490     grub1_cfg = grub_target + 'menu.lst'
1491     logging.debug("Creating grub1 configuration file (menu.lst)")
1492
1493     # install main configuration only *once*, no matter how many ISOs we have:
1494     if os.path.isfile(grub1_cfg):
1495         string = open(grub1_cfg).readline()
1496         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1497         if not re.match(main_identifier, string):
1498             grub1_config_file = open(grub1_cfg, 'w')
1499             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1500             grub1_config_file.close()
1501     else:
1502         grub1_config_file = open(grub1_cfg, 'w')
1503         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1504         grub1_config_file.close()
1505
1506     grub_flavour_config = True
1507     if os.path.isfile(grub1_cfg):
1508         string = open(grub1_cfg).readlines()
1509         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1510         for line in string:
1511             if flavour.match(line):
1512                 grub_flavour_config = False
1513
1514     if grub_flavour_config:
1515         grub1_config_file = open(grub1_cfg, 'a')
1516         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1517         grub1_config_file.close()
1518
1519     modify_grub_config(grub1_cfg)
1520
1521     # make sure grub.conf isn't a symlink but a plain file instead,
1522     # otherwise it will break on FAT16 filesystems
1523     # this works around grub-install of (at least) Fedora 10
1524     if os.path.isfile(grub1_cfg):
1525         grubconf = grub_target + 'grub.conf'
1526         if not os.path.islink(grubconf):
1527             import shutil
1528             shutil.copyfile(grub1_cfg, grub_target + 'grub.conf')
1529
1530 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1531     """Main handler for generating grub2 configuration
1532
1533     @grml_flavour: name of grml flavour the configuration should be generated for
1534     @grub_target: path of grub's configuration files
1535     @bootoptions: additional bootoptions that should be used by default"""
1536
1537     # grub2 config
1538     grub2_cfg = grub_target + 'grub.cfg'
1539     logging.debug("Creating grub2 configuration file (grub.cfg)")
1540
1541     global GRML_DEFAULT
1542
1543     # install main configuration only *once*, no matter how many ISOs we have:
1544     grub_flavour_is_default = False
1545     if os.path.isfile(grub2_cfg):
1546         string = open(grub2_cfg).readline()
1547         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1548         if not re.match(main_identifier, string):
1549             grub2_config_file = open(grub2_cfg, 'w')
1550             GRML_DEFAULT = grml_flavour
1551             grub_flavour_is_default = True
1552             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1553             grub2_config_file.close()
1554     else:
1555         grub2_config_file = open(grub2_cfg, 'w')
1556         GRML_DEFAULT = grml_flavour
1557         grub_flavour_is_default = True
1558         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1559         grub2_config_file.close()
1560
1561     # install flavour specific configuration only *once* as well
1562     grub_flavour_config = True
1563     if os.path.isfile(grub2_cfg):
1564         string = open(grub2_cfg).readlines()
1565         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1566         for line in string:
1567             if flavour.match(line):
1568                 grub_flavour_config = False
1569
1570     if grub_flavour_config:
1571         grub2_config_file = open(grub2_cfg, 'a')
1572         # display only if the grml flavour isn't the default
1573         if not grub_flavour_is_default:
1574             GRML_FLAVOURS.add(grml_flavour)
1575         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1576         grub2_config_file.close()
1577
1578     modify_grub_config(grub2_cfg)
1579
1580
1581 def get_bootoptions(grml_flavour):
1582     """Returns bootoptions for specific flavour
1583
1584     @grml_flavour: name of the grml_flavour
1585     """
1586     # do NOT write "None" in kernel cmdline
1587     if options.bootoptions is None:
1588         bootopt = ""
1589     else:
1590         bootopt = options.bootoptions
1591     bootopt = bootopt.replace("%flavour", grml_flavour)
1592     return bootopt
1593
1594
1595 def handle_grub_config(grml_flavour, device, target):
1596     """Main handler for generating grub (v1 and v2) configuration
1597
1598     @grml_flavour: name of grml flavour the configuration should be generated for
1599     @device: device/partition where grub should be installed to
1600     @target: path of grub's configuration files"""
1601
1602     logging.debug("Generating grub configuration")
1603
1604     grub_target = target + '/boot/grub/'
1605
1606     if os.path.isdir(device):
1607         install_grub1_partition = None
1608     else:
1609         if device[-1:].isdigit():
1610             install_grub1_partition = int(device[-1:]) - 1
1611         else:
1612             raise CriticalException("error validating partition schema (raw device?)")
1613
1614
1615     bootopt = get_bootoptions(grml_flavour)
1616
1617     # write menu.lst
1618     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1619     # write grub.cfg
1620     handle_grub2_config(grml_flavour, grub_target, bootopt)
1621
1622
1623 def initial_syslinux_config(target):
1624     """Generates intial syslinux configuration
1625
1626     @target path of syslinux's configuration files"""
1627
1628     target = target + "/"
1629     filename = target + "grmlmain.cfg"
1630     if os.path.isfile(target + "grmlmain.cfg"):
1631         return
1632     data = open(filename, "w")
1633     data.write(generate_main_syslinux_config())
1634     data.close()
1635
1636     filename = target + "hiddens.cfg"
1637     data = open(filename, "w")
1638     data.write("include hidden.cfg\n")
1639     data.close()
1640
1641 def add_entry_if_not_present(filename, entry):
1642     """Write entry into filename if entry is not already in the file
1643
1644     @filanme: name of the file
1645     @entry: data to write to the file
1646     """
1647     data = open(filename, "a+")
1648     for line in data:
1649         if line == entry:
1650             break
1651     else:
1652         data.write(entry)
1653
1654     data.close()
1655
1656 def get_flavour_filename(flavour):
1657     """Generate a iso9960 save filename out of the specified flavour
1658
1659     @flavour: grml flavour
1660     """
1661     return flavour.replace('-', '_')
1662
1663 def adjust_syslinux_bootoptions(src, flavour):
1664     """Adjust existing bootoptions of specified syslinux config to
1665     grml2usb specific ones, e.g. change the location of the kernel...
1666
1667     @src: config file to alter
1668     @flavour: grml flavour
1669     """
1670
1671     append_re = re.compile("^(\s*append.*/boot/release.*)$", re.I)
1672     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1673     # flavour_re = re.compile("(label.*)(grml\w+)")
1674     default_re = re.compile("(default.cfg)")
1675     bootid_re = re.compile("bootid=[\w_-]+")
1676     live_media_path_re = re.compile("live-media-path=[\w_/-]+")
1677
1678     bootopt = get_bootoptions(flavour)
1679
1680     regexe = []
1681     option_re = None
1682     if options.removeoption:
1683         option_re = re.compile(r'/boot/release/.*/initrd.gz')
1684
1685         for regex in options.removeoption:
1686             regexe.append(re.compile(r'%s' % regex))
1687
1688     for line in fileinput.input(src, inplace=1):
1689         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour.replace('-', ''), line)
1690         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1691         line = default_re.sub(r'%s-\1' % flavour, line)
1692         line = bootid_re.sub('', line)
1693         line = live_media_path_re.sub('', line)
1694         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1695         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1696         line = append_re.sub(r'\1 %s=%s ' % ("bootid", UUID), line)
1697         if option_re and option_re.search(line):
1698             for regex in regexe:
1699                 line = regex.sub(' ', line)
1700         sys.stdout.write(line)
1701     fileinput.close()
1702
1703 def adjust_labels(src, replacement):
1704     """Adjust the specified labels in the syslinux config file src with
1705     specified replacement
1706     """
1707     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1708     for line in fileinput.input(src, inplace=1):
1709         line = label_re.sub(replacement, line)
1710         sys.stdout.write(line)
1711     fileinput.close()
1712
1713
1714 def add_syslinux_entry(filename, grml_flavour):
1715     """Add includes for a specific grml_flavour to the specified filename
1716
1717     @filename: syslinux config file
1718     @grml_flavour: grml flavour to add
1719     """
1720
1721     entry_filename = "option_%s.cfg" % grml_flavour
1722     entry = "include %s\n" % entry_filename
1723
1724     add_entry_if_not_present(filename, entry)
1725     path = os.path.dirname(filename)
1726
1727     data = open(path + "/" + entry_filename, "w")
1728     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1729     data.close()
1730
1731 def modify_filenames(grml_flavour, target, filenames):
1732     """Replace the standarf filenames with the new ones
1733
1734     @grml_flavour: grml-flavour strin
1735     @target: directory where the files are located
1736     @filenames: list of filenames to alter
1737     """
1738     grml_filename = grml_flavour.replace('-', '_')
1739     for filename in filenames:
1740         old_filename = "%s/%s" % (target, filename)
1741         new_filename = "%s/%s_%s" % (target, grml_filename, filename)
1742         os.rename(old_filename, new_filename)
1743
1744
1745 def remove_default_entry(filename):
1746     """Remove the default entry from specified syslinux file
1747
1748     @filename: syslinux config file
1749     """
1750     default_re = re.compile("^(\s*menu\s*default\s*)$", re.I)
1751     for line in fileinput.input(filename, inplace=1):
1752         if default_re.match(line):
1753             continue
1754         sys.stdout.write(line)
1755     fileinput.close()
1756
1757
1758 def handle_syslinux_config(grml_flavour, target):
1759     """Main handler for generating syslinux configuration
1760
1761     @grml_flavour: name of grml flavour the configuration should be generated for
1762     @target: path of syslinux's configuration files"""
1763
1764     logging.debug("Generating syslinux configuration")
1765     syslinux_target = target + '/boot/syslinux/'
1766     # should be present via  copy_bootloader_files(), but make sure it exits:
1767     execute(mkdir, syslinux_target)
1768     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1769
1770
1771     # install main configuration only *once*, no matter how many ISOs we have:
1772     syslinux_config_file = open(syslinux_cfg, 'w')
1773     syslinux_config_file.write("TIMEOUT 300\n")
1774     syslinux_config_file.write("include vesamenu.cfg\n")
1775     syslinux_config_file.close()
1776
1777     prompt_name = open(syslinux_target + 'promptname.cfg', 'w')
1778     prompt_name.write('menu label S^yslinux prompt\n')
1779     prompt_name.close()
1780
1781     initial_syslinux_config(syslinux_target)
1782     flavour_filename = grml_flavour.replace('-', '_')
1783
1784     if search_file('default.cfg', syslinux_target):
1785         modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1786
1787     filename = search_file("new_hidden.cfg", syslinux_target)
1788
1789
1790     # process hidden file
1791     if not search_file("hidden.cfg", syslinux_target):
1792         new_hidden = syslinux_target + "hidden.cfg"
1793         os.rename(filename, new_hidden)
1794         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1795     else:
1796         new_hidden_file =  "%s/%s_hidden.cfg" % (syslinux_target, flavour_filename)
1797         os.rename(filename, new_hidden_file)
1798         adjust_labels(new_hidden_file, r'\1 %s-\2' % grml_flavour)
1799         adjust_syslinux_bootoptions(new_hidden_file, grml_flavour)
1800         entry = 'include %s_hidden.cfg\n' % flavour_filename
1801         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1802
1803
1804
1805     new_default = "%s_default.cfg" % (flavour_filename)
1806     entry = 'include %s\n' % new_default
1807     defaults_file = '%s/defaults.cfg' % syslinux_target
1808     new_default_with_path = "%s/%s" % (syslinux_target, new_default)
1809     new_grml_cfg = "%s/%s_grml.cfg" % ( syslinux_target, flavour_filename)
1810
1811     if os.path.isfile(defaults_file):
1812
1813         # remove default menu entry in menu
1814         remove_default_entry(new_default_with_path)
1815
1816         # adjust all labels for additional isos
1817         adjust_labels(new_default_with_path, r'\1 %s' % grml_flavour)
1818         adjust_labels(new_grml_cfg, r'\1 %s-\2' % grml_flavour)
1819
1820     # always adjust bootoptions
1821     adjust_syslinux_bootoptions(new_default_with_path, grml_flavour)
1822     adjust_syslinux_bootoptions(new_grml_cfg, grml_flavour)
1823
1824     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1825
1826     add_syslinux_entry("%s/additional.cfg" % syslinux_target, flavour_filename)
1827
1828
1829 def handle_bootloader_config(grml_flavour, device, target):
1830     """Main handler for generating bootloader's configuration
1831
1832     @grml_flavour: name of grml flavour the configuration should be generated for
1833     @device: device/partition where bootloader should be installed to
1834     @target: path of bootloader's configuration files"""
1835
1836     global UUID
1837     UUID = get_uuid(target)
1838     if options.skipsyslinuxconfig:
1839         logging.info("Skipping generation of syslinux configuration as requested.")
1840     else:
1841         try:
1842             handle_syslinux_config(grml_flavour, target)
1843         except CriticalException, error:
1844             logging.critical("Fatal: %s", error)
1845             sys.exit(1)
1846
1847     if options.skipgrubconfig:
1848         logging.info("Skipping generation of grub configuration as requested.")
1849     else:
1850         try:
1851             handle_grub_config(grml_flavour, device, target)
1852         except CriticalException, error:
1853             logging.critical("Fatal: %s", error)
1854             sys.exit(1)
1855
1856
1857
1858 def install(image, device):
1859     """Install a grml image to the specified device
1860
1861     @image: directory or is file
1862     @device: partition or directory to install the device
1863     """
1864     iso_mountpoint = image
1865     remove_image_mountpoint = False
1866     if os.path.isdir(image):
1867         logging.info("Using %s as install base", image)
1868     else:
1869         logging.info("Using ISO %s", image)
1870         iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1871         register_tmpfile(iso_mountpoint)
1872         remove_image_mountpoint = True
1873         try:
1874             mount(image, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1875         except CriticalException, error:
1876             logging.critical("Fatal: %s", error)
1877             sys.exit(1)
1878
1879     try:
1880         install_grml(iso_mountpoint, device)
1881     finally:
1882         if remove_image_mountpoint:
1883             try:
1884                 remove_mountpoint(iso_mountpoint)
1885             except CriticalException, error:
1886                 logging.critical("Fatal: %s", error)
1887                 cleanup()
1888
1889
1890
1891 def install_grml(mountpoint, device):
1892     """Main logic for copying files of the currently running grml system.
1893
1894     @mountpoin: directory where currently running live system resides (usually /live/image)
1895     @device: partition where the specified ISO should be installed to"""
1896
1897     device_mountpoint = device
1898     if os.path.isdir(device):
1899         logging.info("Specified device is not a directory, therefore not mounting.")
1900         remove_device_mountpoint = False
1901     else:
1902         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1903         register_tmpfile(device_mountpoint)
1904         remove_device_mountpoint = True
1905         try:
1906             check_for_fat(device)
1907             mount(device, device_mountpoint, ['-o', 'utf8,iocharset=iso8859-1'])
1908         except CriticalException, error:
1909             try:
1910                 mount(device, device_mountpoint, "")
1911             except CriticalException, error:
1912                 logging.critical("Fatal: %s", error)
1913                 raise
1914     try:
1915         grml_flavours = identify_grml_flavour(mountpoint)
1916         for flavour in set(grml_flavours):
1917             logging.info("Identified grml flavour \"%s\".", flavour)
1918             install_iso_files(flavour, mountpoint, device, device_mountpoint)
1919             GRML_FLAVOURS.add(flavour)
1920     finally:
1921         if remove_device_mountpoint:
1922             remove_mountpoint(device_mountpoint)
1923
1924 def remove_mountpoint(mountpoint):
1925     """remove a registred mountpoint
1926     """
1927
1928     try:
1929         unmount(mountpoint, "")
1930         if os.path.isdir(mountpoint):
1931             os.rmdir(mountpoint)
1932             unregister_tmpfile(mountpoint)
1933     except CriticalException, error:
1934         logging.critical("Fatal: %s", error)
1935         cleanup()
1936
1937 def handle_mbr(device):
1938     """Main handler for installing master boot record (MBR)
1939
1940     @device: device where the MBR should be installed to"""
1941
1942     if options.dryrun:
1943         logging.info("Would install MBR")
1944         return 0
1945
1946     if device[-1:].isdigit():
1947         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1948         partition_number = int(device[-1:]) - 1
1949         skip_install_mir_mbr = False
1950
1951     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1952     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1953     if mbr_device == "/dev/loop":
1954         mbr_device = device
1955         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1956         skip_install_mir_mbr = True
1957
1958     try:
1959         if options.syslinuxmbr:
1960             handle_syslinux_mbr(mbr_device)
1961         elif not skip_install_mir_mbr:
1962             if options.mbrmenu:
1963                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True)
1964             else:
1965                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False)
1966     except IOError, error:
1967         logging.critical("Execution failed: %s", error)
1968         sys.exit(1)
1969     except Exception, error:
1970         logging.critical("Execution failed: %s", error)
1971         sys.exit(1)
1972
1973
1974 def handle_vfat(device):
1975     """Check for FAT specific settings and options
1976
1977     @device: device that should checked / formated"""
1978
1979     # make sure we have mkfs.vfat available
1980     if options.fat16:
1981         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1982             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1983             logging.critical('Please make sure to install dosfstools.')
1984             sys.exit(1)
1985
1986         if options.force:
1987             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1988         else:
1989             # make sure the user is aware of what he is doing
1990             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1991             if f == "y" or f == "Y":
1992                 logging.info("Note: you can skip this question using the option --force")
1993             else:
1994                 sys.exit(1)
1995         try:
1996             mkfs_fat16(device)
1997         except CriticalException, error:
1998             logging.critical("Execution failed: %s", error)
1999             sys.exit(1)
2000
2001     # check for vfat filesystem
2002     if device is not None and not os.path.isdir(device) and options.syslinux:
2003         try:
2004             check_for_fat(device)
2005         except CriticalException, error:
2006             logging.critical("Execution failed: %s", error)
2007             sys.exit(1)
2008
2009     if not os.path.isdir(device) and not check_for_usbdevice(device) and not options.force:
2010         print "Warning: the specified device %s does not look like a removable usb device." % device
2011         f = raw_input("Do you really want to continue? y/N ")
2012         if f == "y" or f == "Y":
2013             pass
2014         else:
2015             sys.exit(1)
2016
2017
2018 def handle_compat_warning(device):
2019     """Backwards compatible checks
2020
2021     @device: device that should be checked"""
2022
2023     # make sure we can replace old grml2usb script and warn user when using old way of life:
2024     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
2025         print "Warning: the semantics of grml2usb has changed."
2026         print "Instead of using grml2usb /path/to/iso %s you might" % device
2027         print "want to use grml2usb /path/to/iso /dev/... instead."
2028         print "Please check out the grml2usb manpage for details."
2029         f = raw_input("Do you really want to continue? y/N ")
2030         if f == "y" or f == "Y":
2031             pass
2032         else:
2033             sys.exit(1)
2034
2035
2036 def handle_logging():
2037     """Log handling and configuration"""
2038
2039     if options.verbose and options.quiet:
2040         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
2041
2042     if options.verbose:
2043         FORMAT = "Debug: %(asctime)-15s %(message)s"
2044         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
2045     elif options.quiet:
2046         FORMAT = "Critical: %(message)s"
2047         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
2048     else:
2049         FORMAT = "%(message)s"
2050         logging.basicConfig(level=logging.INFO, format=FORMAT)
2051
2052
2053 def handle_bootloader(device):
2054     """wrapper for installing bootloader
2055
2056     @device: device where bootloader should be installed to"""
2057
2058     # Install bootloader only if not using the --copy-only option
2059     if options.copyonly:
2060         logging.info("Not installing bootloader and its files as requested via option copyonly.")
2061     elif os.path.isdir(device):
2062         logging.info("Not installing bootloader as %s is a directory.", device)
2063     else:
2064         install_bootloader(device)
2065
2066
2067 def check_options(opts):
2068     """Check compability of provided user opts
2069
2070     @opts option dict from OptionParser
2071     """
2072     if opts.grubmbr and not opts.grub:
2073         logging.critical("Error: --grub-mbr requires --grub option.")
2074         sys.exit(1)
2075
2076
2077 def check_programs():
2078     """check if all needed programs are installed"""
2079     if options.grub:
2080         if not which("grub-install"):
2081             logging.critical("Fatal: grub-install not available (please install the "
2082                              + "grub package or drop the --grub option)")
2083             sys.exit(1)
2084
2085     if options.syslinux:
2086         if not which("syslinux"):
2087             logging.critical("Fatal: syslinux not available (please install the "
2088                              + "syslinux package or use the --grub option)")
2089             sys.exit(1)
2090
2091     if not which("rsync"):
2092         logging.critical("Fatal: rsync not available, can not continue - sorry.")
2093         sys.exit(1)
2094
2095 def main():
2096     """Main function [make pylint happy :)]"""
2097
2098     if options.version:
2099         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
2100         sys.exit(0)
2101
2102     if len(args) < 2:
2103         parser.error("invalid usage")
2104
2105     # log handling
2106     handle_logging()
2107
2108     # make sure we have the appropriate permissions
2109     check_uid_root()
2110
2111     check_options(options)
2112
2113     logging.info("Executing grml2usb version %s", PROG_VERSION)
2114
2115     if options.dryrun:
2116         logging.info("Running in simulation mode as requested via option dry-run.")
2117
2118     check_programs()
2119
2120     # specified arguments
2121     device = args[len(args) - 1]
2122     isos = args[0:len(args) - 1]
2123
2124     if not os.path.isdir(device):
2125         if device[-1:].isdigit():
2126             if int(device[-1:]) > 4 or device[-2:].isdigit():
2127                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
2128                 sys.exit(1)
2129         elif os.path.exists(device):
2130             logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
2131             sys.exit(1)
2132
2133
2134     # provide upgrade path
2135     handle_compat_warning(device)
2136
2137     # check for vfat partition
2138     handle_vfat(device)
2139
2140     # main operation (like installing files)
2141     for iso in isos:
2142         install(iso, device)
2143
2144     # install mbr
2145     if not options.skipmbr and not os.path.isdir(device):
2146         handle_mbr(device)
2147
2148     handle_bootloader(device)
2149
2150     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
2151
2152     for flavour in GRML_FLAVOURS:
2153         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
2154
2155     # finally be politely :)
2156     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
2157
2158
2159 if __name__ == "__main__":
2160     try:
2161         main()
2162     except KeyboardInterrupt:
2163         logging.info("Received KeyboardInterrupt")
2164         cleanup()
2165
2166 ## END OF FILE #################################################################
2167 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8