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