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