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