Update bootsplash for grub
[grml2usb.git] / grml2usb
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 grml2usb
5 ~~~~~~~~
6
7 This script installs a grml system (either a running system or ISO[s]) to a USB device
8
9 :copyright: (c) 2009 by Michael Prokop <mika@grml.org>
10 :license: GPL v2 or any later version
11 :bugreports: http://grml.org/bugs/
12
13 """
14
15 from __future__ import with_statement
16 from optparse import OptionParser
17 from inspect import isroutine, isclass
18 import datetime, logging, os, re, subprocess, sys, tempfile, time
19
20 # global variables
21 PROG_VERSION = "0.9.2(pre2)"
22 MOUNTED = set()  # register mountpoints
23 TMPFILES = set() # register tmpfiles
24 DATESTAMP = time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
25
26 # cmdline parsing
27 USAGE = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/sdX#>\n\
28 \n\
29 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
30 Make sure you have at least one grml ISO or a running grml system (/live/image),\n\
31 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
32 and root access. Further information can be found in: man grml2usb"
33
34 # pylint: disable-msg=C0103
35 parser = OptionParser(usage=USAGE)
36 parser.add_option("--bootoptions", dest="bootoptions",
37                   action="store", type="string",
38                   help="use specified bootoptions as default")
39 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
40                   help="do not copy files but just install a bootloader")
41 parser.add_option("--copy-only", dest="copyonly", action="store_true",
42                   help="copy files only but do not install bootloader")
43 parser.add_option("--dry-run", dest="dryrun", action="store_true",
44                   help="avoid executing commands")
45 parser.add_option("--fat16", dest="fat16", action="store_true",
46                   help="format specified partition with FAT16")
47 parser.add_option("--force", dest="force", action="store_true",
48                   help="force any actions requiring manual interaction")
49 parser.add_option("--grub", dest="grub", action="store_true",
50                   help="install grub bootloader instead of syslinux")
51 parser.add_option("--initrd", dest="initrd", action="store", type="string",
52                   help="install specified initrd instead of the default [TODO]")
53 parser.add_option("--kernel", dest="kernel", action="store", type="string",
54                   help="install specified kernel instead of the default [TODO]")
55 parser.add_option("--lilo", dest="lilo",  action="store", type="string",
56                   help="lilo executable to be used for installing MBR")
57 parser.add_option("--mbr-manager", dest="mbrmgr", action="store_true",
58                   help="enable boot manager menu in MBR")
59 parser.add_option("--quiet", dest="quiet", action="store_true",
60                   help="do not output anything but just errors on console")
61 parser.add_option("--skip-addons", dest="skipaddons", action="store_true",
62                   help="do not install /boot/addons/ files")
63 parser.add_option("--skip-mbr", dest="skipmbr", action="store_true",
64                   help="do not install a master boot record (MBR) on the device")
65 parser.add_option("--syslinux-mbr", dest="syslinuxmbr", action="store_true",
66                   help="install syslinux master boot record (MBR) instead of default")
67 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
68                   help="install specified squashfs file instead of the default [TODO]")
69 parser.add_option("--uninstall", dest="uninstall", action="store_true",
70                   help="remove grml ISO files from specified device [TODO]")
71 parser.add_option("--verbose", dest="verbose", action="store_true",
72                   help="enable verbose mode")
73 parser.add_option("-v", "--version", dest="version", action="store_true",
74                   help="display version and exit")
75 (options, args) = parser.parse_args()
76
77
78 class CriticalException(Exception):
79     """Throw critical exception if the exact error is not known but fatal."
80
81     @Exception: message"""
82     pass
83
84
85 def cleanup():
86     """Cleanup function to make sure there aren't any mounted devices left behind.
87     """
88
89     logging.info("Cleaning up before exiting...")
90     proc = subprocess.Popen(["sync"])
91     proc.wait()
92
93     try:
94         for device in MOUNTED:
95             unmount(device, "")
96     # ignore: RuntimeError: Set changed size during iteration
97     except RuntimeError:
98         logging.debug('caught expection RuntimeError, ignoring')
99
100
101 def register_tmpfile(path):
102     """TODO
103     """
104
105     TMPFILES.add(path)
106
107
108 def unregister_tmpfile(path):
109     """TODO
110     """
111
112     if path in TMPFILES:
113         TMPFILES.remove(path)
114
115
116 def register_mountpoint(target):
117     """TODO
118     """
119
120     MOUNTED.add(target)
121
122
123 def unregister_mountpoint(target):
124     """TODO
125     """
126
127     if target in MOUNTED:
128         MOUNTED.remove(target)
129
130
131 def get_function_name(obj):
132     """Helper function for use in execute() to retrive name of a function
133
134     @obj: the function object
135     """
136     if not (isroutine(obj) or isclass(obj)):
137         obj = type(obj)
138     return obj.__module__ + '.' + obj.__name__
139
140
141 def execute(f, *exec_arguments):
142     """Wrapper for executing a command. Either really executes
143     the command (default) or when using --dry-run commandline option
144     just displays what would be executed."""
145     # usage: execute(subprocess.Popen, (["ls", "-la"]))
146     # TODO: doesn't work for proc = execute(subprocess.Popen...() -> any ideas?
147     if options.dryrun:
148         # pylint: disable-msg=W0141
149         logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, exec_arguments))))
150     else:
151         # pylint: disable-msg=W0142
152         return f(*exec_arguments)
153
154
155 def is_exe(fpath):
156     """Check whether a given file can be executed
157
158     @fpath: full path to file
159     @return:"""
160     return os.path.exists(fpath) and os.access(fpath, os.X_OK)
161
162
163 def which(program):
164     """Check whether a given program is available in PATH
165
166     @program: name of executable"""
167     fpath = os.path.split(program)[0]
168     if fpath:
169         if is_exe(program):
170             return program
171     else:
172         for path in os.environ["PATH"].split(os.pathsep):
173             exe_file = os.path.join(path, program)
174             if is_exe(exe_file):
175                 return exe_file
176
177     return None
178
179
180 def search_file(filename, search_path='/bin' + os.pathsep + '/usr/bin'):
181     """Given a search path, find file
182
183     @filename: name of file to search for
184     @search_path: path where searching for the specified filename"""
185     file_found = 0
186     paths = search_path.split(os.pathsep)
187     current_dir = '' # make pylint happy :)
188     for path in paths:
189         # pylint: disable-msg=W0612
190         for current_dir, directories, files in os.walk(path):
191             if os.path.exists(os.path.join(current_dir, filename)):
192                 file_found = 1
193                 break
194     if file_found:
195         return os.path.abspath(os.path.join(current_dir, filename))
196     else:
197         return None
198
199
200 def check_uid_root():
201     """Check for root permissions"""
202     if not os.geteuid()==0:
203         sys.exit("Error: please run this script with uid 0 (root).")
204
205
206 def mkfs_fat16(device):
207     """Format specified device with VFAT/FAT16 filesystem.
208
209     @device: partition that should be formated"""
210
211     # syslinux -d boot/isolinux /dev/sdb1
212     logging.info("Formating partition with fat16 filesystem")
213     logging.debug("mkfs.vfat -F 16 %s" % device)
214     proc = subprocess.Popen(["mkfs.vfat", "-F", "16", device])
215     proc.wait()
216     if proc.returncode != 0:
217         raise CriticalException("error executing mkfs.vfat")
218
219
220 def generate_main_grub2_config(grml_flavour, install_partition, bootoptions):
221     """Generate grub2 configuration for use via grub.cfg
222
223     TODO
224
225     @grml_flavour: name of grml flavour the configuration should be generated for"""
226
227     local_datestamp = DATESTAMP
228
229     return("""\
230 ## main grub2 configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
231 set default=0
232 set timeout=5
233
234 insmod fat
235
236 if font (hd0,%(install_partition)s)/boot/grub/ascii.pff ; then
237    insmod png
238    set gfxmode=640x480
239    insmod gfxterm
240    insmod vbe
241    terminal gfxterm
242 fi
243
244 if background_image (hd0,%(install_partition)s)/boot/grub/grml.png ; then
245   set color_normal=black/black
246   set color_highlight=red/black
247 else
248   set menu_color_normal=white/black
249   set menu_color_highlight=black/yellow
250 fi
251
252 menuentry "%(grml_flavour)s (default)" {
253     set root=(hd0,%(install_partition)s)
254     linux   /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s %(bootoptions)s
255     initrd  /boot/release/%(grml_flavour)s/initrd.gz
256 }
257
258 menuentry "Memory test (memtest86+)" {
259     set root=(hd0,%(install_partition)s)
260     linux   /boot/addons/memtest
261 }
262
263 menuentry "Grub - all in one image" {
264     set root=(hd0,%(install_partition)s)
265     linux   /boot/addons/memdisk
266     initrd  /boot/addons/allinone.img
267 }
268
269 menuentry "FreeDOS" {
270     set root=(hd0,%(install_partition)s)
271     linux   /boot/addons/memdisk
272     initrd  /boot/addons/balder10.imz
273 }
274
275 menuentry "Boot OS of first partition on first disk" {
276     set root=(hd0,1)
277     chainloader +1
278 }
279
280 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } )
281
282
283 def generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootoptions):
284     """Generate grub2 configuration for use via grub.cfg
285
286     TODO
287
288     @grml_flavour: name of grml flavour the configuration should be generated for"""
289
290     local_datestamp = DATESTAMP
291
292     return("""\
293 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
294 menuentry "%(grml_flavour)s" {
295     set root=(hd0,%(install_partition)s)
296     linux  /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s
297     initrd /boot/release/%(grml_flavour)s/initrd.gz
298 }
299
300 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
301 menuentry "%(grml_flavour)s2ram" {
302     set root=(hd0,%(install_partition)s)
303     linux  /boot/release/%(grml_flavour)s/linux26 apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s toram=%(grml_flavour)s %(bootoptions)s
304     initrd /boot/release/%(grml_flavour)s/initrd.gz
305 }
306
307 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
308 menuentry "%(grml_flavour)s-debug" {
309     set root=(hd0,%(install_partition)s)
310     linux /boot/release/%(grml_flavour)s/linux26
311     initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s debug boot=live initcall_debug%(bootoptions)s
312 }
313
314 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
315 menuentry "%(grml_flavour)s-x" {
316     set root=(hd0,%(install_partition)s)
317     linux  /boot/release/%(grml_flavour)s/linux26
318     initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s startx=wm-ng %(bootoptions)s
319 }
320
321 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
322 menuentry "%(grml_flavour)s-nofb" {
323     set root=(hd0,%(install_partition)s)
324     linux  /boot/release/%(grml_flavour)s/linux26
325     initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=ofonly %(bootoptions)s
326 }
327
328 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
329 menuentry "%(grml_flavour)s-failsafe" {
330     set root=(hd0,%(install_partition)s)
331     linux /boot/release/%(grml_flavour)s/linux26
332     initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal lang=us boot=live noautoconfig atapicd noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia maxcpus=1 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm %(bootoptions)s
333 }
334
335 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
336 menuentry "%(grml_flavour)s-forensic" {
337     set root=(hd0,%(install_partition)s)
338     linux /boot/release/%(grml_flavour)s/linux26
339     initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s nofstab noraid nolvm noautoconfig noswap raid=noautodetect %(bootoptions)s
340 }
341
342 ## flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
343 menuentry "%(grml_flavour)s-serial" {
344     set root=(hd0,%(install_partition)s)
345     linux /boot/release/%(grml_flavour)s/linux26
346     initrd /boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=vesafb:off console=tty1 console=ttyS0,9600n8 %(bootoptions)s
347 }
348
349 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } )
350
351
352 def generate_grub1_config(grml_flavour, install_partition, bootoptions):
353     """Generate grub1 configuration for use via menu.lst
354
355     @grml_flavour: name of grml flavour the configuration should be generated for"""
356
357     local_datestamp = DATESTAMP
358
359     return("""\
360 # misc options:
361 timeout 30
362 # color red/blue green/black
363 splashimage=/boot/grub/splash.xpm.gz
364 foreground  = 000000
365 background  = FFCC33
366
367 # root=(hd0,%(install_partition)s)
368
369 # define entries:
370 title %(grml_flavour)s  - Default boot (using 1024x768 framebuffer)
371 kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s
372 initrd /boot/release/%(grml_flavour)s/initrd.gz
373
374 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions, 'install_partition': install_partition } )
375
376
377 def generate_isolinux_splash(grml_flavour):
378     """Generate bootsplash for isolinux/syslinux
379
380     @grml_flavour: name of grml flavour the configuration should be generated for"""
381
382     # TODO: adjust last bootsplash line (the one following the "Some information and boot ...")
383
384     grml_name = grml_flavour
385
386     return("""\
387 \ f17\f\18/boot/syslinux/logo.16
388
389 Some information and boot options available via keys F2 - F10. http://grml.org/
390 %(grml_name)s
391 """ % {'grml_name': grml_name} )
392
393
394 def generate_main_syslinux_config(grml_flavour, bootoptions):
395     """Generate main configuration for use in syslinux.cfg
396
397     @grml_flavour: name of grml flavour the configuration should be generated for
398     @bootoptions: bootoptions that should be used as a default"""
399
400     local_datestamp = DATESTAMP
401
402     return("""\
403 ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
404 # use this to control the bootup via a serial port
405 # SERIAL 0 9600
406 DEFAULT grml
407 TIMEOUT 300
408 PROMPT 1
409 DISPLAY /boot/syslinux/boot.msg
410 F1 /boot/syslinux/boot.msg
411 F2 /boot/syslinux/f2
412 F3 /boot/syslinux/f3
413 F4 /boot/syslinux/f4
414 F5 /boot/syslinux/f5
415 F6 /boot/syslinux/f6
416 F7 /boot/syslinux/f7
417 F8 /boot/syslinux/f8
418 F9 /boot/syslinux/f9
419 F10 /boot/syslinux/f10
420 ## end of main configuration
421
422 ## global configuration
423 # the default option (using %(grml_flavour)s)
424 LABEL  grml
425 KERNEL /boot/release/%(grml_flavour)s/linux26
426 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s
427
428 # memtest
429 LABEL  memtest
430 KERNEL /boot/addons/memtest
431
432 # grub
433 LABEL grub
434 MENU LABEL grub
435 KERNEL /boot/addons/memdisk
436 APPEND initrd=/boot/addons/allinone.img
437
438 # dos
439 LABEL dos
440 MENU LABEL dos
441 KERNEL /boot/addons/memdisk
442 APPEND initrd=/boot/addons/balder10.imz
443
444 ## end of global configuration
445 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
446
447
448 def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
449     """Generate flavour specific configuration for use in syslinux.cfg
450
451     @grml_flavour: name of grml flavour the configuration should be generated for
452     @bootoptions: bootoptions that should be used as a default"""
453
454     local_datestamp = DATESTAMP
455
456     return("""\
457
458 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
459 LABEL  %(grml_flavour)s
460 KERNEL /boot/release/%(grml_flavour)s/linux26
461 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s %(bootoptions)s
462
463 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
464 LABEL  %(grml_flavour)s2ram
465 KERNEL /boot/release/%(grml_flavour)s/linux26
466 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s toram=%(grml_flavour)s %(bootoptions)s
467
468 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
469 LABEL  %(grml_flavour)s-debug
470 KERNEL /boot/release/%(grml_flavour)s/linux26
471 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s debug boot=live initcall_debug%(bootoptions)s
472
473 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
474 LABEL  %(grml_flavour)s-x
475 KERNEL /boot/release/%(grml_flavour)s/linux26
476 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s startx=wm-ng %(bootoptions)s
477
478 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
479 LABEL  %(grml_flavour)s-nofb
480 KERNEL /boot/release/%(grml_flavour)s/linux26
481 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=ofonly %(bootoptions)s
482
483 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
484 LABEL  %(grml_flavour)s-failsafe
485 KERNEL /boot/release/%(grml_flavour)s/linux26
486 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal lang=us boot=live noautoconfig atapicd noacpi acpi=off nomodules nofirewire noudev nousb nohotplug noapm nopcmcia maxcpus=1 noscsi noagp nodma ide=nodma noswap nofstab nosound nogpm nosyslog nodhcp nocpu nodisc nomodem xmodule=vesa noraid nolvm %(bootoptions)s
487
488 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
489 LABEL  %(grml_flavour)s-forensic
490 KERNEL /boot/release/%(grml_flavour)s/linux26
491 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce vga=791 quiet module=%(grml_flavour)s nofstab noraid nolvm noautoconfig noswap raid=noautodetect %(bootoptions)s
492
493 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
494 LABEL  %(grml_flavour)s-serial
495 KERNEL /boot/release/%(grml_flavour)s/linux26
496 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s vga=normal video=vesafb:off console=tty1 console=ttyS0,9600n8 %(bootoptions)s
497 """ % {'grml_flavour': grml_flavour, 'local_datestamp': local_datestamp, 'bootoptions': bootoptions} )
498
499
500 def install_grub(device):
501     """Install grub on specified device.
502
503     @mntpoint: mountpoint of device where grub should install its files to
504     @device: partition where grub should be installed to"""
505
506     if options.dryrun:
507         logging.info("Would execute grub-install [--root-directory=mount_point] %s now.", device)
508     else:
509         device_mountpoint = tempfile.mkdtemp()
510         register_tmpfile(device_mountpoint)
511         try:
512             mount(device, device_mountpoint, "")
513             logging.debug("grub-install --root-directory=%s %s", device_mountpoint, device)
514             proc = subprocess.Popen(["grub-install", "--root-directory=%s" % device_mountpoint, device], stdout=file(os.devnull, "r+"))
515             proc.wait()
516             if proc.returncode != 0:
517                 raise Exception("error executing grub-install")
518         except CriticalException, error:
519             logging.critical("Fatal: %s" % error)
520             cleanup()
521             sys.exit(1)
522
523         finally:
524             unmount(device_mountpoint, "")
525             os.rmdir(device_mountpoint)
526             unregister_tmpfile(device_mountpoint)
527
528
529 def install_syslinux(device):
530     """Install syslinux on specified device.
531
532     @device: partition where syslinux should be installed to"""
533
534     if options.dryrun:
535         logging.info("Would install syslinux as bootloader on %s", device)
536         return 0
537
538     # syslinux -d boot/isolinux /dev/sdb1
539     logging.info("Installing syslinux as bootloader")
540     logging.debug("syslinux -d boot/syslinux %s" % device)
541     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
542     proc.wait()
543     if proc.returncode != 0:
544         raise CriticalException("Error executing syslinux (either try --fat16 or --grub?)")
545
546
547 def install_bootloader(device):
548     """Install bootloader on specified device.
549
550     @device: partition where bootloader should be installed to"""
551
552     # Install bootloader on the device (/dev/sda),
553     # not on the partition itself (/dev/sda1)?
554     #if partition[-1:].isdigit():
555     #    device = re.match(r'(.*?)\d*$', partition).group(1)
556     #else:
557     #    device = partition
558
559     if options.grub:
560         install_grub(device)
561     else:
562         try:
563             install_syslinux(device)
564         except CriticalException, error:
565             logging.critical("Fatal: %s" % error)
566             cleanup()
567             sys.exit(1)
568
569
570 def install_lilo_mbr(lilo, device):
571     """TODO"""
572
573     # to support -A for extended partitions:
574     logging.info("Activating partitions in MBR via lilo")
575     logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
576     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
577     proc.wait()
578     if proc.returncode != 0:
579         raise Exception("error executing lilo")
580
581     # activate partition:
582     logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
583     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
584     proc.wait()
585     if proc.returncode != 0:
586         raise Exception("error executing lilo")
587
588
589 def install_syslinux_mbr(device):
590     """TODO"""
591
592     # lilo's mbr is broken, use the one from syslinux instead:
593     if not os.path.isfile("/usr/lib/syslinux/mbr.bin"):
594         raise Exception("/usr/lib/syslinux/mbr.bin can not be read")
595
596     logging.info("Installing syslinux MBR")
597     logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
598     try:
599         # TODO -> use Popen instead?
600         retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
601         if retcode < 0:
602             logging.critical("Error copying MBR to device (%s)" % retcode)
603     except OSError, error:
604         logging.critical("Execution failed:", error)
605
606
607 def install_mir_mbr(mbrtemplate, device, partition, ismirbsdmbr=True):
608     """Installs an MBR to a device.
609
610     Retrieve the partition table from "device", install an MBR from
611     the "mbrtemplate" file, set the "partition" (0..3) active, and
612     install the result back to "device".
613
614     "device" may be the name of a file assumed to be a hard disc
615     (or USB stick) image, or something like "/dev/sdb". "partition"
616     must be a number between 0 and 3, inclusive. "mbrtemplate" must
617     be a valid MBR file of at least 440 (439 if ismirbsdmbr) bytes.
618
619     If "ismirbsdmbr", the partitions' active flags are not changed.
620     Instead, the MBR's default value is set accordingly.
621     """
622
623     logging.info("Installing default MBR")
624
625     if not os.path.isfile(mbrtemplate):
626         logging.critical("Error: %s can not be read." % mbrtemplate)
627         raise CriticalException("Error installing MBR (either try --syslinux-mbr or install missing file?)")
628
629     if (partition < 0) or (partition > 3):
630         raise ValueError("partition must be between 0 and 3")
631
632     if ismirbsdmbr:
633         nmbrbytes = 439
634     else:
635         nmbrbytes = 440
636
637     tmpf = tempfile.NamedTemporaryFile()
638
639     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1" % (device, tmpf.name))
640     proc = subprocess.Popen(["dd", "if=%s" % device, "of=%s" % tmpf.name, "bs=512", "count=1"], stderr=file(os.devnull, "r+"))
641     proc.wait()
642     if proc.returncode != 0:
643         raise Exception("error executing dd (first run)")
644
645     logging.debug("executing: dd if=%s of=%s bs=%s count=1 conv=notrunc" % (mbrtemplate, tmpf.name, nmbrbytes))
646     proc = subprocess.Popen(["dd", "if=%s" % mbrtemplate, "of=%s" % tmpf.name, "bs=%s" % nmbrbytes, "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
647     proc.wait()
648     if proc.returncode != 0:
649         raise Exception("error executing dd (second run)")
650
651     mbrcode = tmpf.file.read(512)
652     if len(mbrcode) < 512:
653         raise EOFError("MBR size (%d) < 512" % len(mbrcode))
654
655     if ismirbsdmbr:
656         mbrcode = mbrcode[0:439] + chr(partition) + \
657           mbrcode[440:510] + "\x55\xAA"
658     else:
659         actives = ["\x00", "\x00", "\x00", "\x00"]
660         actives[partition] = "\x80"
661         mbrcode = mbrcode[0:446] + actives[0] + \
662           mbrcode[447:462] + actives[1] + \
663           mbrcode[463:478] + actives[2] + \
664           mbrcode[479:494] + actives[3] + \
665           mbrcode[495:510] + "\x55\xAA"
666
667     tmpf.file.seek(0)
668     tmpf.file.truncate()
669     tmpf.file.write(mbrcode)
670     tmpf.file.close()
671
672     logging.debug("executing: dd if='%s' of='%s' bs=512 count=1 conv=notrunc" % (tmpf.name, device))
673     proc = subprocess.Popen(["dd", "if=%s" % tmpf.name, "of=%s" % device, "bs=512", "count=1", "conv=notrunc"], stderr=file(os.devnull, "r+"))
674     proc.wait()
675     if proc.returncode != 0:
676         raise Exception("error executing dd (third run)")
677     del tmpf
678
679
680 def handle_syslinux_mbr(device):
681     """Install syslinux master boot record on given device
682
683     @device: device where MBR should be installed to"""
684
685     if not is_writeable(device):
686         raise IOError("device not writeable for user")
687
688     # try to use system's lilo
689     if which("lilo"):
690         lilo = which("lilo")
691     else:
692         # otherwise fall back to our static version
693         from platform import architecture
694         if architecture()[0] == '64bit':
695             lilo = '/usr/share/grml2usb/lilo/lilo.static.amd64'
696         else:
697             lilo = '/usr/share/grml2usb/lilo/lilo.static.i386'
698     # finally prefer a specified lilo executable
699     if options.lilo:
700         lilo = options.lilo
701
702     if not is_exe(lilo):
703         raise Exception("lilo executable can not be execute")
704
705     if options.dryrun:
706         logging.info("Would install MBR running lilo and using syslinux.")
707         return 0
708
709     install_lilo_mbr(lilo, device)
710     install_syslinux_mbr(device)
711
712
713 def is_writeable(device):
714     """Check if the device is writeable for the current user
715
716     @device: partition where bootloader should be installed to"""
717
718     if not device:
719         return False
720         #raise Exception("no device for checking write permissions")
721
722     if not os.path.exists(device):
723         return False
724
725     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
726
727
728 def mount(source, target, mount_options):
729     """Mount specified source on given target
730
731     @source: name of device/ISO that should be mounted
732     @target: directory where the ISO should be mounted to
733     @options: mount specific options"""
734
735     # note: options.dryrun does not work here, as we have to
736     # locate files and identify the grml flavour
737
738     for x in file('/proc/mounts').readlines():
739         if x.startswith(source):
740             raise CriticalException("Error executing mount: %s already mounted - please unmount before invoking grml2usb" % source)
741
742     logging.debug("mount %s %s %s" % (mount_options, source, target))
743     proc = subprocess.Popen(["mount"] + list(mount_options) + [source, target])
744     proc.wait()
745     if proc.returncode != 0:
746         raise CriticalException("Error executing mount")
747     else:
748         logging.debug("register_mountpoint(%s)" % target)
749         register_mountpoint(target)
750
751
752 def unmount(target, unmount_options):
753     """Unmount specified target
754
755     @target: target where something is mounted on and which should be unmounted
756     @options: options for umount command"""
757
758     # make sure we unmount only already mounted targets
759     target_unmount = False
760     mounts = open('/proc/mounts').readlines()
761     mountstring = re.compile(".*%s.*" % re.escape(target))
762     for line in mounts:
763         if re.match(mountstring, line):
764             target_unmount = True
765
766     if not target_unmount:
767         logging.debug("%s not mounted anymore" % target)
768     else:
769         logging.debug("umount %s %s" % (list(unmount_options), target))
770         proc = subprocess.Popen(["umount"] + list(unmount_options) + [target])
771         proc.wait()
772         if proc.returncode != 0:
773             raise Exception("Error executing umount")
774         else:
775             logging.debug("unregister_mountpoint(%s)" % target)
776             unregister_mountpoint(target)
777
778
779 def check_for_usbdevice(device):
780     """Check whether the specified device is a removable USB device
781
782     @device: device name, like /dev/sda1 or /dev/sda
783     """
784
785     usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
786     usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
787     if os.path.isfile(usbdevice):
788         is_usb = open(usbdevice).readline()
789         if is_usb == "1":
790             return 0
791         else:
792             return 1
793
794
795 def check_for_fat(partition):
796     """Check whether specified partition is a valid VFAT/FAT16 filesystem
797
798     @partition: device name of partition"""
799
800     try:
801         udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],
802                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
803         filesystem = udev_info.communicate()[0].rstrip()
804
805         if udev_info.returncode == 2:
806             raise CriticalException("Failed to read device %s"
807                                     " (wrong UID/permissions or device not present?)" % partition)
808
809         if filesystem != "vfat":
810             raise CriticalException("Partition %s does not contain a FAT16 filesystem. (Use --fat16 or run mkfs.vfat %s)" % (partition, partition))
811
812     except OSError:
813         raise CriticalException("Sorry, /lib/udev/vol_id not available.")
814
815
816 def mkdir(directory):
817     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
818
819     # just silently pass as it's just fine it the directory exists
820     if not os.path.isdir(directory):
821         try:
822             os.makedirs(directory)
823         # pylint: disable-msg=W0704
824         except OSError:
825             pass
826
827
828 def copy_system_files(grml_flavour, iso_mount, target):
829     """TODO"""
830
831     squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
832     if squashfs is None:
833         logging.critical("Fatal: squashfs file not found")
834     else:
835         squashfs_target = target + '/live/'
836         execute(mkdir, squashfs_target)
837         # use install(1) for now to make sure we can write the files afterwards as normal user as well
838         logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
839         proc = subprocess.Popen(["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
840         proc.wait()
841
842     filesystem_module = search_file('filesystem.module', iso_mount)
843     if filesystem_module is None:
844         logging.critical("Fatal: filesystem.module not found")
845     else:
846         logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
847         proc = subprocess.Popen(["install", "--mode=664", filesystem_module,
848                                 squashfs_target + grml_flavour + '.module'])
849         proc.wait()
850
851     release_target = target + '/boot/release/' + grml_flavour
852     execute(mkdir, release_target)
853
854     kernel = search_file('linux26', iso_mount)
855     if kernel is None:
856         logging.critical("Fatal kernel not found")
857     else:
858         logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
859         proc = subprocess.Popen(["install", "--mode=664", kernel, release_target + '/linux26'])
860         proc.wait()
861
862     initrd = search_file('initrd.gz', iso_mount)
863     if initrd is None:
864         logging.critical("Fatal: initrd not found")
865     else:
866         logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
867         proc = subprocess.Popen(["install", "--mode=664", initrd, release_target + '/initrd.gz'])
868         proc.wait()
869
870
871 def copy_grml_files(iso_mount, target):
872     """TODO"""
873
874     grml_target = target + '/grml/'
875     execute(mkdir, grml_target)
876
877     for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
878         grml_file = search_file(myfile, iso_mount)
879         if grml_file is None:
880             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
881         else:
882             logging.debug("cp %s %s" % (grml_file, grml_target + grml_file))
883             proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_target + myfile])
884             proc.wait()
885
886     grml_web_target = grml_target + '/web/'
887     execute(mkdir, grml_web_target)
888
889     for myfile in 'index.html', 'style.css':
890         grml_file = search_file(myfile, iso_mount)
891         if grml_file is None:
892             logging.warn("Warning: myfile %s could not be found - can not install it")
893         else:
894             logging.debug("cp %s %s" % (grml_file, grml_web_target + grml_file))
895             proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_web_target + myfile])
896             proc.wait()
897
898     grml_webimg_target = grml_web_target + '/images/'
899     execute(mkdir, grml_webimg_target)
900
901     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
902         grml_file = search_file(myfile, iso_mount)
903         if grml_file is None:
904             logging.warn("Warning: myfile %s could not be found - can not install it")
905         else:
906             logging.debug("cp %s %s" % (grml_file, grml_webimg_target + grml_file))
907             proc = subprocess.Popen(["install", "--mode=664", grml_file, grml_webimg_target + myfile])
908             proc.wait()
909
910
911 def copy_addons(iso_mount, target):
912     """TODO"""
913     addons = target + '/boot/addons/'
914     execute(mkdir, addons)
915
916     # grub all-in-one image
917     allinoneimg = search_file('allinone.img', iso_mount)
918     if allinoneimg is None:
919         logging.warn("Warning: allinone.img not found - can not install it")
920     else:
921         logging.debug("cp %s %s" % (allinoneimg, addons + '/allinone.img'))
922         proc = subprocess.Popen(["install", "--mode=664", allinoneimg, addons + 'allinone.img'])
923         proc.wait()
924
925     # freedos image
926     balderimg = search_file('balder10.imz', iso_mount)
927     if balderimg is None:
928         logging.warn("Warning: balder10.imz not found - can not install it")
929     else:
930         logging.debug("cp %s %s" % (balderimg, addons + '/balder10.imz'))
931         proc = subprocess.Popen(["install", "--mode=664", balderimg, addons + 'balder10.imz'])
932         proc.wait()
933
934     # memdisk image
935     memdiskimg = search_file('memdisk', iso_mount)
936     if memdiskimg is None:
937         logging.warn("Warning: memdisk not found - can not install it")
938     else:
939         logging.debug("cp %s %s" % (memdiskimg, addons + '/memdisk'))
940         proc = subprocess.Popen(["install", "--mode=664", memdiskimg, addons + 'memdisk'])
941         proc.wait()
942
943     # memtest86+ image
944     memtestimg = search_file('memtest', iso_mount)
945     if memtestimg is None:
946         logging.warn("Warning: memtest not found - can not install it")
947     else:
948         logging.debug("cp %s %s" % (memtestimg, addons + '/memtest'))
949         proc = subprocess.Popen(["install", "--mode=664", memtestimg, addons + 'memtest'])
950         proc.wait()
951
952
953 def copy_bootloader_files(iso_mount, target):
954     """"TODO"""
955
956     syslinux_target = target + '/boot/syslinux/'
957     execute(mkdir, syslinux_target)
958
959     logo = search_file('logo.16', iso_mount)
960     logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
961     proc = subprocess.Popen(["install", "--mode=664", logo, syslinux_target + 'logo.16'])
962     proc.wait()
963
964     for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
965         bootsplash = search_file(ffile, iso_mount)
966         logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
967         proc = subprocess.Popen(["install", "--mode=664", bootsplash, syslinux_target + ffile])
968         proc.wait()
969
970     grub_target = target + '/boot/grub/'
971     execute(mkdir, grub_target)
972
973     if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
974         logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
975         raise
976     else:
977         logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
978         proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/splash.xpm.gz',
979                                 grub_target + 'splash.xpm.gz'])
980         proc.wait()
981
982     # grml splash in grub
983     if os.path.isfile("/usr/share/grml2usb/grub/grml.png"):
984         logging.debug("cp /usr/share/grml2usb/grub/grml.png to %s" % grub_target + 'grml.png')
985         proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/grml.png',
986                                 grub_target + 'grml.png'])
987         proc.wait()
988
989     # font file for graphical bootsplash in grub
990     if os.path.isfile("/usr/share/grub/ascii.pff"):
991         logging.debug("cp /usr/share/grub/ascii.pff to %s" % grub_target + 'ascii.pff')
992         proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grub/ascii.pff',
993                                 grub_target + 'ascii.pff'])
994         proc.wait()
995
996     if not os.path.isfile("/usr/share/grml2usb/grub/stage2_eltorito"):
997         logging.critical("Error: /usr/share/grml2usb/grub/stage2_eltorito can not be read.")
998         raise
999     else:
1000         logging.debug("cp /usr/share/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito')
1001         proc = subprocess.Popen(["install", "--mode=664", '/usr/share/grml2usb/grub/stage2_eltorito',
1002                                 grub_target + 'stage2_eltorito'])
1003         proc.wait()
1004
1005
1006 def install_iso_files(grml_flavour, iso_mount, device, target):
1007     """Copy files from ISO on given target"""
1008
1009     # TODO
1010     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1011     # * provide alternative search_file() if file information is stored in a config.ini file?
1012     # * catch "install: .. No space left on device" & CO
1013
1014     if options.dryrun:
1015         logging.info("Would copy files to %s", iso_mount)
1016         return 0
1017     elif not options.bootloaderonly:
1018         logging.info("Copying files. This might take a while....")
1019         copy_system_files(grml_flavour, iso_mount, target)
1020         copy_grml_files(iso_mount, target)
1021
1022     if not options.skipaddons:
1023         copy_addons(iso_mount, target)
1024
1025     if not options.copyonly:
1026         copy_bootloader_files(iso_mount, target)
1027
1028         if not options.dryrun:
1029             handle_bootloader_config(grml_flavour, device, target)
1030
1031     # make sure we sync filesystems before returning
1032     proc = subprocess.Popen(["sync"])
1033     proc.wait()
1034
1035
1036 def uninstall_files(device):
1037     """Get rid of all grml files on specified device
1038
1039     @device: partition where grml2usb files should be removed from"""
1040
1041     # TODO
1042     logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
1043
1044
1045 def identify_grml_flavour(mountpath):
1046     """Get name of grml flavour
1047
1048     @mountpath: path where the grml ISO is mounted to
1049     @return: name of grml-flavour"""
1050
1051     version_file = search_file('grml-version', mountpath)
1052
1053     if version_file == "":
1054         logging.critical("Error: could not find grml-version file.")
1055         raise
1056
1057     try:
1058         tmpfile = open(version_file, 'r')
1059         grml_info = tmpfile.readline()
1060         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1061     except TypeError:
1062         raise
1063     except:
1064         logging.critical("Unexpected error:", sys.exc_info()[0])
1065         raise
1066
1067     return grml_flavour
1068
1069
1070 def handle_grub_config(grml_flavour, device, target):
1071     """ TODO """
1072
1073     logging.debug("Generating grub configuration")
1074     #with open("...", "w") as f:
1075     #f.write("bla bla bal")
1076
1077     grub_target = target + '/boot/grub/'
1078     # should be present via copy_bootloader_files(), but make sure it exists:
1079     execute(mkdir, grub_target)
1080     # we have to adjust root() inside grub configuration
1081     if device[-1:].isdigit():
1082         install_partition = device[-1:]
1083
1084     # do NOT write "None" in kernel cmdline
1085     if options.bootoptions is None:
1086         bootopt = ""
1087     else:
1088         bootopt = options.bootoptions
1089
1090     # grub1 config
1091     #logging.debug("Creating grub1 configuration file")
1092     #grub_config_file = open(grub_target + 'menu.lst', 'w')
1093     #grub_config_file.write(generate_grub1_config(grml_flavour, install_partition, bootopt))
1094     #grub_config_file.close()
1095     # TODO => generate_main_grub1_config() && generate_flavour_specific_grub1_config()
1096
1097     # grub2 config
1098     grub2_cfg = grub_target + 'grub.cfg'
1099     logging.debug("Creating grub2 configuration file")
1100
1101     # install main configuration only *once*, no matter how many ISOs we have:
1102     if os.path.isfile(grub2_cfg):
1103         string = open(grub2_cfg).readline()
1104         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1105         if not re.match(main_identifier, string):
1106             grub2_config_file = open(grub2_cfg, 'w')
1107             logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1108             grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1109             grub2_config_file.close()
1110     else:
1111         grub2_config_file = open(grub2_cfg, 'w')
1112         grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1113         grub2_config_file.close()
1114
1115     grub_flavour_config = True
1116     if os.path.isfile(grub2_cfg):
1117         string = open(grub2_cfg).readlines()
1118         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1119         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1120         for line in string:
1121             if flavour.match(line):
1122                 grub_flavour_config = False
1123
1124     if grub_flavour_config:
1125         grub2_config_file = open(grub2_cfg, 'a')
1126         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootopt))
1127         grub2_config_file.close( )
1128
1129
1130 def handle_syslinux_config(grml_flavour, target):
1131     """ TODO
1132     """
1133
1134     # do NOT write "None" in kernel cmdline
1135     if options.bootoptions is None:
1136         bootopt = ""
1137     else:
1138         bootopt = options.bootoptions
1139
1140     logging.info("Generating syslinux configuration")
1141     syslinux_target = target + '/boot/syslinux/'
1142     # should be present via  copy_bootloader_files(), but make sure it exits:
1143     execute(mkdir, syslinux_target)
1144     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1145
1146     # install main configuration only *once*, no matter how many ISOs we have:
1147     if os.path.isfile(syslinux_cfg):
1148         string = open(syslinux_cfg).readline()
1149         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1150         if not re.match(main_identifier, string):
1151             syslinux_config_file = open(syslinux_cfg, 'w')
1152             logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1153             syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1154             syslinux_config_file.close()
1155     else:
1156         syslinux_config_file = open(syslinux_cfg, 'w')
1157         syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1158         syslinux_config_file.close()
1159
1160     # install flavour specific configuration only *once* as well
1161     # kind of ugly - I'm pretty sure this could be smoother...
1162     syslinux_flavour_config = True
1163     if os.path.isfile(syslinux_cfg):
1164         string = open(syslinux_cfg).readlines()
1165         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1166         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1167         for line in string:
1168             if flavour.match(line):
1169                 syslinux_flavour_config = False
1170
1171     if syslinux_flavour_config:
1172         syslinux_config_file = open(syslinux_cfg, 'a')
1173         syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
1174         syslinux_config_file.close( )
1175
1176     logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
1177     isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
1178     isolinux_splash.write(generate_isolinux_splash(grml_flavour))
1179     isolinux_splash.close( )
1180
1181
1182 def handle_bootloader_config(grml_flavour, device, target):
1183     """TODO"""
1184
1185     if options.grub:
1186         handle_grub_config(grml_flavour, device, target)
1187     else:
1188         handle_syslinux_config(grml_flavour, target)
1189
1190
1191 def handle_iso(iso, device):
1192     """Main logic for mounting ISOs and copying files.
1193
1194     @iso: full path to the ISO that should be installed to the specified device
1195     @device: partition where the specified ISO should be installed to"""
1196
1197     logging.info("Using ISO %s" % iso)
1198
1199     if os.path.isdir(iso):
1200         logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
1201         sys.exit(1)
1202
1203     iso_mountpoint = tempfile.mkdtemp()
1204     register_tmpfile(iso_mountpoint)
1205     remove_iso_mountpoint = True
1206
1207     if not os.path.isfile(iso):
1208         logging.critical("Fatal: specified ISO %s could not be read" % iso)
1209         cleanup()
1210         sys.exit(1)
1211
1212     try:
1213         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1214     except CriticalException, error:
1215         logging.critical("Fatal: %s" % error)
1216         sys.exit(1)
1217
1218     if os.path.isdir(device):
1219         logging.info("Specified target is a directory, not mounting therefor.")
1220         device_mountpoint = device
1221         remove_device_mountpoint = False
1222         # skip_mbr = True
1223     else:
1224         device_mountpoint = tempfile.mkdtemp()
1225         register_tmpfile(device_mountpoint)
1226         remove_device_mountpoint = True
1227         try:
1228             mount(device, device_mountpoint, "")
1229         except CriticalException, error:
1230             logging.critical("Fatal: %s" % error)
1231             cleanup()
1232             sys.exit(1)
1233
1234     try:
1235         grml_flavour = identify_grml_flavour(iso_mountpoint)
1236         logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1237         install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1238     except TypeError:
1239         logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1240         sys.exit(1)
1241     finally:
1242         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1243             unmount(iso_mountpoint, "")
1244             os.rmdir(iso_mountpoint)
1245             unregister_tmpfile(iso_mountpoint)
1246         if remove_device_mountpoint:
1247             unmount(device_mountpoint, "")
1248             if os.path.isdir(device_mountpoint):
1249                 os.rmdir(device_mountpoint)
1250                 unregister_tmpfile(device_mountpoint)
1251
1252
1253 def handle_mbr(device):
1254     """TODO"""
1255
1256     # install MBR
1257     # if not options.mbr:
1258     #     logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
1259     # else:
1260     # make sure we install MBR on /dev/sdX and not /dev/sdX#
1261
1262     # make sure we have syslinux available
1263     # if options.mbr:
1264     if not options.skipmbr:
1265         if not which("syslinux") and not options.copyonly and not options.dryrun:
1266             logging.critical('Sorry, syslinux not available. Exiting.')
1267             logging.critical('Please install syslinux or consider using the --grub option.')
1268             sys.exit(1)
1269
1270     if not options.skipmbr:
1271         if device[-1:].isdigit():
1272             mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1273             partition_number = int(device[-1:]) - 1
1274
1275         try:
1276             if options.syslinuxmbr:
1277                 handle_syslinux_mbr(mbr_device)
1278             else:
1279                 if options.mbrmgr:
1280                     install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True)
1281                 else:
1282                     install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, True)
1283         except IOError, error:
1284             logging.critical("Execution failed: %s", error)
1285             sys.exit(1)
1286         except Exception, error:
1287             logging.critical("Execution failed: %s", error)
1288             sys.exit(1)
1289
1290
1291 def handle_vfat(device):
1292     """TODO"""
1293
1294     # make sure we have mkfs.vfat available
1295     if options.fat16 and not options.force:
1296         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1297             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1298             logging.critical('Please make sure to install dosfstools.')
1299             sys.exit(1)
1300
1301         # make sure the user is aware of what he is doing
1302         f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1303         if f == "y" or f == "Y":
1304             logging.info("Note: you can skip this question using the option --force")
1305             try:
1306                 mkfs_fat16(device)
1307             except CriticalException, error:
1308                 logging.critical("Execution failed: %s", error)
1309                 sys.exit(1)
1310         else:
1311             sys.exit(1)
1312
1313     # check for vfat filesystem
1314     if device is not None and not os.path.isdir(device):
1315         try:
1316             check_for_fat(device)
1317         except CriticalException, error:
1318             logging.critical("Execution failed: %s", error)
1319             sys.exit(1)
1320
1321     if not check_for_usbdevice(device):
1322         print "Warning: the specified device %s does not look like a removable usb device." % device
1323         f = raw_input("Do you really want to continue? y/N ")
1324         if f == "y" or f == "Y":
1325             pass
1326         else:
1327             sys.exit(1)
1328
1329
1330 def handle_compat_warning(device):
1331     """TODO"""
1332
1333     # make sure we can replace old grml2usb script and warn user when using old way of life:
1334     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1335         print "Warning: the semantics of grml2usb has changed."
1336         print "Instead of using grml2usb /path/to/iso %s you might" % device
1337         print "want to use grml2usb /path/to/iso /dev/... instead."
1338         print "Please check out the grml2usb manpage for details."
1339         f = raw_input("Do you really want to continue? y/N ")
1340         if f == "y" or f == "Y":
1341             pass
1342         else:
1343             sys.exit(1)
1344
1345
1346 def handle_logging():
1347     """TODO"""
1348
1349     if options.verbose:
1350         FORMAT = "%(asctime)-15s %(message)s"
1351         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1352     elif options.quiet:
1353         FORMAT = "Critical: %(message)s"
1354         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1355     else:
1356         FORMAT = "Info: %(message)s"
1357         logging.basicConfig(level=logging.INFO, format=FORMAT)
1358
1359
1360 def handle_bootloader(device):
1361     """TODO"""
1362     # Install bootloader only if not using the --copy-only option
1363     if options.copyonly:
1364         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1365     else:
1366         install_bootloader(device)
1367
1368
1369 def main():
1370     """Main function [make pylint happy :)]"""
1371
1372     if options.version:
1373         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1374         sys.exit(0)
1375
1376     if len(args) < 2:
1377         parser.error("invalid usage")
1378
1379     # log handling
1380     handle_logging()
1381
1382     # make sure we have the appropriate permissions
1383     check_uid_root()
1384
1385     if options.dryrun:
1386         logging.info("Running in simulation mode as requested via option dry-run.")
1387
1388     # specified arguments
1389     device = args[len(args) - 1]
1390     isos = args[0:len(args) - 1]
1391
1392     if device[-1:].isdigit():
1393         if int(device[-1:]) > 4:
1394             logging.critical("Fatal: installation on partition number >4 not supported. (As the BIOS won't support it.)")
1395             sys.exit(1)
1396     else:
1397         logging.critical("Fatal: installation on raw device not supported. (As the BIOS won't support it.)")
1398         sys.exit(1)
1399
1400     # provide upgrade path
1401     handle_compat_warning(device)
1402
1403     # check for vfat partition
1404     handle_vfat(device)
1405
1406     # main operation (like installing files)
1407     for iso in isos:
1408         handle_iso(iso, device)
1409
1410     # install mbr
1411     handle_mbr(device)
1412
1413     handle_bootloader(device)
1414
1415     # finally be politely :)
1416     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
1417
1418
1419 if __name__ == "__main__":
1420     try:
1421         main()
1422     except KeyboardInterrupt:
1423         logging.info("Received KeyboardInterrupt")
1424         cleanup()
1425
1426 ## END OF FILE #################################################################
1427 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8