]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/scripts/dvd-handler.in
kes Implement --without-qwt
[bacula/bacula] / bacula / scripts / dvd-handler.in
index e25893284a96e3539041526dfd1a59299d332ec1..a02878eaf23aa9fff542ae3e6f0e1844a2c1a09b 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!@PYTHON@
 #
 # Check the free space available on a writable DVD
 # Should always exit with 0 status, otherwise it indicates a serious error.
 #
 #  called:  dvd-handler <dvd-device-name> operation args
 #
-#  where operation is one of
-#    free
-#    write
+#  operations used by Bacula:
 #
-#  further arguments:
-#    free: one argument: 0 to keep the existing data on disk, i.e.
-#         free space is measured
-#                       anything else: overwrite entire disk, so
-#         free space is always maximum space
+#   free  (no arguments)
+#            Scan the device and report the available space. It returns:
+#            Prints on the first output line the free space available in bytes.
+#            If an error occurs, prints a negative number (-errno), followed,
+#            on the second line, by an error message.
+#
+#   write  op filename
+#             Write a part file to disk.
+#             This operation needs two additional arguments.
+#             The first (op) indicates to
+#                 0 -- append
+#                 1 -- first write to a blank disk
+#                 2 -- blank or truncate a disk
+#
+#              The second is the filename to write
+#
+#   operations available but not used by Bacula:
+#
+#   test      Scan the device and report the information found.
+#             This operation needs no further arguments.
+#   prepare   Prepare a DVD+/-RW for being used by Bacula.
+#             Note: This is only useful if you already have some
+#             non-Bacula data on a medium, and you want to use
+#             it with Bacula. Don't run this on blank media, it
+#             is useless.
 #
-# in case of operation ``free'' returns:
-# Prints on the first output line the free space available in bytes.
-# If an error occurs, prints a negative number (-errno), followed,
-# on the second line, by an error message.
 # 
 # $Id$
 #
 
-# end of configurable values
-
 import popen2
 import os
+import os.path
 import errno
 import sys
 import re
 import signal
 import time
+import array
 
 class disk:
-   # Configurable values:
-   df = "@DF@ -P"
+# Configurable values:
+   
    dvdrwmediainfo = "@DVDRWMEDIAINFO@"
-   growisofs = "@GROWISOFS@"
+   growcmd = "@GROWISOFS@"
+   dvdrwformat = "@DVDRWFORMAT@"
+   dd = "@DD@"
    margin = 10485760 # 10 mb security margin
-   # We disable 4GB boundary checking - function free should handle this
-   # already, and it doesn't seem to work anyway (here: 2.6.8-24.18 from SuSE,
-   # LG GSA-5163D, dvd+rw-tools 5.21
-   growcmd = growisofs + " -use-the-force-luke=notray -use-the-force-luke=4gms "
-   growcmd += "-A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
-             "-publisher 'ITS Lehmann info@its-lehmann.de' " + \
-             "-p 'dvd-handler / growisofs' -sysid 'BACULADATA'"
+
+   # Comment the following line if you want the tray to be reloaded
+   # when writing ends.
+   growcmd += " -use-the-force-luke=notray"
+
+# end of configurable values
 
 ###############################################################################
 #
-# This class represents a DVD disk (various flavours).
+# This class represents DVD disk informations.
 # When instantiated, it needs a device name.
-# Status information about the device and the disk loaded is collected.
+# Status information about the device and the disk loaded is collected only when
+# asked for (for example dvd-freespace doesn't need to know the media type, and
+# dvd-writepart doesn't not always need to know the free space).
 #
 # The following methods are implemented:
