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