Drop stage2_eltorito file
[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
1081 def install_iso_files(grml_flavour, iso_mount, device, target):
1082     """Copy files from ISO on given target"""
1083
1084     # TODO
1085     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1086     # * provide alternative search_file() if file information is stored in a config.ini file?
1087     # * catch "install: .. No space left on device" & CO
1088
1089     if options.dryrun:
1090         logging.info("Would copy files to %s", iso_mount)
1091         return 0
1092     elif not options.bootloaderonly:
1093         logging.info("Copying files. This might take a while....")
1094         copy_system_files(grml_flavour, iso_mount, target)
1095         copy_grml_files(iso_mount, target)
1096
1097     if not options.skipaddons:
1098         copy_addons(iso_mount, target)
1099
1100     if not options.copyonly:
1101         copy_bootloader_files(iso_mount, target)
1102
1103         if not options.dryrun:
1104             handle_bootloader_config(grml_flavour, device, target)
1105
1106     # make sure we sync filesystems before returning
1107     proc = subprocess.Popen(["sync"])
1108     proc.wait()
1109
1110
1111 def uninstall_files(device):
1112     """Get rid of all grml files on specified device
1113
1114     @device: partition where grml2usb files should be removed from"""
1115
1116     # TODO
1117     logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
1118
1119
1120 def identify_grml_flavour(mountpath):
1121     """Get name of grml flavour
1122
1123     @mountpath: path where the grml ISO is mounted to
1124     @return: name of grml-flavour"""
1125
1126     version_file = search_file('grml-version', mountpath)
1127
1128     if version_file == "":
1129         logging.critical("Error: could not find grml-version file.")
1130         raise
1131
1132     try:
1133         tmpfile = open(version_file, 'r')
1134         grml_info = tmpfile.readline()
1135         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1136     except TypeError:
1137         raise
1138     except:
1139         logging.critical("Unexpected error:", sys.exc_info()[0])
1140         raise
1141
1142     return grml_flavour
1143
1144
1145 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1146     """TODO"""
1147
1148     # grub1 config
1149     grub1_cfg = grub_target + 'menu.lst'
1150     logging.debug("Creating grub1 configuration file (menu.lst)")
1151
1152     # install main configuration only *once*, no matter how many ISOs we have:
1153     if os.path.isfile(grub1_cfg):
1154         string = open(grub1_cfg).readline()
1155         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1156         if not re.match(main_identifier, string):
1157             grub1_config_file = open(grub1_cfg, 'w')
1158             # logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1159             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1160             grub1_config_file.close()
1161     else:
1162         grub1_config_file = open(grub1_cfg, 'w')
1163         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1164         grub1_config_file.close()
1165
1166     grub_flavour_config = True
1167     if os.path.isfile(grub1_cfg):
1168         string = open(grub1_cfg).readlines()
1169         # logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1170         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1171         for line in string:
1172             if flavour.match(line):
1173                 grub_flavour_config = False
1174
1175     if grub_flavour_config:
1176         grub1_config_file = open(grub1_cfg, 'a')
1177         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1178         grub1_config_file.close( )
1179
1180
1181 def handle_grub2_config(grml_flavour, install_partition, grub_target, bootopt):
1182     """TODO"""
1183
1184     # grub2 config
1185     grub2_cfg = grub_target + 'grub.cfg'
1186     logging.debug("Creating grub2 configuration file (grub.lst)")
1187
1188     # install main configuration only *once*, no matter how many ISOs we have:
1189     if os.path.isfile(grub2_cfg):
1190         string = open(grub2_cfg).readline()
1191         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1192         if not re.match(main_identifier, string):
1193             grub2_config_file = open(grub2_cfg, 'w')
1194             logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1195             grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1196             grub2_config_file.close()
1197     else:
1198         grub2_config_file = open(grub2_cfg, 'w')
1199         grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1200         grub2_config_file.close()
1201
1202     grub_flavour_config = True
1203     if os.path.isfile(grub2_cfg):
1204         string = open(grub2_cfg).readlines()
1205         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1206         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1207         for line in string:
1208             if flavour.match(line):
1209                 grub_flavour_config = False
1210
1211     if grub_flavour_config:
1212         grub2_config_file = open(grub2_cfg, 'a')
1213         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootopt))
1214         grub2_config_file.close( )
1215
1216
1217 def handle_grub_config(grml_flavour, device, target):
1218     """ TODO """
1219
1220     logging.debug("Generating grub configuration")
1221
1222     grub_target = target + '/boot/grub/'
1223     execute(mkdir, grub_target)
1224
1225     # we have to adjust root() inside grub configuration
1226     if device[-1:].isdigit():
1227         install_grub1_partition = int(device[-1:]) - 1
1228         install_grub2_partition = device[-1:]
1229
1230     # do NOT write "None" in kernel cmdline
1231     if options.bootoptions is None:
1232         bootopt = ""
1233     else:
1234         bootopt = options.bootoptions
1235
1236     # write menu.lst
1237     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1238     # write grub.cfg
1239     handle_grub2_config(grml_flavour, install_grub2_partition, grub_target, bootopt)
1240
1241
1242 def handle_syslinux_config(grml_flavour, target):
1243     """ TODO
1244     """
1245
1246     # do NOT write "None" in kernel cmdline
1247     if options.bootoptions is None:
1248         bootopt = ""
1249     else:
1250         bootopt = options.bootoptions
1251
1252     logging.info("Generating syslinux configuration")
1253     syslinux_target = target + '/boot/syslinux/'
1254     # should be present via  copy_bootloader_files(), but make sure it exits:
1255     execute(mkdir, syslinux_target)
1256     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1257
1258     # install main configuration only *once*, no matter how many ISOs we have:
1259     if os.path.isfile(syslinux_cfg):
1260         string = open(syslinux_cfg).readline()
1261         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1262         if not re.match(main_identifier, string):
1263             syslinux_config_file = open(syslinux_cfg, 'w')
1264             logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1265             syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1266             syslinux_config_file.close()
1267     else:
1268         syslinux_config_file = open(syslinux_cfg, 'w')
1269         syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1270         syslinux_config_file.close()
1271
1272     # install flavour specific configuration only *once* as well
1273     # kind of ugly - I'm pretty sure this could be smoother...
1274     syslinux_flavour_config = True
1275     if os.path.isfile(syslinux_cfg):
1276         string = open(syslinux_cfg).readlines()
1277         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1278         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1279         for line in string:
1280             if flavour.match(line):
1281                 syslinux_flavour_config = False
1282
1283     if syslinux_flavour_config:
1284         syslinux_config_file = open(syslinux_cfg, 'a')
1285         syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
1286         syslinux_config_file.close( )
1287
1288     logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
1289     isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
1290     isolinux_splash.write(generate_isolinux_splash(grml_flavour))
1291     isolinux_splash.close( )
1292
1293
1294 def handle_bootloader_config(grml_flavour, device, target):
1295     """TODO"""
1296
1297     if options.syslinux:
1298         handle_syslinux_config(grml_flavour, target)
1299     else:
1300         handle_grub_config(grml_flavour, device, target)
1301
1302
1303 def handle_iso(iso, device):
1304     """Main logic for mounting ISOs and copying files.
1305
1306     @iso: full path to the ISO that should be installed to the specified device
1307     @device: partition where the specified ISO should be installed to"""
1308
1309     logging.info("Using ISO %s" % iso)
1310
1311     if os.path.isdir(iso):
1312         logging.critical("TODO: /live/image handling not yet implemented - sorry") # TODO
1313         sys.exit(1)
1314
1315     iso_mountpoint = tempfile.mkdtemp()
1316     register_tmpfile(iso_mountpoint)
1317     remove_iso_mountpoint = True
1318
1319     if not os.path.isfile(iso):
1320         logging.critical("Fatal: specified ISO %s could not be read" % iso)
1321         cleanup()
1322         sys.exit(1)
1323
1324     try:
1325         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1326     except CriticalException, error:
1327         logging.critical("Fatal: %s" % error)
1328         sys.exit(1)
1329
1330     if os.path.isdir(device):
1331         logging.info("Specified target is a directory, not mounting therefor.")
1332         device_mountpoint = device
1333         remove_device_mountpoint = False
1334         # skip_mbr = True
1335     else:
1336         device_mountpoint = tempfile.mkdtemp()
1337         register_tmpfile(device_mountpoint)
1338         remove_device_mountpoint = True
1339         try:
1340             mount(device, device_mountpoint, "")
1341         except CriticalException, error:
1342             logging.critical("Fatal: %s" % error)
1343             cleanup()
1344             sys.exit(1)
1345
1346     try:
1347         grml_flavour = identify_grml_flavour(iso_mountpoint)
1348         logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1349         install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1350     except TypeError:
1351         logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1352         sys.exit(1)
1353     finally:
1354         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1355             unmount(iso_mountpoint, "")
1356             os.rmdir(iso_mountpoint)
1357             unregister_tmpfile(iso_mountpoint)
1358         if remove_device_mountpoint:
1359             unmount(device_mountpoint, "")
1360             if os.path.isdir(device_mountpoint):
1361                 os.rmdir(device_mountpoint)
1362                 unregister_tmpfile(device_mountpoint)
1363
1364
1365 def handle_mbr(device):
1366     """TODO"""
1367
1368     # install MBR
1369     # if not options.mbr:
1370     #     logging.info("You are NOT using the --mbr option. Consider using it if your device does not boot.")
1371     # else:
1372     # make sure we install MBR on /dev/sdX and not /dev/sdX#
1373
1374     # make sure we have syslinux available
1375     # if options.mbr:
1376     if not options.skipmbr:
1377         if not which("syslinux") and not options.copyonly and not options.dryrun:
1378             logging.critical('Sorry, syslinux not available. Exiting.')
1379             logging.critical('Please install syslinux or consider using the --grub option.')
1380             sys.exit(1)
1381
1382     if not options.skipmbr:
1383         if device[-1:].isdigit():
1384             mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1385             partition_number = int(device[-1:]) - 1
1386
1387         try:
1388             if options.syslinuxmbr:
1389                 handle_syslinux_mbr(mbr_device)
1390             else:
1391                 if options.mbrmgr:
1392                     install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True)
1393                 else:
1394                     install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, True)
1395         except IOError, error:
1396             logging.critical("Execution failed: %s", error)
1397             sys.exit(1)
1398         except Exception, error:
1399             logging.critical("Execution failed: %s", error)
1400             sys.exit(1)
1401
1402
1403 def handle_vfat(device):
1404     """TODO"""
1405
1406     # make sure we have mkfs.vfat available
1407     if options.fat16 and not options.force:
1408         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1409             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1410             logging.critical('Please make sure to install dosfstools.')
1411             sys.exit(1)
1412
1413         # make sure the user is aware of what he is doing
1414         f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1415         if f == "y" or f == "Y":
1416             logging.info("Note: you can skip this question using the option --force")
1417             try:
1418                 mkfs_fat16(device)
1419             except CriticalException, error:
1420                 logging.critical("Execution failed: %s", error)
1421                 sys.exit(1)
1422         else:
1423             sys.exit(1)
1424
1425     # check for vfat filesystem
1426     if device is not None and not os.path.isdir(device):
1427         try:
1428             check_for_fat(device)
1429         except CriticalException, error:
1430             logging.critical("Execution failed: %s", error)
1431             sys.exit(1)
1432
1433     if not check_for_usbdevice(device):
1434         print "Warning: the specified device %s does not look like a removable usb device." % device
1435         f = raw_input("Do you really want to continue? y/N ")
1436         if f == "y" or f == "Y":
1437             pass
1438         else:
1439             sys.exit(1)
1440
1441
1442 def handle_compat_warning(device):
1443     """TODO"""
1444
1445     # make sure we can replace old grml2usb script and warn user when using old way of life:
1446     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1447         print "Warning: the semantics of grml2usb has changed."
1448         print "Instead of using grml2usb /path/to/iso %s you might" % device
1449         print "want to use grml2usb /path/to/iso /dev/... instead."
1450         print "Please check out the grml2usb manpage for details."
1451         f = raw_input("Do you really want to continue? y/N ")
1452         if f == "y" or f == "Y":
1453             pass
1454         else:
1455             sys.exit(1)
1456
1457
1458 def handle_logging():
1459     """TODO"""
1460
1461     if options.verbose:
1462         FORMAT = "%(asctime)-15s %(message)s"
1463         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1464     elif options.quiet:
1465         FORMAT = "Critical: %(message)s"
1466         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1467     else:
1468         FORMAT = "Info: %(message)s"
1469         logging.basicConfig(level=logging.INFO, format=FORMAT)
1470
1471
1472 def handle_bootloader(device):
1473     """TODO"""
1474     # Install bootloader only if not using the --copy-only option
1475     if options.copyonly:
1476         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1477     else:
1478         install_bootloader(device)
1479
1480
1481 def main():
1482     """Main function [make pylint happy :)]"""
1483
1484     if options.version:
1485         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1486         sys.exit(0)
1487
1488     if len(args) < 2:
1489         parser.error("invalid usage")
1490
1491     # log handling
1492     handle_logging()
1493
1494     # make sure we have the appropriate permissions
1495     check_uid_root()
1496
1497     if options.dryrun:
1498         logging.info("Running in simulation mode as requested via option dry-run.")
1499
1500     # specified arguments
1501     device = args[len(args) - 1]
1502     isos = args[0:len(args) - 1]
1503
1504     if device[-1:].isdigit():
1505         if int(device[-1:]) > 4:
1506             logging.critical("Fatal: installation on partition number >4 not supported. (As the BIOS won't support it.)")
1507             sys.exit(1)
1508     else:
1509         logging.critical("Fatal: installation on raw device not supported. (As the BIOS won't support it.)")
1510         sys.exit(1)
1511
1512     # provide upgrade path
1513     handle_compat_warning(device)
1514
1515     # check for vfat partition
1516     handle_vfat(device)
1517
1518     # main operation (like installing files)
1519     for iso in isos:
1520         handle_iso(iso, device)
1521
1522     # install mbr
1523     handle_mbr(device)
1524
1525     handle_bootloader(device)
1526
1527     # finally be politely :)
1528     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
1529
1530
1531 if __name__ == "__main__":
1532     try:
1533         main()
1534     except KeyboardInterrupt:
1535         logging.info("Received KeyboardInterrupt")
1536         cleanup()
1537
1538 ## END OF FILE #################################################################
1539 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8