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