Slightly improve mount error message
[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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 live-media-path=/live/%(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 (no filesystem on the partition?)")
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/' + grml_flavour + '/'
938         execute(mkdir, squashfs_target)
939         logging.debug("cp %s %s" % (squashfs, squashfs_target + grml_flavour + '.squashfs'))
940         proc = subprocess.Popen(["cp", squashfs, squashfs_target + grml_flavour + ".squashfs"])
941         proc.wait()
942
943     filesystem_module = search_file('filesystem.module', iso_mount)
944     if filesystem_module is None:
945         logging.critical("Fatal: filesystem.module not found")
946     else:
947         logging.debug("cp %s %s" % (filesystem_module, squashfs_target + 'filesystem.module'))
948         proc = subprocess.Popen(["cp", filesystem_module, squashfs_target + 'filesystem.module'])
949         proc.wait()
950
951     release_target = target + '/boot/release/' + grml_flavour
952     execute(mkdir, release_target)
953
954     kernel = search_file('linux26', iso_mount)
955     if kernel is None:
956         logging.critical("Fatal kernel not found")
957     else:
958         logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
959         proc = subprocess.Popen(["cp", kernel, release_target + '/linux26'])
960         proc.wait()
961
962     initrd = search_file('initrd.gz', iso_mount)
963     if initrd is None:
964         logging.critical("Fatal: initrd not found")
965     else:
966         logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
967         proc = subprocess.Popen(["cp", initrd, release_target + '/initrd.gz'])
968         proc.wait()
969
970
971 def copy_grml_files(iso_mount, target):
972     """copy some minor grml files to a given target
973
974     @iso_mount: path where a grml ISO is mounted on
975     @target: path where grml's main files should be copied to"""
976
977     grml_target = target + '/grml/'
978     execute(mkdir, grml_target)
979
980     for myfile in 'grml-cheatcodes.txt', 'grml-version', 'LICENSE.txt', 'md5sums', 'README.txt':
981         grml_file = search_file(myfile, iso_mount)
982         if grml_file is None:
983             logging.warn("Warning: myfile %s could not be found - can not install it", myfile)
984         else:
985             logging.debug("cp %s %s" % (grml_file, grml_target + grml_file))
986             proc = subprocess.Popen(["cp", grml_file, grml_target + myfile])
987             proc.wait()
988
989     grml_web_target = grml_target + '/web/'
990     execute(mkdir, grml_web_target)
991
992     for myfile in 'index.html', 'style.css':
993         grml_file = search_file(myfile, iso_mount)
994         if grml_file is None:
995             logging.warn("Warning: myfile %s could not be found - can not install it")
996         else:
997             logging.debug("cp %s %s" % (grml_file, grml_web_target + grml_file))
998             proc = subprocess.Popen(["cp", grml_file, grml_web_target + myfile])
999             proc.wait()
1000
1001     grml_webimg_target = grml_web_target + '/images/'
1002     execute(mkdir, grml_webimg_target)
1003
1004     for myfile in 'button.png', 'favicon.png', 'linux.jpg', 'logo.png':
1005         grml_file = search_file(myfile, iso_mount)
1006         if grml_file is None:
1007             logging.warn("Warning: myfile %s could not be found - can not install it")
1008         else:
1009             logging.debug("cp %s %s" % (grml_file, grml_webimg_target + grml_file))
1010             proc = subprocess.Popen(["cp", grml_file, grml_webimg_target + myfile])
1011             proc.wait()
1012
1013
1014 def copy_addons(iso_mount, target):
1015     """copy grml's addons files (like allinoneimg, bsd4grml,..) to a given target
1016
1017     @iso_mount: path where a grml ISO is mounted on
1018     @target: path where grml's main files should be copied to"""
1019
1020     addons = target + '/boot/addons/'
1021     execute(mkdir, addons)
1022
1023     # grub all-in-one image
1024     allinoneimg = search_file('allinone.img', iso_mount)
1025     if allinoneimg is None:
1026         logging.warn("Warning: allinone.img not found - can not install it")
1027     else:
1028         logging.debug("cp %s %s" % (allinoneimg, addons + '/allinone.img'))
1029         proc = subprocess.Popen(["cp", allinoneimg, addons + 'allinone.img'])
1030         proc.wait()
1031
1032     # bsd imag
1033     bsdimg = search_file('bsd4grml', iso_mount)
1034     if bsdimg is None:
1035         logging.warn("Warning: bsd4grml not found - can not install it")
1036     else:
1037         logging.debug("cp -a %s %s" % (bsdimg, addons + '/'))
1038         proc = subprocess.Popen(["cp", "-a", bsdimg, addons + '/'])
1039         proc.wait()
1040
1041     # freedos image
1042     balderimg = search_file('balder10.imz', iso_mount)
1043     if balderimg is None:
1044         logging.warn("Warning: balder10.imz not found - can not install it")
1045     else:
1046         logging.debug("cp %s %s" % (balderimg, addons + '/balder10.imz'))
1047         proc = subprocess.Popen(["cp", balderimg, addons + 'balder10.imz'])
1048         proc.wait()
1049
1050     # memdisk image
1051     memdiskimg = search_file('memdisk', iso_mount)
1052     if memdiskimg is None:
1053         logging.warn("Warning: memdisk not found - can not install it")
1054     else:
1055         logging.debug("cp %s %s" % (memdiskimg, addons + '/memdisk'))
1056         proc = subprocess.Popen(["cp", memdiskimg, addons + 'memdisk'])
1057         proc.wait()
1058
1059     # memtest86+ image
1060     memtestimg = search_file('memtest', iso_mount)
1061     if memtestimg is None:
1062         logging.warn("Warning: memtest not found - can not install it")
1063     else:
1064         logging.debug("cp %s %s" % (memtestimg, addons + '/memtest'))
1065         proc = subprocess.Popen(["cp", memtestimg, addons + 'memtest'])
1066         proc.wait()
1067
1068
1069 def copy_bootloader_files(iso_mount, target):
1070     """copy grml's bootloader files to a given target
1071
1072     @iso_mount: path where a grml ISO is mounted on
1073     @target: path where grml's main files should be copied to"""
1074
1075     syslinux_target = target + '/boot/syslinux/'
1076     execute(mkdir, syslinux_target)
1077
1078     logo = search_file('logo.16', iso_mount)
1079     logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
1080     proc = subprocess.Popen(["cp", logo, syslinux_target + 'logo.16'])
1081     proc.wait()
1082
1083     for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
1084         bootsplash = search_file(ffile, iso_mount)
1085         logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
1086         proc = subprocess.Popen(["cp", bootsplash, syslinux_target + ffile])
1087         proc.wait()
1088
1089     grub_target = target + '/boot/grub/'
1090     execute(mkdir, grub_target)
1091
1092     if not os.path.isfile("/usr/share/grml2usb/grub/splash.xpm.gz"):
1093         logging.critical("Error: /usr/share/grml2usb/grub/splash.xpm.gz can not be read.")
1094         raise
1095     else:
1096         logging.debug("cp /usr/share/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz')
1097         proc = subprocess.Popen(["cp", '/usr/share/grml2usb/grub/splash.xpm.gz',
1098                                 grub_target + 'splash.xpm.gz'])
1099         proc.wait()
1100
1101     # grml splash in grub
1102     if os.path.isfile("/usr/share/grml2usb/grub/grml.png"):
1103         logging.debug("cp /usr/share/grml2usb/grub/grml.png to %s" % grub_target + 'grml.png')
1104         proc = subprocess.Popen(["cp", '/usr/share/grml2usb/grub/grml.png',
1105                                 grub_target + 'grml.png'])
1106         proc.wait()
1107
1108     # font file for graphical bootsplash in grub
1109     if os.path.isfile("/usr/share/grub/ascii.pff"):
1110         logging.debug("cp /usr/share/grub/ascii.pff to %s" % grub_target + 'ascii.pff')
1111         proc = subprocess.Popen(["cp", '/usr/share/grub/ascii.pff',
1112                                 grub_target + 'ascii.pff'])
1113         proc.wait()
1114
1115
1116 def install_iso_files(grml_flavour, iso_mount, device, target):
1117     """Copy files from ISO to given target
1118
1119     @grml_flavour: name of grml flavour the configuration should be generated for
1120     @iso_mount: path where a grml ISO is mounted on
1121     @target: path where grml's main files should be copied to"""
1122
1123     # TODO => several improvements:
1124     # * make sure grml_flavour, iso_mount, target are set when the function is called, otherwise raise exception
1125     # * provide alternative search_file() if file information is stored in a config.ini file?
1126     # * catch "install: .. No space left on device" & CO
1127
1128     if options.dryrun:
1129         logging.info("Would copy files to %s", iso_mount)
1130         return 0
1131     elif not options.bootloaderonly:
1132         logging.info("Copying files. This might take a while....")
1133         copy_system_files(grml_flavour, iso_mount, target)
1134         copy_grml_files(iso_mount, target)
1135
1136     if not options.skipaddons:
1137         copy_addons(iso_mount, target)
1138
1139     if not options.copyonly:
1140         copy_bootloader_files(iso_mount, target)
1141
1142         if not options.dryrun:
1143             handle_bootloader_config(grml_flavour, device, target)
1144
1145     # make sure we sync filesystems before returning
1146     proc = subprocess.Popen(["sync"])
1147     proc.wait()
1148
1149
1150 def uninstall_files(device):
1151     """Get rid of all grml files on specified device
1152
1153     @device: partition where grml2usb files should be removed from"""
1154
1155     # TODO - not implemented yet
1156     logging.critical("TODO: uninstalling files from %s not yet implement, sorry." % device)
1157
1158
1159 def identify_grml_flavour(mountpath):
1160     """Get name of grml flavour
1161
1162     @mountpath: path where the grml ISO is mounted to
1163     @return: name of grml-flavour"""
1164
1165     version_file = search_file('grml-version', mountpath)
1166
1167     if version_file == "":
1168         logging.critical("Error: could not find grml-version file.")
1169         raise
1170
1171     try:
1172         tmpfile = open(version_file, 'r')
1173         grml_info = tmpfile.readline()
1174         grml_flavour = re.match(r'[\w-]*', grml_info).group()
1175     except TypeError:
1176         raise
1177     except:
1178         logging.critical("Unexpected error:", sys.exc_info()[0])
1179         raise
1180
1181     return grml_flavour
1182
1183
1184 def handle_grub1_config(grml_flavour, install_partition, grub_target, bootopt):
1185     """Main handler for generating grub1 configuration
1186
1187     @grml_flavour: name of grml flavour the configuration should be generated for
1188     @install_partition: partition number for use in (hd0,X)
1189     @grub_target: path of grub's configuration files
1190     @bootoptions: additional bootoptions that should be used by default"""
1191
1192     # grub1 config
1193     grub1_cfg = grub_target + 'menu.lst'
1194     logging.debug("Creating grub1 configuration file (menu.lst)")
1195
1196     # install main configuration only *once*, no matter how many ISOs we have:
1197     if os.path.isfile(grub1_cfg):
1198         string = open(grub1_cfg).readline()
1199         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1200         if not re.match(main_identifier, string):
1201             grub1_config_file = open(grub1_cfg, 'w')
1202             grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1203             grub1_config_file.close()
1204     else:
1205         grub1_config_file = open(grub1_cfg, 'w')
1206         grub1_config_file.write(generate_main_grub1_config(grml_flavour, install_partition, bootopt))
1207         grub1_config_file.close()
1208
1209     grub_flavour_config = True
1210     if os.path.isfile(grub1_cfg):
1211         string = open(grub1_cfg).readlines()
1212         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1213         for line in string:
1214             if flavour.match(line):
1215                 grub_flavour_config = False
1216
1217     if grub_flavour_config:
1218         grub1_config_file = open(grub1_cfg, 'a')
1219         grub1_config_file.write(generate_flavour_specific_grub1_config(grml_flavour, install_partition, bootopt))
1220         grub1_config_file.close( )
1221
1222
1223 def handle_grub2_config(grml_flavour, install_partition, grub_target, bootopt):
1224     """Main handler for generating grub2 configuration
1225
1226     @grml_flavour: name of grml flavour the configuration should be generated for
1227     @install_partition: partition number for use in (hd0,X)
1228     @grub_target: path of grub's configuration files
1229     @bootoptions: additional bootoptions that should be used by default"""
1230
1231     # grub2 config
1232     grub2_cfg = grub_target + 'grub.cfg'
1233     logging.debug("Creating grub2 configuration file (grub.lst)")
1234
1235     # install main configuration only *once*, no matter how many ISOs we have:
1236     if os.path.isfile(grub2_cfg):
1237         string = open(grub2_cfg).readline()
1238         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1239         if not re.match(main_identifier, string):
1240             grub2_config_file = open(grub2_cfg, 'w')
1241             logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1242             grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1243             grub2_config_file.close()
1244     else:
1245         grub2_config_file = open(grub2_cfg, 'w')
1246         logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1247         grub2_config_file.write(generate_main_grub2_config(grml_flavour, install_partition, bootopt))
1248         grub2_config_file.close()
1249
1250     grub_flavour_config = True
1251     if os.path.isfile(grub2_cfg):
1252         string = open(grub2_cfg).readlines()
1253         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1254         for line in string:
1255             if flavour.match(line):
1256                 grub_flavour_config = False
1257
1258     if grub_flavour_config:
1259         grub2_config_file = open(grub2_cfg, 'a')
1260         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1261         grub2_config_file.write(generate_flavour_specific_grub2_config(grml_flavour, install_partition, bootopt))
1262         grub2_config_file.close( )
1263
1264
1265 def handle_grub_config(grml_flavour, device, target):
1266     """Main handler for generating grub (v1 and v2) configuration
1267
1268     @grml_flavour: name of grml flavour the configuration should be generated for
1269     @device: device/partition where grub should be installed to
1270     @target: path of grub's configuration files"""
1271
1272     logging.debug("Generating grub configuration")
1273
1274     grub_target = target + '/boot/grub/'
1275     execute(mkdir, grub_target)
1276
1277     # we have to adjust root() inside grub configuration
1278     if device[-1:].isdigit():
1279         install_grub1_partition = int(device[-1:]) - 1
1280         install_grub2_partition = device[-1:]
1281
1282     # do NOT write "None" in kernel cmdline
1283     if options.bootoptions is None:
1284         bootopt = ""
1285     else:
1286         bootopt = options.bootoptions
1287
1288     # write menu.lst
1289     handle_grub1_config(grml_flavour, install_grub1_partition, grub_target, bootopt)
1290     # write grub.cfg
1291     handle_grub2_config(grml_flavour, install_grub2_partition, grub_target, bootopt)
1292
1293
1294 def handle_syslinux_config(grml_flavour, target):
1295     """Main handler for generating syslinux configuration
1296
1297     @grml_flavour: name of grml flavour the configuration should be generated for
1298     @target: path of syslinux's configuration files"""
1299
1300     # do NOT write "None" in kernel cmdline
1301     if options.bootoptions is None:
1302         bootopt = ""
1303     else:
1304         bootopt = options.bootoptions
1305
1306     logging.info("Generating syslinux configuration")
1307     syslinux_target = target + '/boot/syslinux/'
1308     # should be present via  copy_bootloader_files(), but make sure it exits:
1309     execute(mkdir, syslinux_target)
1310     syslinux_cfg = syslinux_target + 'syslinux.cfg'
1311
1312     # install main configuration only *once*, no matter how many ISOs we have:
1313     if os.path.isfile(syslinux_cfg):
1314         string = open(syslinux_cfg).readline()
1315         main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(DATESTAMP)))
1316         if not re.match(main_identifier, string):
1317             syslinux_config_file = open(syslinux_cfg, 'w')
1318             logging.info("Note: grml flavour %s is being installed as the default booting system." % grml_flavour)
1319             syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1320             syslinux_config_file.close()
1321     else:
1322         syslinux_config_file = open(syslinux_cfg, 'w')
1323         syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, bootopt))
1324         syslinux_config_file.close()
1325
1326     # install flavour specific configuration only *once* as well
1327     # kind of ugly - I'm pretty sure this could be smoother...
1328     syslinux_flavour_config = True
1329     if os.path.isfile(syslinux_cfg):
1330         string = open(syslinux_cfg).readlines()
1331         logging.info("Note: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
1332         flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(DATESTAMP))))
1333         for line in string:
1334             if flavour.match(line):
1335                 syslinux_flavour_config = False
1336
1337     if syslinux_flavour_config:
1338         syslinux_config_file = open(syslinux_cfg, 'a')
1339         syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, bootopt))
1340         syslinux_config_file.close( )
1341
1342     logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
1343     isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
1344     isolinux_splash.write(generate_isolinux_splash(grml_flavour))
1345     isolinux_splash.close( )
1346
1347
1348 def handle_bootloader_config(grml_flavour, device, target):
1349     """Main handler for generating bootloader's configuration
1350
1351     @grml_flavour: name of grml flavour the configuration should be generated for
1352     @device: device/partition where bootloader should be installed to
1353     @target: path of bootloader's configuration files"""
1354
1355     if options.syslinux:
1356         handle_syslinux_config(grml_flavour, target)
1357     else:
1358         handle_grub_config(grml_flavour, device, target)
1359
1360
1361 def handle_iso(iso, device):
1362     """Main logic for mounting ISOs and copying files.
1363
1364     @iso: full path to the ISO that should be installed to the specified device
1365     @device: partition where the specified ISO should be installed to"""
1366
1367     logging.info("Using ISO %s" % iso)
1368
1369     if os.path.isdir(iso):
1370         logging.critical("TODO: /live/image handling not yet implemented - sorry")
1371         sys.exit(1)
1372
1373     iso_mountpoint = tempfile.mkdtemp()
1374     register_tmpfile(iso_mountpoint)
1375     remove_iso_mountpoint = True
1376
1377     if not os.path.isfile(iso):
1378         logging.critical("Fatal: specified ISO %s could not be read" % iso)
1379         cleanup()
1380         sys.exit(1)
1381
1382     try:
1383         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
1384     except CriticalException, error:
1385         logging.critical("Fatal: %s" % error)
1386         sys.exit(1)
1387
1388     if os.path.isdir(device):
1389         logging.info("Specified target is a directory, not mounting therefor.")
1390         device_mountpoint = device
1391         remove_device_mountpoint = False
1392         # skip_mbr = True
1393     else:
1394         device_mountpoint = tempfile.mkdtemp()
1395         register_tmpfile(device_mountpoint)
1396         remove_device_mountpoint = True
1397         try:
1398             mount(device, device_mountpoint, "")
1399         except CriticalException, error:
1400             logging.critical("Fatal: %s" % error)
1401             cleanup()
1402             sys.exit(1)
1403
1404     try:
1405         grml_flavour = identify_grml_flavour(iso_mountpoint)
1406         logging.info("Identified grml flavour \"%s\"." % grml_flavour)
1407         install_iso_files(grml_flavour, iso_mountpoint, device, device_mountpoint)
1408     except TypeError:
1409         logging.critical("Fatal: a critical error happend during execution (not a grml ISO?), giving up")
1410         sys.exit(1)
1411     finally:
1412         if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
1413             unmount(iso_mountpoint, "")
1414             os.rmdir(iso_mountpoint)
1415             unregister_tmpfile(iso_mountpoint)
1416         if remove_device_mountpoint:
1417             unmount(device_mountpoint, "")
1418             if os.path.isdir(device_mountpoint):
1419                 os.rmdir(device_mountpoint)
1420                 unregister_tmpfile(device_mountpoint)
1421
1422
1423 def handle_mbr(device):
1424     """Main handler for installing master boot record (MBR)
1425
1426     @device: device where the MBR should be installed to"""
1427
1428     # make sure we have syslinux available
1429     if not options.skipmbr:
1430         if not which("syslinux") and not options.copyonly and not options.dryrun:
1431             logging.critical('Sorry, syslinux not available. Exiting.')
1432             logging.critical('Please install syslinux or consider using the --grub option.')
1433             sys.exit(1)
1434
1435     if not options.skipmbr:
1436         if device[-1:].isdigit():
1437             mbr_device = re.match(r'(.*?)\d*$', device).group(1)
1438             partition_number = int(device[-1:]) - 1
1439
1440         try:
1441             if options.syslinuxmbr:
1442                 handle_syslinux_mbr(mbr_device)
1443             else:
1444                 if options.mbrmgr:
1445                     install_mir_mbr('/usr/share/grml2usb/mbr/mbrmgr', mbr_device, partition_number, True)
1446                 else:
1447                     install_mir_mbr('/usr/share/grml2usb/mbr/mbrldr', mbr_device, partition_number, True)
1448         except IOError, error:
1449             logging.critical("Execution failed: %s", error)
1450             sys.exit(1)
1451         except Exception, error:
1452             logging.critical("Execution failed: %s", error)
1453             sys.exit(1)
1454
1455
1456 def handle_vfat(device):
1457     """Check for FAT specific settings and options
1458
1459     @device: device that should checked / formated"""
1460
1461     # make sure we have mkfs.vfat available
1462     if options.fat16 and not options.force:
1463         if not which("mkfs.vfat") and not options.copyonly and not options.dryrun:
1464             logging.critical('Sorry, mkfs.vfat not available. Exiting.')
1465             logging.critical('Please make sure to install dosfstools.')
1466             sys.exit(1)
1467
1468         # make sure the user is aware of what he is doing
1469         f = raw_input("Are you sure you want to format the specified partition with fat16? y/N ")
1470         if f == "y" or f == "Y":
1471             logging.info("Note: you can skip this question using the option --force")
1472             try:
1473                 mkfs_fat16(device)
1474             except CriticalException, error:
1475                 logging.critical("Execution failed: %s", error)
1476                 sys.exit(1)
1477         else:
1478             sys.exit(1)
1479
1480     # check for vfat filesystem
1481     if device is not None and not os.path.isdir(device):
1482         try:
1483             check_for_fat(device)
1484         except CriticalException, error:
1485             logging.critical("Execution failed: %s", error)
1486             sys.exit(1)
1487
1488     if not check_for_usbdevice(device):
1489         print "Warning: the specified device %s does not look like a removable usb device." % device
1490         f = raw_input("Do you really want to continue? y/N ")
1491         if f == "y" or f == "Y":
1492             pass
1493         else:
1494             sys.exit(1)
1495
1496
1497 def handle_compat_warning(device):
1498     """Backwards compatible checks
1499
1500     @device: device that should be checked"""
1501
1502     # make sure we can replace old grml2usb script and warn user when using old way of life:
1503     if device.startswith("/mnt/external") or device.startswith("/mnt/usb") and not options.force:
1504         print "Warning: the semantics of grml2usb has changed."
1505         print "Instead of using grml2usb /path/to/iso %s you might" % device
1506         print "want to use grml2usb /path/to/iso /dev/... instead."
1507         print "Please check out the grml2usb manpage for details."
1508         f = raw_input("Do you really want to continue? y/N ")
1509         if f == "y" or f == "Y":
1510             pass
1511         else:
1512             sys.exit(1)
1513
1514
1515 def handle_logging():
1516     """Log handling and configuration"""
1517
1518     if options.verbose:
1519         FORMAT = "Debug: %(asctime)-15s %(message)s"
1520         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
1521     elif options.quiet:
1522         FORMAT = "Critical: %(message)s"
1523         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
1524     else:
1525         FORMAT = "%(message)s"
1526         logging.basicConfig(level=logging.INFO, format=FORMAT)
1527
1528
1529 def handle_bootloader(device):
1530     """wrapper for installing bootloader
1531
1532     @device: device where bootloader should be installed to"""
1533
1534     # Install bootloader only if not using the --copy-only option
1535     if options.copyonly:
1536         logging.info("Not installing bootloader and its files as requested via option copyonly.")
1537     else:
1538         install_bootloader(device)
1539
1540
1541 def main():
1542     """Main function [make pylint happy :)]"""
1543
1544     if options.version:
1545         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
1546         sys.exit(0)
1547
1548     if len(args) < 2:
1549         parser.error("invalid usage")
1550
1551     # log handling
1552     handle_logging()
1553
1554     # make sure we have the appropriate permissions
1555     check_uid_root()
1556
1557     if options.dryrun:
1558         logging.info("Running in simulation mode as requested via option dry-run.")
1559
1560     # specified arguments
1561     device = args[len(args) - 1]
1562     isos = args[0:len(args) - 1]
1563
1564     if device[-1:].isdigit():
1565         if int(device[-1:]) > 4:
1566             logging.critical("Fatal: installation on partition number >4 not supported. (BIOS won't support it.)")
1567             sys.exit(1)
1568     else:
1569         logging.critical("Fatal: installation on raw device not supported. (BIOS won't support it.)")
1570         sys.exit(1)
1571
1572     # provide upgrade path
1573     handle_compat_warning(device)
1574
1575     # check for vfat partition
1576     handle_vfat(device)
1577
1578     # main operation (like installing files)
1579     for iso in isos:
1580         handle_iso(iso, device)
1581
1582     # install mbr
1583     handle_mbr(device)
1584
1585     handle_bootloader(device)
1586
1587     # finally be politely :)
1588     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
1589
1590
1591 if __name__ == "__main__":
1592     try:
1593         main()
1594     except KeyboardInterrupt:
1595         logging.info("Received KeyboardInterrupt")
1596         cleanup()
1597
1598 ## END OF FILE #################################################################
1599 # vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8