-# __init__     we need that...
-#              NOTE: Currently, this class only works with DVD+RW
-#              and simply refuses to work with anything else.
-#              This is because I had to start with some sort of disk,
-#              and I learned that +RW and -RW are quite different, so
-#              I decided to play it safe.
-# __repr__     this seems to be a good idea to have.
-#              Quite minimalistic implementation, though.
-# __str__      For casts to string. Return the current disk information
-# is_empty     Returns TRUE if the disk is empty, blank... this needs more
-#              work, especially concerning non-RW media and blank vs. no
-#              filesystem considerations. Here, we should also look for
-#              other filesystems - probably we don't want to silently
-#              overwrite UDF or ext2 or anything not mentioned in fstab...
-# is_RW        TRUE if disk is rewritable. We need that to determine if
-#              a new filesystem can be written onto a used disk.
-# lasterr      returns a string describing the last error in the class.
-#              Valuable after creating the object and after free()
-# free         Returns the available free space, distinguishing between
-#              new volume disks (overwrite everything) and appending
-#              There are some assumtions about disk status and usage that
-#              need work.
-# write        Writes one part file to disk, either starting a new file
-#              system on disk, or appending to it.
-#              This method should also prepare a blank disk so that a
-#              certain part of the disk is used to allow detection of a
-#              used disk by all / more disk drives.
-# blank        NOT IMPLEMENTED
+# __init__      we need that...
+# __repr__      this seems to be a good idea to have.
+#               Quite minimalistic implementation, though.
+# __str__       For casts to string. Return the current disk information
+# is_empty      Returns TRUE if the disk is empty, blank... this needs more
+#               work, especially concerning non-RW media and blank vs. no
+#               filesystem considerations. Here, we should also look for
+#               other filesystems - probably we don't want to silently
+#               overwrite UDF or ext2 or anything not mentioned in fstab...
+#               (NB: I don't think it is a problem)
+# free          Returns the available free space.
+# write         Writes one part file to disk, either starting a new file
+#               system on disk, or appending to it.
+#               This method should also prepare a blank disk so that a
+#               certain part of the disk is used to allow detection of a
+#               used disk by all / more disk drives.
+# prepare       Blank the device
 #
 ###############################################################################
    def __init__(self, devicename):
-      self.device = "none"
+      self.device = devicename
       self.disktype = "none"
-      self.leadout = -1
-      self.track = -1
-      self.maximum = -1
-      self.used = 0
-      self.lasterror = "none"
+      self.diskmode = "none"
+      self.diskstatus = "none"
       self.hardwaredevice = "none"
       self.pid = 0
+      self.next_session = -1
+      self.capacity = -1
 
-      # first, we collect information about the media as reported by
-      # dvd+rw-mediainfo
-      # we need an indication of the usable total size.
+      self.freespace_collected = 0
+      self.mediumtype_collected = 0
+
+      self.growcmd += " -quiet"
+
+      if self.is4gbsupported():
+        self.growcmd += " -use-the-force-luke=4gms"
+
+      self.growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
+                       "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
 
