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