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