-      self.cmd = self.dvdrwmediainfo + " " + devicename
-      self.processi = popen2.Popen4(self.cmd)
-      self.status = self.processi.wait()
-      if not os.WIFEXITED(self.status):
-        self.lasterror = self.dvdrwmediainfo + " process did not exit correctly."
-        return
-      if os.WEXITSTATUS(self.status) != 0:
-        self.lasterror = "Cannot get media info from " + self.dvdrwmediainfo
-        return
-      self.device = str(devicename)
-      self.result = self.processi.fromchild.read()
-      self.hardware = re.search(r"INQUIRY:\s+(.*)\n", self.result, re.MULTILINE)
-      self.mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s",
-        self.result, re.MULTILINE)
-      self.tracksize = re.search(r"\sTrack Size:\s+(\d+)\*2KB\s",
-        self.result, re.MULTILINE)
-      self.leadout = re.search(r"\sLegacy lead-out at:\s+(\d+)\*2KB=(\d+)\s",
-        self.result, re.MULTILINE)
-      if self.hardware:
-        self.hardwaredevice = self.hardware.group(1)
-      if self.mediatype:
-        self.disktype = self.mediatype.group(2)
-      else:
-        self.lasterror = "Media type not found."
-      if self.leadout:
-        self.leadout = long(self.leadout.group(1))*2048
-      else:
-        self.lasterror = "Lead-out block not found."
-      if self.tracksize:
-        self.track = long(self.tracksize.group(1))*2048
-      else:
-        self.lasterror = "Track size not found."
-      self.result = 0
-      if ( "DVD+RW" == self.disktype ):
-        if self.leadout > self.track:
-           self.result = self.leadout
-        else:
-           self.result = self.track
-      else:
-        self.lasterror = "Unsupported media: " + self.disktype
-      self.maximum = self.result - self.margin
-      if self.maximum < 0:
-        self.maximum = 0
-
-      # now, the actual size used on the disk.
-      # here, we use what df reports, although the
-      # current track size should give us the necessary information,
-      # too. Well, depending on the media type, it seems.
-      # We should see if the media is mounted, try to mount, if possible
-      # proceed and, if necessary, unmount. Otherwise assume 0 used bytes.
-      # __init__ and __del__ would be the right places to mount and
-      # unmount - mounting before df'ing is always a good idea,
-      # and setting the previos state might be important.
-
-      self.cmd = self.df + " " + self.device
-      self.process = popen2.Popen4(self.cmd)
-      self.status = self.process.wait()
-      if not os.WIFEXITED(self.status):
-        self.lasterror = self.df + " process did not not exit correctly."
-        return
-      self.exitstat = os.WEXITSTATUS(self.status) & ~0x80
-      if self.exitstat == errno.ENOSPC:
-        self.used = 0
-      elif self.exitstat != 0:
-        self.lasterror = os.strerror(self.exitstat)
-        return
-      self.dftext = self.process.fromchild.read()
-      self.blocks = re.search(self.device + r"\s+(\d+)\s+",
-        self.dftext, re.MULTILINE)
-      if self.blocks:
-        self.used = long(self.blocks.group(1))*1024
-      else:
-        self.used = 0
-        self.lasterror = "No blocks found in " + self.cmd + " output:\n"
-        self.lasterror += self.dftext
       return
 
    def __repr__(self):
       return "disk(" + self.device + ") # This is an instance of class disk"
 
    def __str__(self):
-      self.me  = "Class disk, initialized with device " + self.device + "\n"
-      self.me += "type = " + self.disktype + " leadout = " + str(self.leadout)
-      self.me += " track = " + str(self.track) + " maximum = " + str(self.maximum) + "\n"
-      self.me += "used = " + str(self.used) + "\n"
-      self.me += "Hardware device is " + self.hardwaredevice + "\n"
-      self.me += "last error = " + self.lasterror + "\n"
+      if not self.freespace_collected:
+        self.collect_freespace();
+      if not self.mediumtype_collected:
+        self.collect_mediumtype();
+      
+      self.me  = "Class disk, initialized with device '" + self.device + "'\n"
+      self.me += "type = '" + self.disktype + "' mode='" + self.diskmode + "' status = '" + self.diskstatus + "'\n"
+      self.me += " next_session = " + str(self.next_session) + " capacity = " + str(self.capacity) + "\n"
+      self.me += "Hardware device is '" + self.hardwaredevice + "'\n"
+      self.me += "growcmd = '" + self.growcmd + "'\ngrowparams = '" + self.growparams + "'\n"
       return self.me
 
