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