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