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