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