6b0a381943d0ee6b1f9a5faf07605706df790c6a
[grml2usb.git] / grml2usb.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 grml2usb
5 ~~~~~~~~
6
7 This script installs a grml system (running system / 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 TODO
14 ----
15
16 * code improvements:
17   - improve error handling :)
18   - get rid of all TODOs in code :)
19   - use 'with open("...", "w") as f: ... f.write("...")'
20   - simplify functions/code as much as possible -> audit
21 * implement missing options (--kernel, --initrd, --uninstall,...)
22 * validate partition schema/layout: is the partition schema ok and the bootable flag set?
23 * implement logic for storing information about copied files -> register every file in a set()
24 * the last line in bootsplash (boot.msg) should mention all installed grml flavours
25 * extend flavour's syslinux configuration
26 * graphical version? :)
27 """
28
29 from __future__ import with_statement
30 import os, re, subprocess, sys, tempfile
31 from optparse import OptionParser
32 from os.path import exists, join, abspath
33 from os import pathsep
34 from inspect import isroutine, isclass
35 import logging
36 import datetime, time
37
38 # global variables
39 PROG_VERSION = "0.0.1"
40 skip_mbr = False # By default we don't want to skip it; TODO - can we get rid of that?
41 mounted = set()  # register mountpoints
42 tmpfiles = set() # register tmpfiles
43 datestamp= time.mktime(datetime.datetime.now().timetuple()) # unique identifier for syslinux.cfg
44
45 # cmdline parsing
46 usage = "Usage: %prog [options] <[ISO[s] | /live/image]> </dev/ice>\n\
47 \n\
48 %prog installs a grml ISO to an USB device to be able to boot from it.\n\
49 Make sure you have at least a grml ISO or a running grml system (/live/image),\n\
50 syslinux (just run 'aptitude install syslinux' on Debian-based systems)\n\
51 and root access."
52
53 parser = OptionParser(usage=usage)
54 parser.add_option("--bootoptions", dest="bootoptions",
55                   action="store", type="string",
56                   help="use specified bootoptions as defaut")
57 parser.add_option("--bootloader-only", dest="bootloaderonly", action="store_true",
58                   help="do not copy files only but just install a bootloader")
59 parser.add_option("--copy-only", dest="copyonly", action="store_true",
60                   help="copy files only and do not install bootloader")
61 parser.add_option("--dry-run", dest="dryrun", action="store_true",
62                   help="do not actually execute any commands")
63 parser.add_option("--fat16", dest="fat16", action="store_true",
64                   help="format specified partition with FAT16")
65 parser.add_option("--force", dest="force", action="store_true",
66                   help="force any actions requiring manual interaction")
67 parser.add_option("--grub", dest="grub", action="store_true",
68                   help="install grub bootloader instead of syslinux")
69 parser.add_option("--initrd", dest="initrd", action="store", type="string",
70                   help="install specified initrd instead of the default")
71 parser.add_option("--kernel", dest="kernel", action="store", type="string",
72                   help="install specified kernel instead of the default")
73 parser.add_option("--mbr", dest="mbr", action="store_true",
74                   help="install master boot record (MBR) on the device")
75 parser.add_option("--quiet", dest="quiet", action="store_true",
76                   help="do not output anything than errors on console")
77 parser.add_option("--squashfs", dest="squashfs", action="store", type="string",
78                   help="install specified squashfs file instead of the default")
79 parser.add_option("--uninstall", dest="uninstall", action="store_true",
80                   help="remove grml ISO files")
81 parser.add_option("--verbose", dest="verbose", action="store_true",
82                   help="enable verbose mode")
83 parser.add_option("-v", "--version", dest="version", action="store_true",
84                   help="display version and exit")
85 (options, args) = parser.parse_args()
86
87
88 def cleanup():
89     """TODO
90     """
91
92     logging.info("Cleaning up")
93     proc = subprocess.Popen(["sync"])
94     proc.wait()
95
96     try:
97         for device in mounted:
98             unmount(device, "")
99     # ignore: RuntimeError: Set changed size during iteration
100     except:
101         pass
102
103 def get_function_name(obj):
104     if not (isroutine(obj) or isclass(obj)):
105         obj = type(obj)
106     return obj.__module__ + '.' + obj.__name__
107
108
109 def execute(f, *args):
110     """Wrapper for executing a command. Either really executes
111     the command (default) or when using --dry-run commandline option
112     just displays what would be executed."""
113     # demo: execute(subprocess.Popen, (["ls", "-la"]))
114     if options.dryrun:
115         logging.debug('dry-run only: %s(%s)' % (get_function_name(f), ', '.join(map(repr, args))))
116     else:
117         return f(*args)
118
119
120 def is_exe(fpath):
121     """Check whether a given file can be executed
122
123     @fpath: full path to file
124     @return:"""
125     return os.path.exists(fpath) and os.access(fpath, os.X_OK)
126
127
128 def which(program):
129     """Check whether a given program is available in PATH
130
131     @program: name of executable"""
132     fpath, fname = os.path.split(program)
133     if fpath:
134         if is_exe(program):
135             return program
136     else:
137         for path in os.environ["PATH"].split(os.pathsep):
138             exe_file = os.path.join(path, program)
139             if is_exe(exe_file):
140                 return exe_file
141
142     return None
143
144
145 def search_file(filename, search_path='/bin' + pathsep + '/usr/bin'):
146     """Given a search path, find file"""
147     file_found = 0
148     paths = search_path.split(pathsep)
149     for path in paths:
150         for current_dir, directories, files in os.walk(path):
151             if exists(join(current_dir, filename)):
152                 file_found = 1
153                 break
154     if file_found:
155         return abspath(join(current_dir, filename))
156     else:
157         return None
158
159
160 def check_uid_root():
161     """Check for root permissions"""
162     if not os.geteuid()==0:
163         sys.exit("Error: please run this script with uid 0 (root).")
164
165
166 def install_syslinux(device, dry_run=False):
167     # TODO
168     """Install syslinux on specified device."""
169
170     # syslinux -d boot/isolinux /dev/sdb1
171     logging.info("Installing syslinux as bootloader")
172     logging.debug("syslinux -d boot/syslinux %s" % device)
173     proc = subprocess.Popen(["syslinux", "-d", "boot/syslinux", device])
174     proc.wait()
175     if proc.returncode != 0:
176         raise Exception, "error executing syslinux"
177
178
179 def generate_grub_config(grml_flavour):
180     """Generate grub configuration for use via menu,lst"""
181
182     # TODO
183     # * install main part of configuration just *once* and append
184     #   flavour specific configuration only
185     # * what about systems using grub2 without having grub1 available?
186     # * support grub2?
187
188     return("""\
189 # misc options:
190 timeout 30
191 # color red/blue green/black
192 splashimage=/boot/grub/splash.xpm.gz
193 foreground  = 000000
194 background  = FFCC33
195
196 # define entries:
197 title %(grml_flavour)s  - Default boot (using 1024x768 framebuffer)
198 kernel /boot/release/%(grml_flavour)s/linux26 apm=power-off lang=us vga=791 quiet boot=live nomce module=%(grml_flavour)s
199 initrd /boot/release/%(grml_flavour)s/initrd.gz
200
201 # TODO: extend configuration :)
202 """ % locals())
203
204
205 def generate_isolinux_splash(grml_flavour):
206     """Generate bootsplash for isolinux/syslinux"""
207
208     # TODO
209     # * adjust last bootsplash line
210
211     grml_name = grml_flavour
212
213     return("""\
214 \ f17\f\18/boot/syslinux/logo.16
215
216 Some information and boot options available via keys F2 - F10. http://grml.org/
217 %(grml_name)s
218 """ % locals())
219
220 def generate_main_syslinux_config(grml_flavour, grml_bootoptions):
221     """Generate main configuration for use in syslinux.cfg"""
222
223     # TODO
224     # * install main part of configuration just *once* and append
225     #   flavour specific configuration only
226     # * unify isolinux and syslinux setup ("INCLUDE /boot/...")
227     #   as far as possible
228
229     local_datestamp = datestamp
230
231     return("""\
232 ## main syslinux configuration - generated by grml2usb [main config generated at: %(local_datestamp)s]
233 # use this to control the bootup via a serial port
234 # SERIAL 0 9600
235 DEFAULT grml
236 TIMEOUT 300
237 PROMPT 1
238 DISPLAY /boot/syslinux/boot.msg
239 F1 /boot/syslinux/boot.msg
240 F2 /boot/syslinux/f2
241 F3 /boot/syslinux/f3
242 F4 /boot/syslinux/f4
243 F5 /boot/syslinux/f5
244 F6 /boot/syslinux/f6
245 F7 /boot/syslinux/f7
246 F8 /boot/syslinux/f8
247 F9 /boot/syslinux/f9
248 F10 /boot/syslinux/f10
249 ## end of main configuration
250
251 # flavour specific configuration for grml
252 LABEL  grml
253 KERNEL /boot/release/%(grml_flavour)s/linux26
254 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(grml_bootoptions)s
255
256 """ % locals())
257
258 def generate_flavour_specific_syslinux_config(grml_flavour, bootoptions):
259     """Generate flavour specific configuration for use in syslinux.cfg"""
260
261     local_datestamp = datestamp
262
263     return("""\
264
265 # flavour specific configuration for %(grml_flavour)s [grml2usb for %(grml_flavour)s: %(local_datestamp)s]
266 LABEL  %(grml_flavour)s
267 KERNEL /boot/release/%(grml_flavour)s/linux26
268 APPEND initrd=/boot/release/%(grml_flavour)s/initrd.gz apm=power-off boot=live nomce quiet module=%(grml_flavour)s %(bootoptions)s
269 """ % locals())
270
271
272 def install_grub(device, dry_run=False):
273     """Install grub on specified device."""
274     logging.critical("TODO: grub-install %s"  % device)
275
276
277 def install_bootloader(partition, dry_run=False):
278     """Install bootloader on device."""
279
280     # Install bootloader on the device (/dev/sda),
281     # not on the partition itself (/dev/sda1)?
282 #    if partition[-1:].isdigit():
283 #        device = re.match(r'(.*?)\d*$', partition).group(1)
284 #    else:
285 #        device = partition
286
287     if options.grub:
288         install_grub(partition, dry_run)
289     else:
290         install_syslinux(partition, dry_run)
291
292
293 def is_writeable(device):
294     """Check if the device is writeable for the current user"""
295
296     if not device:
297         return False
298         #raise Exception, "no device for checking write permissions"
299
300     if not os.path.exists(device):
301         return False
302
303     return os.access(device, os.W_OK) and os.access(device, os.R_OK)
304
305 def install_mbr(device, dry_run=False):
306     """Install a default master boot record on given device
307
308     @device: device where MBR should be installed to"""
309
310     if not is_writeable(device):
311         raise IOError, "device not writeable for user"
312
313     lilo = '/grml/git/grml2usb/lilo/lilo.static' # FIXME
314
315     if not is_exe(lilo):
316         raise Exception, "lilo executable not available."
317
318     # to support -A for extended partitions:
319     logging.info("Installing MBR")
320     logging.debug("%s -S /dev/null -M %s ext" % (lilo, device))
321     proc = subprocess.Popen([lilo, "-S", "/dev/null", "-M", device, "ext"])
322     proc.wait()
323     if proc.returncode != 0:
324         raise Exception, "error executing lilo"
325
326     # activate partition:
327     logging.debug("%s -S /dev/null -A %s 1" % (lilo, device))
328     if not dry_run:
329         proc = subprocess.Popen([lilo, "-S", "/dev/null", "-A", device, "1"])
330         proc.wait()
331         if proc.returncode != 0:
332             raise Exception, "error executing lilo"
333
334     # lilo's mbr is broken, use the one from syslinux instead:
335     logging.debug("cat /usr/lib/syslinux/mbr.bin > %s" % device)
336     if not dry_run:
337         try:
338             # TODO -> use Popen instead?
339             retcode = subprocess.call("cat /usr/lib/syslinux/mbr.bin > "+ device, shell=True)
340             if retcode < 0:
341                 logging.critical("Error copying MBR to device (%s)" % retcode)
342         except OSError, error:
343             logging.critical("Execution failed:", error)
344
345
346 def register_tmpfile(path):
347     """TODO
348     """
349
350     tmpfiles.add(path)
351
352
353 def unregister_tmpfile(path):
354     """TODO
355     """
356
357     if path in tmpfiles:
358         tmpfiles.remove(path)
359
360
361 def register_mountpoint(target):
362     """TODO
363     """
364
365     mounted.add(target)
366
367
368 def unregister_mountpoint(target):
369     """TODO
370     """
371
372     if target in mounted:
373         mounted.remove(target)
374
375
376 def mount(source, target, options):
377     """Mount specified source on given target
378
379     @source: name of device/ISO that should be mounted
380     @target: directory where the ISO should be mounted to
381     @options: mount specific options"""
382
383 #   notice: dry_run does not work here, as we have to locate files, identify flavour,...
384     logging.debug("mount %s %s %s" % (options, source, target))
385     proc = subprocess.Popen(["mount"] + list(options) + [source, target])
386     proc.wait()
387     if proc.returncode != 0:
388         raise Exception, "Error executing mount"
389     else:
390         logging.debug("register_mountpoint(%s)" % target)
391         register_mountpoint(target)
392
393 def unmount(target, options):
394     """Unmount specified target
395
396     @target: target where something is mounted on and which should be unmounted
397     @options: options for umount command"""
398
399     # make sure we unmount only already mounted targets
400     target_unmount = False
401     mounts = open('/proc/mounts').readlines()
402     mountstring = re.compile(".*%s.*" % re.escape(target))
403     for line in mounts:
404         if re.match(mountstring, line):
405             target_unmount = True
406
407     if not target_unmount:
408         logging.debug("%s not mounted anymore" % target)
409     else:
410         logging.debug("umount %s %s" % (list(options), target))
411         proc = subprocess.Popen(["umount"] + list(options) + [target])
412         proc.wait()
413         if proc.returncode != 0:
414             raise Exception, "Error executing umount"
415         else:
416             logging.debug("unregister_mountpoint(%s)" % target)
417             unregister_mountpoint(target)
418
419
420 def check_for_usbdevice(device):
421     """Check whether the specified device is a removable USB device
422
423     @device: device name, like /dev/sda1 or /dev/sda
424     """
425
426     usbdevice = re.match(r'/dev/(.*?)\d*$', device).group(1)
427     usbdevice = os.path.realpath('/sys/class/block/' + usbdevice + '/removable')
428     if os.path.isfile(usbdevice):
429         is_usb = open(usbdevice).readline()
430         if is_usb == "1":
431             return 0
432         else:
433             return 1
434
435
436 def check_for_fat(partition):
437     """Check whether specified partition is a valid VFAT/FAT16 filesystem
438
439     @partition: device name of partition"""
440
441     try:
442         udev_info = subprocess.Popen(["/lib/udev/vol_id", "-t", partition],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
443         filesystem = udev_info.communicate()[0].rstrip()
444
445         if udev_info.returncode == 2:
446             raise Exception, "Failed to read device %s - wrong UID / permissions?" % partition
447
448         if filesystem != "vfat":
449             raise Exception, "Device %s does not contain a FAT16 partition" % partition
450
451     except OSError:
452         raise Exception, "Sorry, /lib/udev/vol_id not available."
453
454
455 def mkdir(directory):
456     """Simple wrapper around os.makedirs to get shell mkdir -p behaviour"""
457
458     if not os.path.isdir(directory):
459         try:
460             os.makedirs(directory)
461         except OSError:
462             # just silently pass as it's just fine it the directory exists
463             pass
464
465
466 def copy_grml_files(grml_flavour, iso_mount, target, dry_run=False):
467     """Copy files from ISO on given target"""
468
469     # TODO
470     # * provide alternative search_file() if file information is stored in a config.ini file?
471     # * catch "install: .. No space left on device" & CO
472     # * abstract copy logic to make the code shorter and get rid of spaghetti ;)
473
474     if not options.bootloaderonly:
475             logging.info("Copying files. This might take a while....")
476
477             squashfs = search_file(grml_flavour + '.squashfs', iso_mount)
478             squashfs_target = target + '/live/'
479             execute(mkdir, squashfs_target)
480
481             # use install(1) for now to make sure we can write the files afterwards as normal user as well
482             logging.debug("cp %s %s" % (squashfs, target + '/live/' + grml_flavour + '.squashfs'))
483             proc = execute(subprocess.Popen, ["install", "--mode=664", squashfs, squashfs_target + grml_flavour + ".squashfs"])
484             proc.wait()
485
486             filesystem_module = search_file('filesystem.module', iso_mount)
487             logging.debug("cp %s %s" % (filesystem_module, squashfs_target + grml_flavour + '.module'))
488             proc = execute(subprocess.Popen, ["install", "--mode=664", filesystem_module, squashfs_target + grml_flavour + '.module'])
489             proc.wait()
490
491             release_target = target + '/boot/release/' + grml_flavour
492             execute(mkdir, release_target)
493
494             kernel = search_file('linux26', iso_mount)
495             logging.debug("cp %s %s" % (kernel, release_target + '/linux26'))
496             proc = execute(subprocess.Popen, ["install", "--mode=664", kernel, release_target + '/linux26'])
497             proc.wait()
498
499             initrd = search_file('initrd.gz', iso_mount)
500             logging.debug("cp %s %s" % (initrd, release_target + '/initrd.gz'))
501             proc = execute(subprocess.Popen, ["install", "--mode=664", initrd, release_target + '/initrd.gz'])
502             proc.wait()
503
504     if not options.copyonly:
505         syslinux_target = target + '/boot/syslinux/'
506         execute(mkdir, syslinux_target)
507
508         logo = search_file('logo.16', iso_mount)
509         logging.debug("cp %s %s" % (logo, syslinux_target + 'logo.16'))
510         proc = execute(subprocess.Popen, ["install", "--mode=664", logo, syslinux_target + 'logo.16'])
511         proc.wait()
512
513         for ffile in 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10':
514             bootsplash = search_file(ffile, iso_mount)
515             logging.debug("cp %s %s" % (bootsplash, syslinux_target + ffile))
516             proc = execute(subprocess.Popen, ["install", "--mode=664", bootsplash, syslinux_target + ffile])
517             proc.wait()
518
519         grub_target = target + '/boot/grub/'
520         execute(mkdir, grub_target)
521
522         logging.debug("cp /grml/git/grml2usb/grub/splash.xpm.gz %s" % grub_target + 'splash.xpm.gz') # FIXME - path of grub
523         proc = execute(subprocess.Popen, ["install", "--mode=664", '/grml/git/grml2usb/grub/splash.xpm.gz', grub_target + 'splash.xpm.gz']) # FIXME
524         proc.wait()
525
526         logging.debug("cp /grml/git/grml2usb/grub/stage2_eltorito to %s" % grub_target + 'stage2_eltorito') # FIXME - path of grub
527         proc = execute(subprocess.Popen, ["install", "--mode=664", '/grml/git/grml2usb/grub/stage2_eltorito', grub_target + 'stage2_eltorito']) # FIXME
528         proc.wait()
529
530         if not dry_run:
531             logging.debug("Generating grub configuration")
532             #with open("...", "w") as f:
533             #f.write("bla bla bal")
534             grub_config_file = open(grub_target + 'menu.lst', 'w')
535             grub_config_file.write(generate_grub_config(grml_flavour))
536             grub_config_file.close()
537
538             logging.info("Generating syslinux configuration")
539             syslinux_cfg = syslinux_target + 'syslinux.cfg'
540
541             # install main configuration only *once*, no matter how many ISOs we have:
542             if os.path.isfile(syslinux_cfg):
543                 string = open(syslinux_cfg).readline()
544                 main_identifier = re.compile(".*main config generated at: %s.*" % re.escape(str(datestamp)))
545                 if not re.match(main_identifier, string):
546                     syslinux_config_file = open(syslinux_cfg, 'w')
547                     logging.info("Notice: grml flavour %s is being installed as the default booting system." % grml_flavour)
548                     syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
549                     syslinux_config_file.close()
550             else:
551                     syslinux_config_file = open(syslinux_cfg, 'w')
552                     syslinux_config_file.write(generate_main_syslinux_config(grml_flavour, options.bootoptions))
553                     syslinux_config_file.close()
554
555
556             # install flavour specific configuration only *once* as well
557             # ugly - I'm pretty sure this could be smoother...
558             flavour_config = True
559             if os.path.isfile(syslinux_cfg):
560                 string = open(syslinux_cfg).readlines()
561                 logging.info("Notice: you can boot flavour %s using '%s' on the commandline." % (grml_flavour, grml_flavour))
562                 flavour = re.compile("grml2usb for %s: %s" % (re.escape(grml_flavour), re.escape(str(datestamp))))
563                 for line in string:
564                     if flavour.match(line):
565                         flavour_config = False
566
567
568             if flavour_config:
569                 syslinux_config_file = open(syslinux_cfg, 'a')
570                 syslinux_config_file.write(generate_flavour_specific_syslinux_config(grml_flavour, options.bootoptions))
571                 syslinux_config_file.close( )
572
573             logging.debug("Generating isolinux/syslinux splash %s" % syslinux_target + 'boot.msg')
574             isolinux_splash = open(syslinux_target + 'boot.msg', 'w')
575             isolinux_splash.write(generate_isolinux_splash(grml_flavour))
576             isolinux_splash.close( )
577
578
579     # make sure we sync filesystems before returning
580     proc = subprocess.Popen(["sync"])
581     proc.wait()
582
583 def uninstall_files(device):
584     """Get rid of all grml files on specified device"""
585
586     # TODO
587     logging.critical("TODO: %s" % device)
588
589
590 def identify_grml_flavour(mountpath):
591     """Get name of grml flavour
592
593     @mountpath: path where the grml ISO is mounted to
594     @return: name of grml-flavour"""
595
596     version_file = search_file('grml-version', mountpath)
597
598     if version_file == "":
599         logging.critical("Error: could not find grml-version file.")
600         raise
601
602     try:
603         tmpfile = open(version_file, 'r')
604         grml_info = tmpfile.readline()
605         grml_flavour = re.match(r'[\w-]*', grml_info).group()
606     except TypeError:
607         raise
608     except:
609         logging.critical("Unexpected error:", sys.exc_info()[0])
610         raise
611
612     return grml_flavour
613
614 def handle_iso(iso, device):
615     """TODO
616     """
617
618     logging.info("Using ISO %s" % iso)
619
620     if os.path.isdir(iso):
621         logging.critical("TODO: /live/image handling not yet implemented") # TODO
622     else:
623         iso_mountpoint = tempfile.mkdtemp()
624         register_tmpfile(iso_mountpoint)
625         remove_iso_mountpoint = True
626
627         mount(iso, iso_mountpoint, ["-o", "loop", "-t", "iso9660"])
628
629         if os.path.isdir(device):
630             logging.info("Specified target is a directory, not mounting therefore.")
631             device_mountpoint = device
632             remove_device_mountpoint = False
633             skip_mbr = True
634
635         else:
636             device_mountpoint = tempfile.mkdtemp()
637             register_tmpfile(device_mountpoint)
638             remove_device_mountpoint = True
639             try:
640                 mount(device, device_mountpoint, "")
641             except Exception, error:
642                 logging.critical("Fatal: %s" % error)
643                 cleanup()
644
645         try:
646             grml_flavour = identify_grml_flavour(iso_mountpoint)
647             logging.info("Identified grml flavour \"%s\"." % grml_flavour)
648             copy_grml_files(grml_flavour, iso_mountpoint, device_mountpoint, dry_run=options.dryrun)
649         except TypeError:
650             logging.critical("Fatal: a critical error happend during execution, giving up")
651             sys.exit(1)
652         finally:
653             if os.path.isdir(iso_mountpoint) and remove_iso_mountpoint:
654                 unmount(iso_mountpoint, "")
655
656                 os.rmdir(iso_mountpoint)
657                 unregister_tmpfile(iso_mountpoint)
658
659             if remove_device_mountpoint:
660                 unmount(device_mountpoint, "")
661
662                 if os.path.isdir(device_mountpoint):
663                     os.rmdir(device_mountpoint)
664                     unregister_tmpfile(device_mountpoint)
665
666         # grml_flavour_short = grml_flavour.replace('-','')
667         # logging.debug("grml_flavour_short = %s" % grml_flavour_short)
668
669
670 def main():
671     """Main function [make pylint happy :)]"""
672
673     if options.version:
674         print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
675         sys.exit(0)
676
677     if len(args) < 2:
678         parser.error("invalid usage")
679
680     # log handling
681     if options.verbose:
682         FORMAT = "%(asctime)-15s %(message)s"
683         logging.basicConfig(level=logging.DEBUG, format=FORMAT)
684     elif options.quiet:
685         FORMAT = "Critial: %(message)s"
686         logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
687     else:
688         FORMAT = "Info: %(message)s"
689         logging.basicConfig(level=logging.INFO, format=FORMAT)
690
691     if options.dryrun:
692         logging.info("Running in simulate mode as requested via option dry-run.")
693     else:
694         check_uid_root()
695
696     # specified arguments
697     device = args[len(args) - 1]
698     isos = args[0:len(args) - 1]
699
700     # make sure we can replace old grml2usb script and warn user when using old way of life:
701     if device.startswith("/mnt/external") or device.startswith("/mnt/usb"):
702         print "Warning: the semantics of grml2usb has changed."
703         print "Instead of using grml2usb /path/to/iso %s you might" % device
704         print "want to use grml2usb /path/to/iso /dev/... instead."
705         print "Please check out the grml2usb manpage for details."
706         f = raw_input("Do you really want to continue? y/N ")
707         if f == "y" or f == "Y":
708            pass
709         else:
710             sys.exit(1)
711
712     # make sure we have syslinux available
713     if not which("syslinux") and not options.copyonly:
714         logging.critical('Sorry, syslinux not available. Exiting.')
715         logging.critical('Please install syslinux or consider using the --grub option.')
716         sys.exit(1)
717
718     # check for vfat filesystem
719     if device is not None and not os.path.isdir(device):
720         try:
721             check_for_fat(device)
722         except Exception, error:
723             logging.critical("Execution failed: %s", error)
724             sys.exit(1)
725
726     if not check_for_usbdevice(device):
727         print "Warning: the specified device %s does not look like a removable usb device." % device
728         f = raw_input("Do you really want to continue? y/N ")
729         if f == "y" or f == "Y":
730            pass
731         else:
732             sys.exit(1)
733
734     # main operation (like installing files)
735     for iso in isos:
736         handle_iso(iso, device)
737
738     # install MBR
739     if not options.mbr or skip_mbr:
740         logging.info("You are not using the --mbr option. Consider using it to get a working USB setup.")
741     else:
742         # make sure we install MBR on /dev/sdX and not /dev/sdX#
743         if device[-1:].isdigit():
744             mbr_device = re.match(r'(.*?)\d*$', device).group(1)
745
746         try:
747             install_mbr(mbr_device, dry_run=options.dryrun)
748         except IOError, error:
749             logging.critical("Execution failed: %s", error)
750             sys.exit(1)
751         except Exception, error:
752             logging.critical("Execution failed: %s", error)
753             sys.exit(1)
754
755     # Install bootloader only if not using the --copy-only option
756     if options.copyonly:
757         logging.info("Not installing bootloader and its files as requested via option copyonly.")
758     else:
759         install_bootloader(device, dry_run=options.dryrun)
760
761     # finally be politely :)
762     logging.info("Finished execution of grml2usb (%s). Have fun with your grml system." % PROG_VERSION)
763
764 if __name__ == "__main__":
765     try:
766         main()
767     except KeyboardInterrupt:
768         logging.info("Received KeyboardInterrupt")
769         cleanup()
770
771 ## END OF FILE #################################################################
772 # vim:foldmethod=marker expandtab ai ft=python tw=120 fileencoding=utf-8