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