Switch order of usb <-> path check
[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, therefore not mounting." % 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 therefore.")
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:
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     if os.path.isdir(device):
1348         install_grub1_partition = None
1349     else:
1350         if device[-1:].isdigit():
1351             install_grub1_partition = int(device[-1:]) - 1
1352         else:
1353             raise CriticalException("error validating partition schema (raw device?)")
1354
1355     # do NOT write "None" in kernel cmdline
1356     if options.bootoptions is None:
1357         bootopt = ""
1358     else:
1359         bootopt = options.bootoptions
1360
1361     # write menu.lst
1362     if install_grub1_partition:
1363         handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1364     # write grub.cfg
1365     handle_grub2_config(grml_flavour, grub_target, bootopt)
1366
1367
1368 def handle_syslinux_config(grml_flavour, target):
1369     """Main handler for generating syslinux configuration
1370
1371     @grml_flavour: name of grml flavour the configuration should be generated for
1372     @target: path of syslinux's configuration files"""
1373
1374     # do NOT write "None" in kernel cmdline
1375     if options.bootoptions is None:
1376         bootopt = ""
1377     else:
1378         bootopt = options.bootoptions
1379
1380     logging.debug("Generating syslinux configuration")
1381     syslinux_target = target + '/boot/syslinux/'
1382     # should be present via  copy_bootloader_files(), but make sure it exits:
1383     execute(mkdir, syslinux_target)
1384     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1385
1386     global GRML_DEFAULT
1387
1388     # install main configuration only *once*, no matter how many ISOs we have:
1389     syslinux_flavour_is_default = False
1390     if os.path.isfile(syslinux_cfg):
1391         string = open(syslinux_cfg).readline()
1392         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1393         if not re.match(main_identifier, string):
1394             syslinux_config_file = open(syslinux_cfg, 'w')
1395             GRML_DEFAULT = grml_flavour
1396             syslinux_flavour_is_default = True
1397             syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1398             syslinux_config_file.close()
1399     else:
1400         syslinux_config_file = open(syslinux_cfg, 'w')
1401         GRML_DEFAULT = grml_flavour
1402         syslinux_flavour_is_default = True
1403         syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1404         syslinux_config_file.close()
1405
1406     # install flavour specific configuration only *once* as well
1407     syslinux_flavour_config = True
1408     if os.path.isfile(syslinux_cfg):
1409         string = open(syslinux_cfg).readlines()
1410         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1411         for line in string:
1412             if flavour.match(line):
1413                 syslinux_flavour_config = False
1414
1415     if syslinux_flavour_config:
1416         syslinux_config_file = open(syslinux_cfg, 'a')
1417         # display only if the grml flavour isn't the default
1418         if not syslinux_flavour_is_default:
1419             GRML_FLAVOURS.add(grml_flavour)
1420         syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
1421         syslinux_config_file.close()
1422
1423     logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
1424     isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
1425     isolinux_splash.write(generate_isolinux_splash(grml_flavour))
1426     isolinux_splash.close()
1427
1428
1429 def handle_bootloader_config(grml_flavour, device, target):
1430     """Main handler for generating bootloader's configuration
1431
1432     @grml_flavour: name of grml flavour the configuration should be generated for
1433     @device: device/partition where bootloader should be installed to
1434     @target: path of bootloader's configuration files"""
1435
1436     if options.skipsyslinuxconfig:
1437         logging.info("Skipping generation of syslinux configuration as requested.")
1438     else:
1439         try:
1440             handle_syslinux_config(grml_flavour, target)
1441         except CriticalException, error:
1442             logging.critical("Fatal: %s" % error)
1443             sys.exit(1)
1444
1445     if options.skipgrubconfig:
1446         logging.info("Skipping generation of grub configuration as requested.")
1447     else:
1448         try:
1449             handle_grub_config(grml_flavour, device, target)
1450         except CriticalException, error:
1451             logging.critical("Fatal: %s" % error)
1452             sys.exit(1)
1453
1454 def handle_dir(live_image, device):
1455     """Main logic for copying files of the currently running grml system.
1456
1457     @live_image: directory where currently running live system resides (usually /live/image)
1458     @device: partition where the specified ISO should be installed to"""
1459
1460     logging.info("Using %s as install base" % live_image)
1461
1462     if os.path.isdir(device):
1463         logging.info("Specified target is a directory, therefore not mounting.")
1464         device_mountpoint = device
1465         remove_device_mountpoint = False
1466     else:
1467         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1468         register_tmpfile(device_mountpoint)
1469         remove_device_mountpoint = True
1470         try:
1471             mount(device, device_mountpoint, "")
1472         except CriticalException, error:
1473             logging.critical("Fatal: %s" % error)
1474             cleanup()
1475             sys.exit(1)
1476
1477     try:
1478         grml_flavour = identify_grml_flavour(live_image)
1479         logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1480         install_iso_files(grml_flavour, live_image, device, device_mountpoint)
1481     except TypeError:
1482         logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1483         sys.exit(1)
1484     finally:
1485         if remove_device_mountpoint:
1486             try:
1487                 unmount(device_mountpoint, "")
1488                 if os.path.isdir(device_mountpoint):
1489                     os.rmdir(device_mountpoint)
1490                     unregister_tmpfile(device_mountpoint)
1491             except CriticalException, error:
1492                 logging.critical("Fatal: %s" % error)
1493                 cleanup()
1494
1495
1496 def handle_iso(iso, device):
1497     """Main logic for mounting ISOs and copying files.
1498
1499     @iso: full path to the ISO that should be installed to the specified device
1500     @device: partition where the specified ISO should be installed to"""
1501
1502     logging.info("Using ISO %s" % iso)
1503
1504     iso_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1505     register_tmpfile(iso_mountpoint)
1506     remove_iso_mountpoint = True
1507
1508     if not os.path.isfile(iso):
1509         logging.critical("Fatal: specified ISO %s could not be read" % iso)
1510         cleanup()
1511         sys.exit(1)
1512
1513     try:
1514         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1515     except CriticalException, error:
1516         logging.critical("Fatal: %s" % error)
1517         sys.exit(1)
1518
1519     if os.path.isdir(device):
1520         logging.info("Specified target is a directory, therefore not mounting.")
1521         device_mountpoint = device
1522         remove_device_mountpoint = False
1523         # skip_mbr = True
1524     else:
1525         device_mountpoint = tempfile.mkdtemp(prefix="grml2usb")
1526         register_tmpfile(device_mountpoint)
1527         remove_device_mountpoint = True
1528         try:
1529             mount(device, device_mountpoint, "")
1530         except CriticalException, error:
1531             logging.critical("Fatal: %s" % error)
1532             cleanup()
1533             sys.exit(1)
1534
1535     try:
1536         grml_flavour = identify_grml_flavour(iso_mountpoint)
1537         logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1538         install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1539     except TypeError:
1540         logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1541         sys.exit(1)
1542     finally:
1543         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1544             unmount(iso_mountpoint, "")
1545             os.rmdir(iso_mountpoint)
1546             unregister_tmpfile(iso_mountpoint)
1547         if remove_device_mountpoint:
1548             try:
1549                 unmount(device_mountpoint, "")
1550                 if os.path.isdir(device_mountpoint):
1551                     os.rmdir(device_mountpoint)
1552                     unregister_tmpfile(device_mountpoint)
1553             except CriticalException, error:
1554                 logging.critical("Fatal: %s" % error)
1555                 cleanup()
1556
1557
1558 def handle_mbr(device):
1559     """Main handler for installing master boot record (MBR)
1560
1561     @device: device where the MBR should be installed to"""
1562
1563     if options.dryrun:
1564         logging.info("Would install MBR")
1565         return 0
1566
1567     # make sure we have syslinux available
1568     if not which("syslinux") and not options.copyonly:
1569         logging.critical('Sorry, syslinux not available. Exiting.')
1570         logging.critical('Please install syslinux or consider using the --grub option.')
1571         sys.exit(1)
1572
1573     if device[-1:].isdigit():
1574         mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1575         partition_number = int(device[-1:]) - 1
1576         skip_install_mir_mbr = False
1577
1578     # if we get e.g. /dev/loop1 as device we don't want to put the MBR
1579     # into /dev/loop of course, therefore use /dev/loop1 as mbr_device
1580     if mbr_device == "/dev/loop":
1581         mbr_device = device
1582         logging.info("Detected loop device - using %s as MBR device therefore" % mbr_device)
1583         skip_install_mir_mbr = True
1584
1585     try:
1586         if options.syslinuxmbr:
1587             handle_syslinux_mbr(mbr_device)
1588         elif not skip_install_mir_mbr:
1589             if options.mbrmenu:
1590                 install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True)
1591             else:
1592                 install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, False)
1593     except IOError, error:
1594         logging.critical("Execution failed: %s", error)
1595         sys.exit(1)
1596     except Exception, error:
1597         logging.critical("Execution failed: %s", error)
1598         sys.exit(1)
1599
1600
1601 def handle_vfat(device):
1602     """Check for FAT specific settings and options
1603
1604     @device: device that should checked / formated"""
1605
1606     # make sure we have mkfs.vfat available
1607     if options.fat16 and not options.force:
1608         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1609             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1610             logging.critical('Please make sure to install dosfstools.')
1611             sys.exit(1)
1612
1613         # make sure the user is aware of what he is doing
1614         f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1615         if f == "y" or f == "Y":
1616             logging.info("Note: you can skip this question using the option --force")
1617             try:
1618                 mkfs_fat16(device)
1619             except CriticalException, error:
1620                 logging.critical("Execution failed: %s", error)
1621                 sys.exit(1)
1622         else:
1623             sys.exit(1)
1624
1625     # check for vfat filesystem
1626     if device is not None and not os.path.isdir(device):
1627         try:
1628             check_for_fat(device)
1629         except CriticalException, error:
1630             logging.critical("Execution failed: %s", error)
1631             sys.exit(1)
1632
1633     if not os.path.isdir(device) and not check_for_usbdevice(device):
1634         print "Warning: the specified device %s does not look like a removable usb device." % device
1635         f = raw_input("Do you really want to continue? y/N ")
1636         if f == "y" or f == "Y":
1637             pass
1638         else:
1639             sys.exit(1)
1640
1641
1642 def handle_compat_warning(device):
1643     """Backwards compatible checks
1644
1645     @device: device that should be checked"""
1646
1647     # make sure we can replace old grml2usb script and warn user when using old way of life:
1648     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1649         print "Warning: the semantics of grml2usb has changed."
1650         print "Instead of using grml2usb /path/to/iso %s you might" % device
1651         print "want to use grml2usb /path/to/iso /dev/... instead."
1652         print "Please check out the grml2usb manpage for details."
1653         f = raw_input("Do you really want to continue? y/N ")
1654         if f == "y" or f == "Y":
1655             pass
1656         else:
1657             sys.exit(1)
1658
1659
1660 def handle_logging():
1661     """Log handling and configuration"""
1662
1663     if options.verbose and options.quiet:
1664         parser.error("please use either verbose (--verbose) or quiet (--quiet) option")
1665
1666     if options.verbose:
1667         FORMAT = "Debug: %(asctime)-15s %(message)s"
1668         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1669     elif options.quiet:
1670         FORMAT = "Critical: %(message)s"
1671         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1672     else:
1673         FORMAT = "%(message)s"
1674         logging.basicConfig(level=logging.INFO, format=FORMAT)
1675
1676
1677 def handle_bootloader(device):
1678     """wrapper for installing bootloader
1679
1680     @device: device where bootloader should be installed to"""
1681
1682     # Install bootloader only if not using the --copy-only option
1683     if options.copyonly:
1684         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1685     elif os.path.isdir(device):
1686         logging.info("Not installing bootloader as %s is a directory." % device)
1687     else:
1688         install_bootloader(device)
1689
1690
1691 def main():
1692     """Main function [make pylint happy :)]"""
1693
1694     if options.version:
1695         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1696         sys.exit(0)
1697
1698     if len(args) < 2:
1699         parser.error("invalid usage")
1700
1701     # log handling
1702     handle_logging()
1703
1704     # make sure we have the appropriate permissions
1705     check_uid_root()
1706
1707     logging.info("Executing grml2usb version %s", PROG_VERSION)
1708
1709     if options.dryrun:
1710         logging.info("Running in simulation mode as requested via option dry-run.")
1711
1712     # specified arguments
1713     device = args[len(args) - 1]
1714     isos = args[0:len(args) - 1]
1715
1716     if not os.path.isdir(device):
1717         if device[-1:].isdigit():
1718             if int(device[-1:]) > 4 or device[-2:].isdigit():
1719                 logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1720                 sys.exit(1)
1721         else:
1722             if os.path.exists(device):
1723                 logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1724                 sys.exit(1)
1725
1726     if not which("rsync"):
1727         logging.critical("Fatal: rsync not available, can not continue - sorry.")
1728         sys.exit(1)
1729
1730     # provide upgrade path
1731     handle_compat_warning(device)
1732
1733     # check for vfat partition
1734     handle_vfat(device)
1735
1736     # main operation (like installing files)
1737     for iso in isos:
1738         if os.path.isdir(iso):
1739             handle_dir(iso, device)
1740         else:
1741             handle_iso(iso, device)
1742
1743     # install mbr
1744     if not os.path.isdir(device):
1745         if not options.skipmbr:
1746             handle_mbr(device)
1747
1748     handle_bootloader(device)
1749
1750     logging.info("Note: grml flavour %s was installed as the default booting system." % GRML_DEFAULT)
1751
1752     for flavour in GRML_FLAVOURS:
1753         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (flavour, flavour))
1754
1755     # finally be politely :)
1756     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
1757
1758
1759 if __name__ == "__main__":
1760     try:
1761         main()
1762     except KeyboardInterrupt:
1763         logging.info("Received KeyboardInterrupt")
1764         cleanup()
1765
1766 ## END OF FILE #################################################################
1767 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8