+   ## Check if we want to allow growisofs to cross the 4gb boundary
+   def is4gbsupported(self):
+      processi = popen2.Popen4("uname -s -r")
+      status = processi.wait()
+      if not os.WIFEXITED(status):
+        return 1
+      if os.WEXITSTATUS(status) != 0:
+        return 1
+      strres = processi.fromchild.readline()[0:-1]
+      version = re.search(r"Linux (\d+)\.(\d+)\.(\d+)", strres)
+      if not version: # Non-Linux: allow
+        return 1
+      
+      if (int(version.group(1)) > 2) or (int(version.group(2)) > 6) or ((int(version.group(1)) == 2) and (int(version.group(2)) == 6) and (int(version.group(3)) >= 8)):
+        return 1
+      else:
+        return 0
+
+   def collect_freespace(self): # Collects current free space
+      self.cmd = self.growcmd + " -F " + self.device
+      processi = popen2.Popen4(self.cmd)
+      status = processi.wait()
+      if not os.WIFEXITED(status):
+        raise DVDError(0, "growisofs process did not exit correctly.")
+      result = processi.fromchild.read()
+      if os.WEXITSTATUS(status) != 0:
+        if (os.WEXITSTATUS(status) & 0x7F) == errno.ENOSPC:
+           # Kludge to force dvd-handler to return a free space of 0
+           self.next_session = 1
+           self.capacity = 1
+           self.freespace_collected = 1
+           return
+        else:
+           raise DVDError(os.WEXITSTATUS(status), "growisofs returned with an error " + result + ". Please check your are using a patched version of dvd+rw-tools.")
+      next_sess = re.search(r"\snext_session=(\d+)\s", result, re.MULTILINE)
+      capa = re.search(r"\scapacity=(\d+)\s", result, re.MULTILINE)
+   
+      if next_sess and capa:
+        self.next_session = long(next_sess.group(1))
+        self.capacity = long(capa.group(1))
+        
+        # testing cheat (emulate 4GB boundary at 100MB)
+        #if self.next_session > 100000000:
+        #   self.capacity = self.next_session
+      else:
+        raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
+      
+      self.freespace_collected = 1
+      return
+   
+   def collect_mediumtype(self): # Collects current medium type
+      self.lasterror = ""
+      cmd = self.dvdrwmediainfo + " " + self.device
+      processi = popen2.Popen4(cmd)
+      status = processi.wait()
+      if not os.WIFEXITED(status):
+        raise DVDError(0, self.dvdrwmediainfo + " process did not exit correctly.")
+      if os.WEXITSTATUS(status) != 0:
+        raise DVDError(0, "Cannot get media info from " + self.dvdrwmediainfo)
+        return
+      result = processi.fromchild.read()
+      
+      hardware = re.search(r"INQUIRY:\s+(.*)\n", result, re.MULTILINE)
+      mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s", result, re.MULTILINE)
+      mediamode = re.search(r"\sMounted Media:\s+[0-9A-F]{2}h, \S* (.*)\n", result, re.MULTILINE)
+      status = re.search(r"\sDisc status:\s+(.*)\n", result, re.MULTILINE)
+      
+      if hardware:
+        self.hardwaredevice = hardware.group(1)
+      
+      if mediatype:
+        self.disktype = mediatype.group(2)
+      else:
+        raise DVDError(0, "Media type not found in " + self.dvdrwmediainfo + " output")
+      
+      if self.disktype == "DVD-RW":
+        if mediamode:
+           self.diskmode = mediamode.group(1)
+        else:
+           raise DVDError(0, "Media mode not found for DVD-RW in " + self.dvdrwmediainfo + " output")
+      
+      if status:
+        self.diskstatus = status.group(1)
+      else:
+        raise DVDError(0, "Disc status not found in " + self.dvdrwmediainfo + " output")
+
+      
+      self.mediumtype_collected = 1
+      return
+
    def is_empty(self):
-      return 0 == self.used
-   # This works for DVD+RW, probably for all rewritable media. self.blank for -R?
-   # This is quite definitely not the best method. I need something
-   # that detects if a session exists on disk, but I didn't do any
-   # experiments with non-RW media yet.
+      if not self.freespace_collected:
+        self.collect_freespace();
+      
+      return 0 == self.next_session
 
    def is_RW(self):
+      if not self.mediumtype_collected:
+        self.collect_mediumtype();
       return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
 
-   def free(self, newvol):
-      if self.used < 0 or self.maximum <= 0:
-        return -1
-      elif newvol:
-        self.ret = self.maximum
-        if not self.is_empty() and not self.is_RW():
-           # better check real disks usage state as read from dvd+rw-mediainfo.
-           # introduce self.blank
-           self.ret = -1
-           self.lasterror = self.disktype + " can not be overwritten and is already used"
-      else:
-        self.ret = self.maximum - self.used
-        if self.used > 4278190080:      # if more than 4GB-16MB are already used
-           self.ret = 0
-      return self.ret
+   def is_plus_RW(self):
+      if not self.mediumtype_collected:
+        self.collect_mediumtype();
+      return "DVD+RW" == self.disktype
+
+   def is_minus_RW(self):
+      if not self.mediumtype_collected:
+        self.collect_mediumtype();
+      return "DVD-RW" == self.disktype
+      
+   def is_restricted_overwrite(self):
+      if not self.mediumtype_collected:
+        self.collect_mediumtype();
+      return self.diskmode == "Restricted Overwrite"
 
