]> git.sur5r.net Git - bacula/bacula/commitdiff
Add dvd-simulator.in
authorKern Sibbald <kern@sibbald.com>
Sat, 16 Sep 2006 07:46:08 +0000 (07:46 +0000)
committerKern Sibbald <kern@sibbald.com>
Sat, 16 Sep 2006 07:46:08 +0000 (07:46 +0000)
git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@3475 91ce42f0-d328-0410-95d8-f526ca767f89

bacula/scripts/dvd-simulator.in [new file with mode: 0644]

diff --git a/bacula/scripts/dvd-simulator.in b/bacula/scripts/dvd-simulator.in
new file mode 100644 (file)
index 0000000..ea0e5c8
--- /dev/null
@@ -0,0 +1,461 @@
+#!@PYTHON@
+#
+#  Modified version of dvd-handler used to simulate reading/writing
+#    to a DVD but using disk storage. This is a pretty crude implementation
+#    and a lot of the old code is still here and just sort of blunders
+#    along.
+#
+#  called:  dvd-simulator <dvd-device-name> operation args
+#
+#  operations used by Bacula:
+#
+#   free  (no arguments)
+#            Scan the device and report the available space.
+#
+#   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$
+#
+
+import popen2
+import os
+import os.path
+import shutil
+import errno
+import sys
+import re
+import signal
+import time
+import array
+
+class disk:
+# Configurable values:
+   
+   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"
+
+# end of configurable values
+
+###############################################################################
+#
+# This class represents DVD disk informations.
+# When instantiated, it needs a device name.
+# 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...
+# __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.
+# 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
+      self.capacity = -1
+      self.maxcapacity = 4000000000
+
+      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):
+      return "disk(" + self.device + ") # This is an instance of class disk"
+
+   def __str__(self):
+      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.next_session = 0
+      self.capacity = 4000000000
+      self.freespace_collected = 1
+
+      cmd = "du -sb " + self.device
+      processi = popen2.Popen4(cmd)       
+      status = processi.wait()
+      if not os.WIFEXITED(status):
+        return 1;
+      if os.WEXITSTATUS(status) != 0:
+        return 1;
+      result = processi.fromchild.read()
+      
+      used = re.search(r"(\d+)\s", result, re.MULTILINE)
+
+      self.capacity = self.maxcapacity - long(used.group(1))
+      if self.capacity < 0:
+        self.capacity = 0
+
+      return 0
+   
+   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):
+      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 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();
+      
+      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
+      if self.pid != 0:
+        print "dvd-handler: Sending SIGTERM to pid", self.pid
+        os.kill(self.pid, signal.SIGTERM)
+        time.sleep(10)
+        print "dvd-handler: Sending SIGKILL to pid", self.pid
+        os.kill(self.pid, signal.SIGKILL)
+        sys.exit(1)
+
+   def write(self, newvol, partfile):
+      if newvol:
+        print "Newvol", newvol
+        print "Zap everything ..."
+        os.system("rm -f "+self.device+"/*")
+      print "cp", partfile, self.device
+      shutil.copy(partfile,self.device)
+
+   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 """
+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.
+          This operation needs no further arguments.
+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), 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) == 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)
+      print sys.argv[1], sys.argv[2], sys.argv[3]
+      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 "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:
+      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 for write operation."
+      usage()
+      sys.exit(1)
+else:
+   print "No operation - use test, free, prepare or write."
+   print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"
+sys.exit(0)