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