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