-   def lasterr(self):
-      return self.lasterror
+   def is_blank(self):
+      if not self.mediumtype_collected:
+        self.collect_mediumtype();
+      
+      return self.diskstatus == "blank"
+
+   def free(self):
+      if not self.freespace_collected:
+        self.collect_freespace();
+      
+      fr = self.capacity-self.next_session-self.margin
+      if fr < 0:
+        return 0
+      else:
+        return fr
 
    def term_handler(self, signum, frame):
       print 'dvd-handler: Signal term_handler called with signal', signum
@@ -229,105 +273,226 @@ class disk:
         sys.exit(1)
 
    def write(self, newvol, partfile):
-      self.lasterror = "none"
-      self.partstat = os.stat(partfile)
-      if not self.partstat:
-        self.lasterror = "Could not stat " + str(partfile) + " which is a fatal error."
-        return
-      if self.partstat.st_size > self.free(newvol):
-        self.lasterror = "Part " + str(partfile) + " is too big: " \
-           + str(self.partstat.st_size) + ", free " + str(self.free(newvol))
+      # Blank DVD+RW when there is no data on it
+      if newvol and self.is_plus_RW() and self.is_blank():
+        print "DVD+RW looks brand-new, blank it to fix some DVD-writers bugs."
+        self.blank()
+        print "Done, now writing the part file."
+      
+      if newvol and self.is_minus_RW() and (not self.is_restricted_overwrite()):
+        print "DVD-RW is in " + self.diskmode + " mode, reformating it to Restricted Overwrite"
+        self.reformat_minus_RW()
+        print "Done, now writing the part file."
+      
+      cmd = self.growcmd + self.growparams
+      if newvol:
+        # Ignore any existing iso9660 filesystem - used for truncate
+        if newvol == 2:
+            cmd += " -use-the-force-luke=tty"
+        cmd += " -Z "
+      else:
+        cmd += " -M "
+      cmd += self.device + " " + str(partfile)
+      print "Running " + cmd
+      oldsig = signal.signal(signal.SIGTERM, self.term_handler)
+      proc = popen2.Popen4(cmd)
+      self.pid = proc.pid
+      status = proc.poll()
+      while status == -1:
+        line = proc.fromchild.readline()
+        while len(line) > 0:
+           print line,
+           line = proc.fromchild.readline()
+        time.sleep(1)
+        status = proc.poll()
+      self.pid = 0
+      print
+      signal.signal(signal.SIGTERM, oldsig)
+      if not os.WIFEXITED(status):
+        raise DVDError(0, cmd + " process did not exit correctly, signal/status " + str(status))
+      if os.WEXITSTATUS(status) != 0:
+        raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
+
+   def prepare(self):
+      if not self.is_RW():
+        raise DVDError(0, "I won't prepare a non-rewritable medium")
+      
+      # Blank DVD+RW when there is no data on it
+      if self.is_plus_RW() and self.is_blank():
+        print "DVD+RW looks brand-new, blank it to fix some DVD-writers bugs."
+        self.blank()
+        return # It has been completely blanked: Medium is ready to be used by Bacula
+      
+      if self.is_minus_RW() and (not self.is_restricted_overwrite()):
+        print "DVD-RW is in " + self.diskmode + " mode, reformating it to Restricted Overwrite"
+        self.reformat_minus_RW()
+        return # Reformated: Medium is ready to be used by Bacula
+      
+      # TODO: Check if /dev/fd/0 and /dev/zero exists, otherwise, run self.blank()
+      if not os.path.exists("/dev/fd/0") or not os.path.exists("/dev/zero"):
+        print "/dev/fd/0 or /dev/zero doesn't exist, blank the medium completely."
+        self.blank()
         return
