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