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