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