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