Update changelog for release 0.9.20.
[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     conf_target = target + "/conf/"
1004     uuid_file_name = conf_target + "/bootid.txt"
1005     if os.path.isdir(conf_target):
1006         if os.path.isfile(uuid_file_name):
1007            uuid_file = open(uuid_file_name, 'r')
1008            uid = uuid_file.readline().strip()
1009            uuid_file.close()
1010            return uid
1011         else:
1012            return write_uuid(uuid_file_name)
1013     else:
1014         execute(mkdir, conf_target)
1015         return write_uuid(uuid_file_name)
1016
1017
1018 def copy_system_files(grml_flavour, iso_mount, target):
1019     """copy grml's main files (like squashfs, kernel and initrd) to a given target
1020
1021     @grml_flavour: name of grml flavour the configuration should be generated for
1022     @iso_mount: path where a grml ISO is mounted on
1023     @target: path where grml's main files should be copied to"""
1024
1025     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
1026     if squashfs is None:
1027         logging.critical("Fatal: squashfs file not found")
1028         raise CriticalException("error locating squashfs file")
1029     else:
1030         squashfs_target = target + '/live/' + grml_flavour + '/'
1031         execute(mkdir, squashfs_target)
1032     exec_rsync(squashfs, squashfs_target + grml_flavour + '.squashfs')
1033
1034     filesystem_module = search_file('filesystem.module', iso_mount)
1035     if filesystem_module is None:
1036         logging.critical("Fatal: filesystem.module not found")
1037         raise CriticalException("error locating filesystem.module file")
1038     else:
1039         exec_rsync(filesystem_module, squashfs_target + 'filesystem.module')
1040
1041
1042     release_target = target + '/boot/release/' + grml_flavour.replace('-', '')
1043     execute(mkdir, release_target)
1044
1045     kernel = search_file('linux26', iso_mount)
1046     if kernel is None:
1047         logging.critical("Fatal kernel not found")
1048         raise CriticalException("error locating kernel file")
1049     else:
1050         exec_rsync(kernel, release_target + '/linux26')
1051
1052     initrd = search_file('initrd.gz', iso_mount)
1053     if initrd is None:
1054         logging.critical("Fatal: initrd not found")
1055         raise CriticalException("error locating initrd file")
1056     else:
1057         exec_rsync(initrd, release_target + '/initrd.gz')
1058
1059
1060 def copy_grml_files(iso_mount, target):
1061     """copy some minor grml files to a given target
1062
1063     @iso_mount: path where a grml ISO is mounted on
1064     @target: path where grml's main files should be copied to"""
1065
1066     grml_target = target + '/grml/'
1067     execute(mkdir, grml_target)
1068
1069     for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
1070         grml_file = search_file(myfile, iso_mount)
1071         if grml_file is None:
1072             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
1073         else:
1074             exec_rsync(grml_file, grml_target + myfile)
1075
1076     grml_web_target = grml_target + '/web/'
1077     execute(mkdir, grml_web_target)
1078
1079     for myfile in 'index.html', 'style.css':
1080         grml_file = search_file(myfile, iso_mount)
1081         if grml_file is None:
1082             logging.warn("Warning: myfile %s could not be found - can not install it")
1083         else:
1084             exec_rsync(grml_file, grml_web_target + myfile)
1085
1086     grml_webimg_target = grml_web_target + '/images/'
1087     execute(mkdir, grml_webimg_target)
1088
1089     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1090         grml_file = search_file(myfile, iso_mount)
1091         if grml_file is None:
1092             logging.warn("Warning: myfile %s could not be found - can not install it")
1093         else:
1094             exec_rsync(grml_file, grml_webimg_target + myfile)
1095
1096
1097 def copy_addons(iso_mount, target):
1098     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1099
1100     @iso_mount: path where a grml ISO is mounted on
1101     @target: path where grml's main files should be copied to"""
1102
1103     addons = target + '/boot/addons/'
1104     execute(mkdir, addons)
1105
1106     # grub all-in-one image
1107     allinoneimg = search_file('allinone.img', iso_mount)
1108     if allinoneimg is None:
1109         logging.warn("Warning: allinone.img not found (that's fine if you don't need it)")
1110     else:
1111         exec_rsync(allinoneimg, addons + 'allinone.img')
1112
1113     # bsd imag
1114     bsdimg = search_file('bsd4grml', iso_mount)
1115     if bsdimg is None:
1116         logging.warn("Warning: bsd4grml not found (that's fine if you don't need it)")
1117     else:
1118         exec_rsync(bsdimg, addons + '/')
1119
1120     # freedos image
1121     balderimg = search_file('balder10.imz', iso_mount)
1122     if balderimg is None:
1123         logging.warn("Warning: balder10.imz not found (that's fine if you don't need it)")
1124     else:
1125         exec_rsync(balderimg, addons + 'balder10.imz')
1126
1127     # install hdt and pci.ids only when using syslinux (grub doesn't support it)
1128     if options.syslinux:
1129         # hdt (hardware detection tool) image
1130         hdtimg = search_file('hdt.c32', iso_mount)
1131         if hdtimg:
1132             exec_rsync(hdtimg, addons + '/hdt.c32')
1133
1134         # pci.ids file
1135         picids = search_file('pci.ids', iso_mount)
1136         if picids:
1137             exec_rsync(picids, addons + '/pci.ids')
1138
1139     # memdisk image
1140     memdiskimg = search_file('memdisk', iso_mount)
1141     if memdiskimg is None:
1142         logging.warn("Warning: memdisk not found (that's fine if you don't need it)")
1143     else:
1144         exec_rsync(memdiskimg, addons + 'memdisk')
1145
1146     # memtest86+ image
1147     memtestimg = search_file('memtest', iso_mount)
1148     if memtestimg is None:
1149         logging.warn("Warning: memtest not found (that's fine if you don't need it)")
1150     else:
1151         exec_rsync(memtestimg, addons + 'memtest')
1152
1153     # gpxe.lkrn
1154     gpxeimg = search_file('gpxe.lkrn', iso_mount)
1155     if gpxeimg is None:
1156         logging.warn("Warning: gpxe.lkrn not found (that's fine if you don't need it)")
1157     else:
1158         exec_rsync(gpxeimg, addons + 'gpxe.lkrn')
1159
1160 def copy_bootloader_files(iso_mount, target):
1161     """copy grml's bootloader files to a given target
1162
1163     @iso_mount: path where a grml ISO is mounted on
1164     @target: path where grml's main files should be copied to"""
1165
1166     syslinux_target = target + '/boot/syslinux/'
1167     execute(mkdir, syslinux_target)
1168
1169     logo = search_file('logo.16', iso_mount)
1170     exec_rsync(logo, syslinux_target + 'logo.16')
1171
1172     for ffile in ['f%d' % number for number in range(1,11) ]:
1173         bootsplash = search_file(ffile, iso_mount)
1174         if not bootsplash:
1175             continue
1176         exec_rsync(bootsplash, syslinux_target + ffile)
1177
1178     # avoid the "file is read only, overwrite anyway (y/n) ?" question
1179     # of mtools by syslinux ("mmove -D o -D O s:/ldlinux.sys $target_file")
1180     if os.path.isfile(syslinux_target + 'ldlinux.sys'):
1181         os.unlink(syslinux_target + 'ldlinux.sys')
1182
1183     bootloader_dirs = ['/boot/isolinux/', '/boot/syslinux/']
1184     source_dir = None
1185     for dir in bootloader_dirs:
1186         if glob.glob(iso_mount + dir + '*default.cfg'):
1187             source_dir = dir
1188             break
1189     else:
1190         logging.critical("Fatal: file default.cfg could not be found.")
1191         logging.critical("Note:  this grml2usb version requires an ISO generated by grml-live >=0.9.24 ...")
1192         logging.critical("       ... either use grml releases >=2009.10 or switch to an older grml2usb version.")
1193         logging.critical("       Please visit http://grml.org/grml2usb/#grml2usb-compat for further information.")
1194         raise
1195
1196     for expr in '*default.cfg', 'distri.cfg', \
1197                     '*grml.cfg', 'grml.png', 'hd.cfg', 'isolinux.cfg', 'isolinux.bin', \
1198                     'isoprompt.cfg', 'options.cfg', \
1199                     'prompt.cfg', 'vesamenu.c32', 'vesamenu.cfg', 'grml.png':
1200         files = glob.glob(iso_mount + source_dir + expr)
1201         for path in files:
1202             filename = os.path.basename(path)
1203             exec_rsync(path, syslinux_target + filename)
1204
1205     # copy the addons_*.cfg file to the new syslinux directory
1206     for filename in glob.glob(iso_mount + source_dir + 'addon*.cfg'):
1207         exec_rsync(filename, syslinux_target)
1208
1209     path = search_file('hidden.cfg', iso_mount + source_dir)
1210     if path:
1211         exec_rsync(path, syslinux_target + "new_" + 'hidden.cfg')
1212
1213
1214     grub_target = target + '/boot/grub/'
1215     execute(mkdir, grub_target)
1216
1217     if not os.path.isfile(GRML2USB_BASE + "/grub/splash.xpm.gz"):
1218         logging.critical("Error: %s/grub/splash.xpm.gz can not be read.", GRML2USB_BASE)
1219         logging.critical("Please make sure you've installed the grml2usb (Debian) package!")
1220         raise
1221     else:
1222         exec_rsync(GRML2USB_BASE + '/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz')
1223
1224     # grml splash in grub
1225     if os.path.isfile(GRML2USB_BASE + "/grub/grml.png"):
1226         exec_rsync(GRML2USB_BASE + '/grub/grml.png', grub_target + 'grml.png')
1227
1228     # font file for graphical bootsplash in grub
1229     if os.path.isfile("/usr/share/grub/ascii.pf2"):
1230         exec_rsync('/usr/share/grub/ascii.pf2', grub_target + 'ascii.pf2')
1231
1232     # always copy grub content as it might be useful
1233     for file in glob.glob(iso_mount + '/boot/grub/*.mod'):
1234         exec_rsync(file, grub_target)
1235
1236     for file in glob.glob(iso_mount + '/boot/grub/*.img'):
1237         exec_rsync(file, grub_target)
1238
1239     for file in glob.glob(iso_mount + '/boot/grub/stage*'):
1240         exec_rsync(file, grub_target)
1241
1242 def install_iso_files(grml_flavour, iso_mount, device, target):
1243     """Copy files from ISO to given target
1244
1245     @grml_flavour: name of grml flavour the configuration should be generated for
1246     @iso_mount: path where a grml ISO is mounted on
1247     @device: device/partition where bootloader should be installed to
1248     @target: path where grml's main files should be copied to"""
1249
1250     # TODO => several improvements:
1251     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1252     # * provide alternative search_file() if file information is stored in a config.ini file?
1253     # * catch "install: .. No space left on device" & CO
1254
1255     if options.dryrun:
1256         global GRML_DEFAULT
1257         GRML_DEFAULT = grml_flavour
1258         return 0
1259     elif not options.bootloaderonly:
1260         logging.info("Copying files. This might take a while....")
1261         try:
1262             copy_system_files(grml_flavour, iso_mount, target)
1263             copy_grml_files(iso_mount, target)
1264         except CriticalException, error:
1265             logging.critical("Execution failed: %s", error)
1266             sys.exit(1)
1267
1268     if not options.skipaddons:
1269         if not search_file('addons', iso_mount):
1270             logging.info("Could not find addons, therefore not installing.")
1271         else:
1272             copy_addons(iso_mount, target)
1273
1274     if not options.copyonly:
1275         copy_bootloader_files(iso_mount, target)
1276
1277         if not options.dryrun:
1278             handle_bootloader_config(grml_flavour, device, target)
1279
1280     # make sure we sync filesystems before returning
1281     proc = subprocess.Popen(["sync"])
1282     proc.wait()
1283
1284
1285 def uninstall_files(device):
1286     """Get rid of all grml files on specified device
1287
1288     @device: partition where grml2usb files should be removed from"""
1289
1290     # TODO - not implemented yet
1291     logging.critical("TODO: uninstalling files from %s not yet implement, sorry.", device)
1292
1293
1294 def identify_grml_flavour(mountpath):
1295     """Get name of grml flavour
1296
1297     @mountpath: path where the grml ISO is mounted to
1298     @return: name of grml-flavour"""
1299
1300     version_file = search_file('grml-version', mountpath)
1301
1302     if version_file == "":
1303         logging.critical("Error: could not find grml-version file.")
1304         raise
1305
1306     try:
1307         tmpfile = open(version_file, 'r')
1308         grml_info = tmpfile.readline()
1309         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1310     except TypeError:
1311         raise
1312     except Exception, e:
1313         logging.critical("Unexpected error: %s", e)
1314         raise
1315
1316     return grml_flavour
1317
1318
1319 def modify_grub_config(filename):
1320     if options.removeoption:
1321         regexe = []
1322         for regex in options.removeoption:
1323             regexe.append(re.compile(r'%s' % regex))
1324
1325         option_re = re.compile(r'(.*/boot/release/.*linux26.*)')
1326
1327         for line in fileinput.input(filename, inplace=1):
1328             if regexe and option_re.search(line):
1329                 for regex in regexe:
1330                     line = regex.sub(' ', line)
1331
1332             sys.stdout.write(line)
1333
1334         fileinput.close()
1335
1336 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1337     """Main handler for generating grub1 configuration
1338
1339     @grml_flavour: name of grml flavour the configuration should be generated for
1340     @install_partition: partition number for use in (hd0,X)
1341     @grub_target: path of grub's configuration files
1342     @bootoptions: additional bootoptions that should be used by default"""
1343
1344     # grub1 config
1345     grub1_cfg = grub_target + 'menu.lst'
1346     logging.debug("Creating grub1 configuration file (menu.lst)")
1347
1348     # install main configuration only *once*, no matter how many ISOs we have:
1349     if os.path.isfile(grub1_cfg):
1350         string = open(grub1_cfg).readline()
1351         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1352         if not re.match(main_identifier, string):
1353             grub1_config_file = open(grub1_cfg, 'w')
1354             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1355             grub1_config_file.close()
1356     else:
1357         grub1_config_file = open(grub1_cfg, 'w')
1358         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1359         grub1_config_file.close()
1360
1361     grub_flavour_config = True
1362     if os.path.isfile(grub1_cfg):
1363         string = open(grub1_cfg).readlines()
1364         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1365         for line in string:
1366             if flavour.match(line):
1367                 grub_flavour_config = False
1368
1369     if grub_flavour_config:
1370         grub1_config_file = open(grub1_cfg, 'a')
1371         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1372         grub1_config_file.close()
1373
1374     modify_grub_config(grub1_cfg)
1375
1376     # make sure grub.conf isn't a symlink but a plain file instead,
1377     # otherwise it will break on FAT16 filesystems
1378     # this works around grub-install of (at least) Fedora 10
1379     if os.path.isfile(grub1_cfg):
1380         grubconf = grub_target + 'grub.conf'
1381         if not os.path.islink(grubconf):
1382             import shutil
1383             shutil.copyfile(grub1_cfg, grub_target + 'grub.conf')
1384
1385 def handle_grub2_config(grml_flavour, grub_target, bootopt):
1386     """Main handler for generating grub2 configuration
1387
1388     @grml_flavour: name of grml flavour the configuration should be generated for
1389     @grub_target: path of grub's configuration files
1390     @bootoptions: additional bootoptions that should be used by default"""
1391
1392     # grub2 config
1393     grub2_cfg = grub_target + 'grub.cfg'
1394     logging.debug("Creating grub2 configuration file (grub.lst)")
1395
1396     global GRML_DEFAULT
1397
1398     # install main configuration only *once*, no matter how many ISOs we have:
1399     grub_flavour_is_default = False
1400     if os.path.isfile(grub2_cfg):
1401         string = open(grub2_cfg).readline()
1402         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1403         if not re.match(main_identifier, string):
1404             grub2_config_file = open(grub2_cfg, 'w')
1405             GRML_DEFAULT = grml_flavour
1406             grub_flavour_is_default = True
1407             grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1408             grub2_config_file.close()
1409     else:
1410         grub2_config_file = open(grub2_cfg, 'w')
1411         GRML_DEFAULT = grml_flavour
1412         grub_flavour_is_default = True
1413         grub2_config_file.write(generate_main_grub2_config(grml_flavour, bootopt))
1414         grub2_config_file.close()
1415
1416     # install flavour specific configuration only *once* as well
1417     grub_flavour_config = True
1418     if os.path.isfile(grub2_cfg):
1419         string = open(grub2_cfg).readlines()
1420         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1421         for line in string:
1422             if flavour.match(line):
1423                 grub_flavour_config = False
1424
1425     if grub_flavour_config:
1426         grub2_config_file = open(grub2_cfg, 'a')
1427         # display only if the grml flavour isn't the default
1428         if not grub_flavour_is_default:
1429             GRML_FLAVOURS.add(grml_flavour)
1430         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, bootopt))
1431         grub2_config_file.close()
1432
1433     modify_grub_config(grub2_cfg)
1434
1435
1436 def handle_grub_config(grml_flavour, device, target):
1437     """Main handler for generating grub (v1 and v2) configuration
1438
1439     @grml_flavour: name of grml flavour the configuration should be generated for
1440     @device: device/partition where grub should be installed to
1441     @target: path of grub's configuration files"""
1442
1443     logging.debug("Generating grub configuration")
1444
1445     grub_target = target + '/boot/grub/'
1446     execute(mkdir, grub_target)
1447
1448     if os.path.isdir(device):
1449         install_grub1_partition = None
1450     else:
1451         if device[-1:].isdigit():
1452             install_grub1_partition = int(device[-1:]) - 1
1453         else:
1454             raise CriticalException("error validating partition schema (raw device?)")
1455
1456     # do NOT write "None" in kernel cmdline
1457     if options.bootoptions is None:
1458         bootopt = ""
1459     else:
1460         bootopt = options.bootoptions
1461
1462     # write menu.lst
1463     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1464     # write grub.cfg
1465     handle_grub2_config(grml_flavour, grub_target, bootopt)
1466
1467
1468 def initial_syslinux_config(target):
1469     """Generates intial syslinux configuration
1470
1471     @target path of syslinux's configuration files"""
1472
1473     target = target + "/"
1474     filename = target + "grmlmain.cfg"
1475     if os.path.isfile(target + "grmlmain.cfg"):
1476         return
1477     data = open(filename, "w")
1478     data.write(generate_main_syslinux_config())
1479     data.close
1480
1481     filename = target + "hiddens.cfg"
1482     data = open(filename, "w")
1483     data.write("include hidden.cfg\n")
1484     data.close()
1485
1486 def add_entry_if_not_present(filename, entry):
1487     data = open(filename, "a+")
1488     for line in data:
1489         if line == entry:
1490             break
1491     else:
1492         data.write(entry)
1493
1494     data.close()
1495
1496
1497
1498 def adjust_syslinux_bootoptions(src, flavour):
1499     append_re = re.compile("^(\s*append.*)$", re.I)
1500     boot_re = re.compile("/boot/([a-zA-Z0-9_]+/)+([a-zA-Z0-9._]+)")
1501     # flavour_re = re.compile("(label.*)(grml\w+)")
1502     default_re = re.compile("(default.cfg)")
1503     bootid_re = re.compile("(bootid)=[\w_-]+")
1504
1505     # do NOT write "None" in kernel cmdline
1506     if options.bootoptions is None:
1507         bootopt = ""
1508     else:
1509         bootopt = options.bootoptions
1510
1511     regexe = []
1512     option_re = None
1513     if options.removeoption:
1514         option_re = re.compile(r'/boot/release/.*/initrd.gz')
1515
1516         for regex in options.removeoption:
1517             regexe.append(re.compile(r'%s' % regex))
1518
1519     global UUID
1520     for line in fileinput.input(src, inplace=1):
1521         line = boot_re.sub(r'/boot/release/%s/\2 ' % flavour.replace('-', ''), line)
1522         # line = flavour_re.sub(r'\1 %s-\2' % flavour, line)
1523         line = default_re.sub(r'%s-\1' % flavour, line)
1524         line = bootid_re.sub(' ', line)
1525         line = append_re.sub(r'\1 live-media-path=/live/%s/ ' % flavour, line)
1526         line = append_re.sub(r'\1 boot=live %s ' % bootopt, line)
1527         line = append_re.sub(r'\1 %s=%s ' % ("bootid", UUID), line)
1528         if option_re and option_re.search(line):
1529             for regex in regexe:
1530                 line = regex.sub(' ', line)
1531         sys.stdout.write(line)
1532     fileinput.close()
1533
1534 def adjust_labels(src, replacement):
1535     label_re = re.compile("^(\s*label\s*) ([a-zA-Z0-9_-]+)", re.I)
1536     for line in fileinput.input(src, inplace=1):
1537         line = label_re.sub(replacement, line)
1538         sys.stdout.write(line)
1539     fileinput.close()
1540
1541
1542 def add_syslinux_entry(filename, grml_flavour):
1543     entry_filename = "option_%s.cfg" % grml_flavour
1544     entry = "include %s\n" % entry_filename
1545
1546     add_entry_if_not_present(filename, entry)
1547     path = os.path.dirname(filename)
1548
1549     data = open(path + "/" + entry_filename, "w")
1550     data.write(generate_flavour_specific_syslinux_config(grml_flavour))
1551     data.close()
1552
1553 def modify_filenames(grml_flavour, target, filenames):
1554     grml_filename = grml_flavour.replace('-', '_')
1555     for filename in filenames:
1556         old_filename = "%s/%s" % (target, filename)
1557         new_filename = "%s/%s_%s" % (target, grml_filename, filename)
1558         os.rename(old_filename, new_filename)
1559         adjust_syslinux_bootoptions(new_filename, grml_flavour)
1560
1561
1562 def remove_default_entry(filename):
1563     default_re = re.compile("^(\s*menu\s*default\s*)$", re.I)
1564     for line in fileinput.input(filename, inplace=1):
1565         if default_re.match(line): continue
1566         sys.stdout.write(line)
1567     fileinput.close()
1568
1569
1570 def handle_syslinux_config(grml_flavour, target):
1571     """Main handler for generating syslinux configuration
1572
1573     @grml_flavour: name of grml flavour the configuration should be generated for
1574     @target: path of syslinux's configuration files"""
1575
1576     # do NOT write "None" in kernel cmdline
1577     if options.bootoptions is None:
1578         bootopt = ""
1579     else:
1580         bootopt = options.bootoptions
1581
1582     logging.debug("Generating syslinux configuration")
1583     syslinux_target = target + '/boot/syslinux/'
1584     # should be present via  copy_bootloader_files(), but make sure it exits:
1585     execute(mkdir, syslinux_target)
1586     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1587
1588     global GRML_DEFAULT
1589
1590     # install main configuration only *once*, no matter how many ISOs we have:
1591     syslinux_flavour_is_default = False
1592     syslinux_config_file = open(syslinux_cfg, 'w')
1593     syslinux_config_file.write("TIMEOUT 300\n")
1594     syslinux_config_file.write("include vesamenu.cfg\n")
1595     syslinux_config_file.close()
1596
1597     prompt_name = open(syslinux_target + 'promptname.cfg', 'w')
1598     prompt_name.write('menu label S^yslinux prompt\n')
1599     prompt_name.close()
1600
1601     initial_syslinux_config(syslinux_target)
1602     if search_file('default.cfg', syslinux_target):
1603         modify_filenames(grml_flavour, syslinux_target, ['grml.cfg', 'default.cfg'])
1604
1605     filename = search_file("new_hidden.cfg", syslinux_target)
1606
1607
1608     flavour_filename = grml_flavour.replace('-', '_')
1609     # process hidden file
1610     if not search_file("hidden.cfg", syslinux_target):
1611         new_hidden = syslinux_target + "hidden.cfg"
1612         os.rename(filename, new_hidden)
1613         adjust_syslinux_bootoptions(new_hidden, grml_flavour)
1614     else:
1615         new_hidden =  "%s_hidden.cfg" % (flavour_filename)
1616         new_hidden_file =  "%s/%s" % (syslinux_target, new_hidden)
1617         os.rename(filename, new_hidden_file)
1618         adjust_labels(new_hidden_file, r'\1 %s-\2' % grml_flavour)
1619         adjust_syslinux_bootoptions(new_hidden_file, grml_flavour)
1620         entry = 'include %s\n' % new_hidden
1621         add_entry_if_not_present("%s/hiddens.cfg" % syslinux_target, entry)
1622
1623
1624
1625     new_default = "%s_default.cfg" % (flavour_filename)
1626     entry = 'include %s\n' % new_default
1627     defaults_file = '%s/defaults.cfg' % syslinux_target
1628
1629     if os.path.isfile(defaults_file):
1630         new_default_with_path = "%s/%s" % (syslinux_target, new_default)
1631         new_grml_cfg = "%s/%s_grml.cfg" % ( syslinux_target, flavour_filename)
1632
1633         # remove default menu entry in menu
1634         remove_default_entry(new_default_with_path)
1635
1636         # adjust all labels for additional isos
1637         adjust_labels(new_default_with_path, r'\1 %s' % grml_flavour)
1638         adjust_labels(new_grml_cfg, r'\1 %s-\2' % grml_flavour)
1639
1640
1641     add_entry_if_not_present("%s/defaults.cfg" % syslinux_target, entry)
1642
1643     add_syslinux_entry("%s/additional.cfg" % syslinux_target, flavour_filename)
1644
1645
1646 def handle_bootloader_config(grml_flavour, device, target):
1647     """Main handler for generating bootloader's configuration
1648
1649     @grml_flavour: name of grml flavour the configuration should be generated for
1650     @device: device/partition where bootloader should be installed to
1651     @target: path of bootloader's configuration files"""
1652
1653     global UUID
1654     UUID = get_uuid(target)
1655     if options.skipsyslinuxconfig:
1656         logging.info("Skipping generation of syslinux configuration as requested.")
1657     else:
1658         try:
1659             handle_syslinux_config(grml_flavour, target)
1660         except CriticalException, error:
1661             logging.critical("Fatal: %s", error)
1662             sys.exit(1)
1663
1664     if options.skipgrubconfig:
1665         logging.info("Skipping generation of grub configuration as requested.")
1666     else:
1667         try:
1668             handle_grub_config(grml_flavour, device, target)
1669         except CriticalException, error:
1670             logging.critical("Fatal: %s", error)
1671             sys.exit(1)
1672
1673
1674 def handle_dir(live_image, device):
1675     """Main logic for copying files of the currently running grml system.
1676
1677     @live_image: directory where currently running live system resides (usually /live/image)
1678     @device: partition where the specified ISO should be installed to"""
1679
1680     logging.info("Using %s as install base", live_image)
1681
1682     if os.path.isdir(device):
1683         logging.info("Specified target is a directory, therefore not mounting.")
1684         device_mountpoint = device
1685         remove_device_mountpoint = False
1686     else:
1687         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1688         register_tmpfile(device_mountpoint)
1689         remove_device_mountpoint = True
1690         try:
1691             mount(device, device_mountpoint, "")
1692         except CriticalException, error:
1693             logging.critical("Fatal: %s", error)
1694             cleanup()
1695             sys.exit(1)
1696
1697     try:
1698         try:
1699             grml_flavour = identify_grml_flavour(live_image)
1700             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1701             install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1702         except TypeError:
1703             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1704             sys.exit(1)
1705     finally:
1706         if remove_device_mountpoint:
1707             try:
1708                 unmount(device_mountpoint, "")
1709                 if os.path.isdir(device_mountpoint):
1710                     os.rmdir(device_mountpoint)
1711                     unregister_tmpfile(device_mountpoint)
1712             except CriticalException, error:
1713                 logging.critical("Fatal: %s", error)
1714                 cleanup()
1715
1716
1717 def handle_iso(iso, device):
1718     """Main logic for mounting ISOs and copying files.
1719
1720     @iso: full path to the ISO that should be installed to the specified device
1721     @device: partition where the specified ISO should be installed to"""
1722
1723     logging.info("Using ISO %s", iso)
1724
1725     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1726     register_tmpfile(iso_mountpoint)
1727     remove_iso_mountpoint = True
1728
1729     if not os.path.isfile(iso):
1730         logging.critical("Fatal: specified ISO %s could not be read", iso)
1731         cleanup()
1732         sys.exit(1)
1733
1734     try:
1735         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1736     except CriticalException, error:
1737         logging.critical("Fatal: %s", error)
1738         sys.exit(1)
1739
1740     if os.path.isdir(device):
1741         logging.info("Specified target is a directory, therefore not mounting.")
1742         device_mountpoint = device
1743         remove_device_mountpoint = False
1744         # skip_mbr = True
1745     else:
1746         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1747         register_tmpfile(device_mountpoint)
1748         remove_device_mountpoint = True
1749         try:
1750             check_for_fat(device)
1751             mount(device, device_mountpoint, ['-o', 'utf8,iocharset=iso8859-1'])
1752         except CriticalException, error:
1753             try:
1754                 mount(device, device_mountpoint, "")
1755             except CriticalException, error:
1756                 logging.critical("Fatal: %s", error)
1757                 cleanup()
1758                 sys.exit(1)
1759
1760     try:
1761         try:
1762             grml_flavour = identify_grml_flavour(iso_mountpoint)
1763             logging.info("Identified grml flavour \"%s\".", grml_flavour)
1764             install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1765         except TypeError:
1766             logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1767             sys.exit(1)
1768     finally:
1769         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1770             unmount(iso_mountpoint, "")
1771             os.rmdir(iso_mountpoint)
1772             unregister_tmpfile(iso_mountpoint)
1773         if remove_device_mountpoint:
1774             try:
1775                 unmount(device_mountpoint, "")
1776                 if os.path.isdir(device_mountpoint):
1777                     os.rmdir(device_mountpoint)
1778                     unregister_tmpfile(device_mountpoint)
1779             except CriticalException, error:
1780                 logging.critical("Fatal: %s", error)
1781                 cleanup()
1782
1783
1784 def handle_mbr(device):
1785     """Main handler for installing master boot record (MBR)
1786
1787     @device: device where the MBR should be installed to"""
1788
1789     if options.dryrun:
1790         logging.info("Would install MBR")
1791         return 0
1792
1793     if device[-1:].isdigit():
1794         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1795         partition_number = int(device[-1:]) - 1
1796         skip_install_mir_mbr = False
1797
1798     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1799     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1800     if mbr_device == "/dev/loop":
1801         mbr_device = device
1802         logging.info("Detected loop device - using %s as MBR device therefore", mbr_device)
1803         skip_install_mir_mbr = True
1804
1805     try:
1806         if options.syslinuxmbr:
1807             handle_syslinux_mbr(mbr_device)
1808         elif not skip_install_mir_mbr:
1809             if options.mbrmenu:
1810                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrmgr', mbr_device, partition_number, True)
1811             else:
1812                 install_mir_mbr(GRML2USB_BASE + '/mbr/mbrldr', mbr_device, partition_number, False)
1813     except IOError, error:
1814         logging.critical("Execution failed: %s", error)
1815         sys.exit(1)
1816     except Exception, error:
1817         logging.critical("Execution failed: %s", error)
1818         sys.exit(1)
1819
1820
1821 def handle_vfat(device):
1822     """Check for FAT specific settings and options
1823
1824     @device: device that should checked / formated"""
1825
1826     # make sure we have mkfs.vfat available
1827     if options.fat16:
1828         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1829             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1830             logging.critical('Please make sure to install dosfstools.')
1831             sys.exit(1)
1832
1833         exec_mkfs = False
1834         if options.force:
1835             print "Forcing mkfs.fat16 on %s as requested via option --force." % device
1836             exec_mkfs = True
1837         else:
1838             # make sure the user is aware of what he is doing
1839             f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1840             if f == "y" or f == "Y":
1841                 logging.info("Note: you can skip this question using the option --force")
1842                 exec_mkfs = True
1843
1844         if exec_mkfs:
1845             try:
1846                 mkfs_fat16(device)
1847             except CriticalException, error:
1848                 logging.critical("Execution failed: %s", error)
1849                 sys.exit(1)
1850         else:
1851             sys.exit(1)
1852
1853     # check for vfat filesystem
1854     if device is not None and not os.path.isdir(device):
1855         try:
1856             if options.syslinux: check_for_fat(device)
1857         except CriticalException, error:
1858             logging.critical("Execution failed: %s", error)
1859             sys.exit(1)
1860
1861     if not os.path.isdir(device) and not check_for_usbdevice(device):
1862         print "Warning: the specified device %s does not look like a removable usb device." % device
1863         f = raw_input("Do you really want to continue? y/N ")
1864         if f == "y" or f == "Y":
1865             pass
1866         else:
1867             sys.exit(1)
1868
1869
1870 def handle_compat_warning(device):
1871     """Backwards compatible checks
1872
1873     @device: device that should be checked"""
1874
1875     # make sure we can replace old grml2usb script and warn user when using old way of life:
1876     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1877         print "Warning: the semantics of grml2usb has changed."
1878         print "Instead of using grml2usb /path/to/iso %s you might" % device
1879         print "want to use grml2usb /path/to/iso /dev/... instead."
1880         print "Please check out the grml2usb manpage for details."
1881         f = raw_input("Do you really want to continue? y/N ")
1882         if f == "y" or f == "Y":
1883             pass
1884         else:
1885             sys.exit(1)
1886
1887
1888 def handle_logging():
1889     """Log handling and configuration"""
1890
1891     if options.verbose and options.quiet:
1892         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1893
1894     if options.verbose:
1895         FORMAT = "Debug: %(asctime)-15s %(message)s"
1896         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1897     elif options.quiet:
1898         FORMAT = "Critical: %(message)s"
1899         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1900     else:
1901         FORMAT = "%(message)s"
1902         logging.basicConfig(level=logging.INFO, format=FORMAT)
1903
1904
1905 def handle_bootloader(device):
1906     """wrapper for installing bootloader
1907
1908     @device: device where bootloader should be installed to"""
1909
1910     # Install bootloader only if not using the --copy-only option
1911     if options.copyonly:
1912         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1913     elif os.path.isdir(device):
1914         logging.info("Not installing bootloader as %s is a directory.", device)
1915     else:
1916         install_bootloader(device)
1917
1918
1919 def main():
1920     """Main function [make pylint happy :)]"""
1921
1922     if options.version:
1923         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1924         sys.exit(0)
1925
1926     if len(args) < 2:
1927         parser.error("invalid usage")
1928
1929     # log handling
1930     handle_logging()
1931
1932     # make sure we have the appropriate permissions
1933     check_uid_root()
1934
1935     logging.info("Executing grml2usb version %s", PROG_VERSION)
1936
1937     if options.dryrun:
1938         logging.info("Running in simulation mode as requested via option dry-run.")
1939
1940     if options.grubmbr and not options.grub:
1941         logging.critical("Error: --grub-mbr requires --grub option.")
1942         sys.exit(1)
1943
1944     # specified arguments
1945     device = args[len(args) - 1]
1946     isos = args[0:len(args) - 1]
1947
1948     if not os.path.isdir(device):
1949         if device[-1:].isdigit():
1950             if int(device[-1:]) > 4 or device[-2:].isdigit():
1951                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1952                 sys.exit(1)
1953         else:
1954             if os.path.exists(device):
1955                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1956                 sys.exit(1)
1957
1958     if not which("rsync"):
1959         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1960         sys.exit(1)
1961
1962     # provide upgrade path
1963     handle_compat_warning(device)
1964
1965     # check for vfat partition
1966     handle_vfat(device)
1967
1968     # main operation (like installing files)
1969     for iso in isos:
1970         if os.path.isdir(iso):
1971             handle_dir(iso, device)
1972         else:
1973             handle_iso(iso, device)
1974
1975     # install mbr
1976     if not os.path.isdir(device):
1977         if not options.skipmbr:
1978             handle_mbr(device)
1979
1980     handle_bootloader(device)
1981
1982     logging.info("Note: grml flavour %s was installed as the default booting system.", GRML_DEFAULT)
1983
1984     for flavour in GRML_FLAVOURS:
1985         logging.info("Note: you can boot flavour %s using '%s' on the commandline.", flavour, flavour)
1986
1987     # finally be politely :)
1988     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system.", PROG_VERSION)
1989
1990
1991 if __name__ == "__main__":
1992     try:
1993         main()
1994     except KeyboardInterrupt:
1995         logging.info("Received KeyboardInterrupt")
1996         cleanup()
1997
1998 ## END OF FILE #################################################################
1999 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8