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