]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/scripts/dvd-handler.in
Fix bug in build-win32-cross-tools script reported by Howard
[bacula/bacula] / bacula / scripts / dvd-handler.in
index 9e396bcca8465979bd2409780d384c03ba1cebd3..b90108156ead4f0a4626b69c8d61605e81b89f4f 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.
 
 import popen2
 import os
+import os.path
 import errno
 import sys
 import re
 import signal
 import time
+import array
 
+class disk:
 # Configurable values:
-dvdrwmediainfo = "@DVDRWMEDIAINFO@"
-growcmd = "@GROWISOFS@"
-margin = 10485760 # 10 mb security margin
+   
+   dvdrwmediainfo = "@DVDRWMEDIAINFO@"
+   growcmd = "@GROWISOFS@"
+   dvdrwformat = "@DVDRWFORMAT@"
+   dd = "@DD@"
+   margin = 10485760 # 10 mb security margin
 
-# Comment the following line if you want the tray to be reloaded
-# when writing ends.
-growcmd += " -use-the-force-luke=notray"
+   # 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
 
-## Check if we want to allow growisofs to cross the 4gb boundary
-def is4gbsupported():
-   processi = popen2.Popen4("uname -s -r")
-   status = processi.wait()
-   if not os.WIFEXITED(status):
-#      print "dvd-writepart: Cannot execute uname, allowing to cross the 4gb boundary."
-      return 1
-   if os.WEXITSTATUS(status) != 0:
-#      print "dvd-writepart: Cannot execute uname, allowing to cross the 4gb boundary."
-      return 1
-   strres = processi.fromchild.readline()[0:-1]
-   res = strres.split(" ")
-   if len(res) != 2:
-#      print "dvd-writepart: Unable to parse uname (" + strres + "), allowing to cross the 4gb boundary."
-      return 1
-   if res[0] != "Linux":
-#      print "dvd-writepart: The current OS is no Linux, allowing to cross the 4gb boundary."
-      return 1
-   ver = res[1].split(".")
-   if len(ver) < 3:
-#      print "dvd-writepart: Unable to parse version string (" + res[1] + "), allowing to cross the 4gb boundary."
-      return 1
-   subver = ver[2].split("-")
-   
-   if ((not ver[0].isdigit()) or (not ver[1].isdigit()) or (not subver[0].isdigit())):
-#      print "dvd-writepart: Unable to parse version string (" + res[1] + "), allowing to cross the 4gb boundary."
-      return 1
-   
-   if (int(ver[0]) > 2) or (int(ver[1]) > 6) or ((int(ver[0]) == 2) and (int(ver[1]) == 6) and (int(subver[0]) >= 8)):
-#      print "dvd-writepart: Kernel version >=2.6.8, allowing to cross the 4gb boundary."
-      return 1
-   else:
-#      print "dvd-writepart: Kernel version <2.6.8, not allowing to cross the 4gb boundary."
-      return 0
-
-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) + ")"
-
-class disk:
 ###############################################################################
 #
 # This class represents DVD disk informations.
@@ -107,12 +68,14 @@ class disk:
 #                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
+# blank          Blank the device
 #
 ###############################################################################
    def __init__(self, devicename):
       self.device = devicename
       self.disktype = "none"
+      self.diskmode = "none"
+      self.diskstatus = "none"
       self.hardwaredevice = "none"
       self.pid = 0
       self.next_session = -1
@@ -121,6 +84,14 @@ class disk:
       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"
+
       return
 
    def __repr__(self):
@@ -132,14 +103,33 @@ class disk:
       if not self.mediumtype_collected:
          self.collect_mediumtype();
       
-      self.me  = "Class disk, initialized with device " + self.device + "\n"
-      self.me += "type = " + self.disktype
+      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 += "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 = growcmd + " -F " + self.device
+      self.cmd = self.growcmd + " -F " + self.device
       processi = popen2.Popen4(self.cmd)
       status = processi.wait()
       if not os.WIFEXITED(status):
@@ -153,17 +143,17 @@ class disk:
             self.freespace_collected = 1
             return
          else:
-            raise DVDError(os.WEXITSTATUS(status), "growisofs returned with an error " + result)
+            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"\capacity=(\d+)\s", result, re.MULTILINE)
+      capa = re.search(r"\scapacity=(\d+)\s", result, re.MULTILINE)
    
       if next_sess and capa:
-         # testing cheat (emulate 4GB boundary at 100MB)
-         #if long(next_session.group(1)) > 100000000:
-         #   return 0
-         
          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)
       
@@ -172,25 +162,41 @@ class disk:
    
    def collect_mediumtype(self): # Collects current medium type
       self.lasterror = ""
-      cmd = dvdrwmediainfo + " " + self.device
+      cmd = self.dvdrwmediainfo + " " + self.device
       processi = popen2.Popen4(cmd)
       status = processi.wait()
       if not os.WIFEXITED(status):
-         raise DVDError(dvdrwmediainfo + " process did not exit correctly.")
+         raise DVDError(0, self.dvdrwmediainfo + " process did not exit correctly.")
       if os.WEXITSTATUS(status) != 0:
-         raise DVDError("Cannot get media info from " + dvdrwmediainfo)
+         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("Media type not found in " + dvdrwmediainfo + " output")
+         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
 
@@ -203,14 +209,38 @@ class disk:
    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 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 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();
       
-      return self.capacity-self.next_session-margin
+      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
@@ -223,33 +253,132 @@ class disk:
          sys.exit(1)
 
    def write(self, newvol, partfile):
-      ## TODO: blank DVD+RW when needed
+      # 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 = growcmd
+      cmd = self.growcmd + self.growparams
       if newvol:
          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:
-         out = proc.fromchild.read(512)
-         while out != "":
-            sys.stdout.write(out)
-            out = proc.fromchild.read(512)
+         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), growcmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
+         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
+      
+      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) + ")"
+
 def usage():
    print "Wrong number of arguments."
    print """
@@ -258,29 +387,29 @@ Usage:
 dvd-handler DEVICE test
 dvd-handler DEVICE free
 dvd-handler DEVICE write APPEND FILE
+dvd-handler DEVICE blank
 
 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.
-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 (0) or restart the
            disk (1). 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()
 
-growcmd += " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
-              "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
-
-if is4gbsupported():
-   growcmd += " -use-the-force-luke=4gms"
-
 dvd = disk(sys.argv[1])
 
 if "free" == sys.argv[2]:
@@ -299,10 +428,25 @@ if "free" == sys.argv[2]:
    else:
       print "Wrong number of arguments for free operation."
       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 "Medium prepared successfully."
+   else:
+      print "Wrong number of arguments for prepare operation."
+      usage()
 elif "test" == sys.argv[2]:
    try:
       print str(dvd)
-      print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
+      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)
@@ -323,6 +467,6 @@ elif "write" == sys.argv[2]:
       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)