-      if "DVD-RW" == self.disktype:
-        self.cmd = self.growcmd + " -R "
-        if newvol:
-           self.cmd += "-Z "
-        else:
-           self.cmd += "-M "
-        self.cmd += self.device + " " + str(partfile)
-        self.oldsig = signal.signal(signal.SIGTERM, self.term_handler)
-        self.proc = popen2.Popen4(self.cmd)
-        self.pid = self.proc.pid
-        self.status = self.proc.poll()
-        while -1 == self.status:
-           self.out = self.proc.fromchild.read(512)
-           while "" != self.out:
-              sys.stdout.write(self.out)
-              self.out = self.proc.fromchild.read(512)
-           time.sleep(1)
-           self.status = self.proc.poll()
-        self.pid = 0
-        print
-        signal.signal(signal.SIGTERM, self.oldsig)
-        if 0 != os.WEXITSTATUS(self.status):
-           self.lasterror = self.cmd + " exited with status " + str(os.WEXITSTATUS(self.status))
-           print self.cmd + " exited with signal/status " + hex(self.status)         
-      else:    # Other disk type
-        self.lasterror = "Can't write to " + self.disktype
+      
+      cmd = self.dd + " if=/dev/zero bs=1024 count=512 | " + self.growcmd + " -Z " + self.device + "=/dev/fd/0"
+      print "Running " + cmd
+      oldsig = signal.signal(signal.SIGTERM, self.term_handler)
+      proc = popen2.Popen4(cmd)
+      self.pid = proc.pid
+      status = proc.poll() 
+      while status == -1:
+        line = proc.fromchild.readline()
+        while len(line) > 0:
+           print line,
+           line = proc.fromchild.readline()
+        time.sleep(1)
+        status = proc.poll()
+      self.pid = 0
+      print
+      signal.signal(signal.SIGTERM, oldsig)
+      if os.WEXITSTATUS(status) != 0:
+        raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
+
+   def blank(self):
+      cmd = self.growcmd + " -Z " + self.device + "=/dev/zero"
+      print "Running " + cmd
+      oldsig = signal.signal(signal.SIGTERM, self.term_handler)
+      proc = popen2.Popen4(cmd)
+      self.pid = proc.pid
+      status = proc.poll()
+      while status == -1:
+        line = proc.fromchild.readline()
+        while len(line) > 0:
+           print line,
+           line = proc.fromchild.readline()
+        time.sleep(1)
+        status = proc.poll()
+      self.pid = 0
+      print
+      signal.signal(signal.SIGTERM, oldsig)
+      if os.WEXITSTATUS(status) != 0:
+        raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
+
+   def reformat_minus_RW(self):
+      cmd = self.dvdrwformat + " -force " + self.device
+      print "Running " + cmd
+      oldsig = signal.signal(signal.SIGTERM, self.term_handler)
+      proc = popen2.Popen4(cmd)
+      self.pid = proc.pid
+      status = proc.poll()
+      while status == -1:
+        line = proc.fromchild.readline()
+        while len(line) > 0:
+           print line,
+           line = proc.fromchild.readline()
+        time.sleep(1)
+        status = proc.poll()
+      self.pid = 0
+      print
+      signal.signal(signal.SIGTERM, oldsig)
+      if os.WEXITSTATUS(status) != 0:
+        raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
+
 # class disk ends here.
 
+class DVDError(Exception):
+   def __init__(self, errno, value):
+      self.errno = errno
+      self.value = value
+      if self.value[-1] == '\n':
+        self.value = self.value[0:-1]
+   def __str__(self):
+      return str(self.value) + " || errno = " + str(self.errno) + " (" + os.strerror(self.errno & 0x7F) + ")"
 
