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