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