-if len(sys.argv) < 3:
+def usage():
    print "Wrong number of arguments."
    print """
-This program needs to be called with the following parameters.
+Usage:
 
-device operation [more arguments]
+dvd-handler DEVICE test
+dvd-handler DEVICE free
+dvd-handler DEVICE write APPEND FILE
+dvd-handler DEVICE prepare
 
-where device is a device name like /dev/sr0 or /dev/dvd and
-operation can be "test", "free" or "write".
+where DEVICE is a device name like /dev/sr0 or /dev/dvd.
 
 Operations:
-test      Scan the device and report the information found.
+test     Scan the device and report the information found.
           This operation needs no further arguments.
-free      Scan the device and report the available space.
-          "free" needs one additional argument to determine
-          if data is to be appended or if the disk will be
-          started from the beginning: "0" means append,
-          everything else indicates a new volume.
-write     Write a part file to disk.
+free     Scan the device and report the available space.
+write    Write a part file to disk.
           This operation needs two additional arguments.
-          The first indicates to append or restart the
-          disk; see above. The second is the file to write.
+          The first indicates to append (0), restart the
+          disk (1) or restart existing disk (2). The second
+          is the file to write.
+prepare   Prepare a DVD+/-RW for being used by Bacula.
+          Note: This is only useful if you already have some
+          non-Bacula data on a medium, and you want to use
+          it with Bacula. Don't run this on blank media, it
+          is useless.
 """
    sys.exit(1)
 
+if len(sys.argv) < 3:
+   usage()
+
 dvd = disk(sys.argv[1])
 
 if "free" == sys.argv[2]:
-   if len(sys.argv) == 4:
-      newvol = 1
-      if "0" == sys.argv[3]:
-        newvol = 0
-      free = dvd.free(newvol)
-      print free
-      if free >= 0:
+   if len(sys.argv) == 3:
+      try:
+        free = dvd.free()
+      except DVDError, e:
+        if e.errno != 0:
+           print -e.errno
+        else:
+           print errno.EPIPE
+        print str(e)
+      else:
+        print free
         print "No Error reported."
+   else:
+      print "Wrong number of arguments for free operation. Wanted 3 got", len(sys.argv)
+      usage()
+elif "prepare" == sys.argv[2]:
+   if len(sys.argv) == 3:
+      try:
+        dvd.prepare()
+      except DVDError, e:
+        print "Error while preparing medium: ", str(e)
+        if e.errno != 0:
+           sys.exit(e.errno & 0x7F)
+        else:
+           sys.exit(errno.EPIPE)
       else:
-        print dvd.lasterr()
+        print "Medium prepared successfully."
    else:
-      print "Wrong number of arguments."
-      sys.exit(1)
+      print "Wrong number of arguments for prepare operation. Wanted 3 got", len(sys.argv)
+      usage()
 elif "test" == sys.argv[2]:
-   print str(dvd)
-   print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
-   print "Free for new volume: " + str(dvd.free(1))
-   print "Free for append:     " + str(dvd.free(0))
+   try:
+      print str(dvd)
+      print "Blank disk: " + str(dvd.is_blank()) + " ReWritable disk: " + str(dvd.is_RW())
+      print "Free space: " + str(dvd.free())
+   except DVDError, e:
+      print "Error while getting informations: ", str(e)
 elif "write" == sys.argv[2]:
    if len(sys.argv) == 5:
-      newvol = 1
-      if "0" == sys.argv[3]:
-        newvol = 0
-      dvd.write(newvol, sys.argv[4])
-      if "none" != dvd.lasterr():
-        print str(dvd.lasterr())
-        sys.exit(1)
+      try:
+        dvd.write(long(sys.argv[3]), sys.argv[4])
+      except DVDError, e:
+        print "Error while writing part file: ", str(e)
+        if e.errno != 0:
+           sys.exit(e.errno & 0x7F)
+        else:
+           sys.exit(errno.EPIPE)
       else:
         print "Part file " + sys.argv[4] + " successfully written to disk."
    else:
-      print "Wrong number of arguments."
+      print "Wrong number of arguments for write operation. Wanted 5 got", len(sys.argv)
+      usage()
       sys.exit(1)
 else:
-   print "No operation - use test, free or write."
+   print "No operation - use test, free, prepare or write."
    print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"
 sys.exit(0)