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