]> git.sur5r.net Git - bacula/bacula/blob - bacula/scripts/dvd-handler.in
- Add Arno's dvd-handler script to the scripts directory and
[bacula/bacula] / bacula / scripts / dvd-handler.in
1 #!/usr/bin/python
2 #
3 # Check the free space available on a writable DVD
4 # Should always exit with 0 status, otherwise it indicates a serious error.
5 # (wrong number of arguments, Python exception...)
6 #
7 #  called:  dvd-handler <dvd-device-name> operation args
8 #
9 #  where operation is one of
10 #    free
11 #    write
12 #
13 #  further arguments:
14 #    free: one argument: 0 to keep the existing data on disk, i.e.
15 #          free space is measured
16 #                        anything else: overwrite entire disk, so
17 #          free space is always maximum space
18 #
19 # in case of operation ``free'' returns:
20 # Prints on the first output line the free space available in bytes.
21 # If an error occurs, prints a negative number (-errno), followed,
22 # on the second line, by an error message.
23
24 # $Id$
25 #
26
27 # end of configurable values
28
29 import popen2
30 import os
31 import errno
32 import sys
33 import re
34 import signal
35 import time
36
37 class disk:
38    # Configurable values:
39    df = "@DF@ -P"
40    dvdrwmediainfo = "@DVDRWMEDIAINFO@"
41    growisofs = "@GROWISOFS@"
42    margin = 10485760 # 10 mb security margin
43    # We disable 4GB boundary checking - function free should handle this
44    # already, and it doesn't seem to work anyway (here: 2.6.8-24.18 from SuSE,
45    # LG GSA-5163D, dvd+rw-tools 5.21
46    growcmd = growisofs + " -use-the-force-luke=notray -use-the-force-luke=4gms "
47    growcmd += "-A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
48               "-publisher 'ITS Lehmann info@its-lehmann.de' " + \
49               "-p 'dvd-handler / growisofs' -sysid 'BACULADATA'"
50
51 ###############################################################################
52 #
53 # This class represents a DVD disk (various flavours).
54 # When instantiated, it needs a device name.
55 # Status information about the device and the disk loaded is collected.
56 #
57 # The following methods are implemented:
58 # __init__      we need that...
59 #               NOTE: Currently, this class only works with DVD+RW
60 #               and simply refuses to work with anything else.
61 #               This is because I had to start with some sort of disk,
62 #               and I learned that +RW and -RW are quite different, so
63 #               I decided to play it safe.
64 # __repr__      this seems to be a good idea to have.
65 #               Quite minimalistic implementation, though.
66 # __str__       For casts to string. Return the current disk information
67 # is_empty      Returns TRUE if the disk is empty, blank... this needs more
68 #               work, especially concerning non-RW media and blank vs. no
69 #               filesystem considerations. Here, we should also look for
70 #               other filesystems - probably we don't want to silently
71 #               overwrite UDF or ext2 or anything not mentioned in fstab...
72 # is_RW         TRUE if disk is rewritable. We need that to determine if
73 #               a new filesystem can be written onto a used disk.
74 # lasterr       returns a string describing the last error in the class.
75 #               Valuable after creating the object and after free()
76 # free          Returns the available free space, distinguishing between
77 #               new volume disks (overwrite everything) and appending
78 #               There are some assumtions about disk status and usage that
79 #               need work.
80 # write         Writes one part file to disk, either starting a new file
81 #               system on disk, or appending to it.
82 #               This method should also prepare a blank disk so that a
83 #               certain part of the disk is used to allow detection of a
84 #               used disk by all / more disk drives.
85 # blank         NOT IMPLEMENTED
86 #
87 ###############################################################################
88    def __init__(self, devicename):
89       self.device = "none"
90       self.disktype = "none"
91       self.leadout = -1
92       self.track = -1
93       self.maximum = -1
94       self.used = 0
95       self.lasterror = "none"
96       self.hardwaredevice = "none"
97       self.pid = 0
98
99       # first, we collect information about the media as reported by
100       # dvd+rw-mediainfo
101       # we need an indication of the usable total size.
102
103       self.cmd = self.dvdrwmediainfo + " " + devicename
104       self.processi = popen2.Popen4(self.cmd)
105       self.status = self.processi.wait()
106       if not os.WIFEXITED(self.status):
107          self.lasterror = self.dvdrwmediainfo + " process did not exit correctly."
108          return
109       if os.WEXITSTATUS(self.status) != 0:
110          self.lasterror = "Cannot get media info from " + self.dvdrwmediainfo
111          return
112       self.device = str(devicename)
113       self.result = self.processi.fromchild.read()
114       self.hardware = re.search(r"INQUIRY:\s+(.*)\n", self.result, re.MULTILINE)
115       self.mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s",
116          self.result, re.MULTILINE)
117       self.tracksize = re.search(r"\sTrack Size:\s+(\d+)\*2KB\s",
118          self.result, re.MULTILINE)
119       self.leadout = re.search(r"\sLegacy lead-out at:\s+(\d+)\*2KB=(\d+)\s",
120          self.result, re.MULTILINE)
121       if self.hardware:
122          self.hardwaredevice = self.hardware.group(1)
123       if self.mediatype:
124          self.disktype = self.mediatype.group(2)
125       else:
126          self.lasterror = "Media type not found."
127       if self.leadout:
128          self.leadout = long(self.leadout.group(1))*2048
129       else:
130          self.lasterror = "Lead-out block not found."
131       if self.tracksize:
132          self.track = long(self.tracksize.group(1))*2048
133       else:
134          self.lasterror = "Track size not found."
135       self.result = 0
136       if ( "DVD+RW" == self.disktype ):
137          if self.leadout > self.track:
138             self.result = self.leadout
139          else:
140             self.result = self.track
141       else:
142          self.lasterror = "Unsupported media: " + self.disktype
143       self.maximum = self.result - self.margin
144       if self.maximum < 0:
145          self.maximum = 0
146
147       # now, the actual size used on the disk.
148       # here, we use what df reports, although the
149       # current track size should give us the necessary information,
150       # too. Well, depending on the media type, it seems.
151       # We should see if the media is mounted, try to mount, if possible
152       # proceed and, if necessary, unmount. Otherwise assume 0 used bytes.
153       # __init__ and __del__ would be the right places to mount and
154       # unmount - mounting before df'ing is always a good idea,
155       # and setting the previos state might be important.
156
157       self.cmd = self.df + " " + self.device
158       self.process = popen2.Popen4(self.cmd)
159       self.status = self.process.wait()
160       if not os.WIFEXITED(self.status):
161          self.lasterror = self.df + " process did not not exit correctly."
162          return
163       self.exitstat = os.WEXITSTATUS(self.status) & ~0x80
164       if self.exitstat == errno.ENOSPC:
165          self.used = 0
166       elif self.exitstat != 0:
167          self.lasterror = os.strerror(self.exitstat)
168          return
169       self.dftext = self.process.fromchild.read()
170       self.blocks = re.search(self.device + r"\s+(\d+)\s+",
171          self.dftext, re.MULTILINE)
172       if self.blocks:
173          self.used = long(self.blocks.group(1))*1024
174       else:
175          self.used = 0
176          self.lasterror = "No blocks found in " + self.cmd + " output:\n"
177          self.lasterror += self.dftext
178       return
179
180    def __repr__(self):
181       return "disk(" + self.device + ") # This is an instance of class disk"
182
183    def __str__(self):
184       self.me  = "Class disk, initialized with device " + self.device + "\n"
185       self.me += "type = " + self.disktype + " leadout = " + str(self.leadout)
186       self.me += " track = " + str(self.track) + " maximum = " + str(self.maximum) + "\n"
187       self.me += "used = " + str(self.used) + "\n"
188       self.me += "Hardware device is " + self.hardwaredevice + "\n"
189       self.me += "last error = " + self.lasterror + "\n"
190       return self.me
191
192    def is_empty(self):
193       return 0 == self.used
194    # This works for DVD+RW, probably for all rewritable media. self.blank for -R?
195    # This is quite definitely not the best method. I need something
196    # that detects if a session exists on disk, but I didn't do any
197    # experiments with non-RW media yet.
198
199    def is_RW(self):
200       return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
201
202    def free(self, newvol):
203       if self.used < 0 or self.maximum <= 0:
204          return -1
205       elif newvol:
206          self.ret = self.maximum
207          if not self.is_empty() and not self.is_RW():
208             # better check real disks usage state as read from dvd+rw-mediainfo.
209             # introduce self.blank
210             self.ret = -1
211             self.lasterror = self.disktype + " can not be overwritten and is already used"
212       else:
213          self.ret = self.maximum - self.used
214          if self.used > 4278190080:      # if more than 4GB-16MB are already used
215             self.ret = 0
216       return self.ret
217
218    def lasterr(self):
219       return self.lasterror
220
221    def term_handler(self, signum, frame):
222       print 'dvd-handler: Signal term_handler called with signal', signum
223       if self.pid != 0:
224          print "dvd-handler: Sending SIGTERM to pid", self.pid
225          os.kill(self.pid, signal.SIGTERM)
226          time.sleep(10)
227          print "dvd-handler: Sending SIGKILL to pid", self.pid
228          os.kill(self.pid, signal.SIGKILL)
229          sys.exit(1)
230
231    def write(self, newvol, partfile):
232       self.lasterror = "none"
233       self.partstat = os.stat(partfile)
234       if not self.partstat:
235          self.lasterror = "Could not stat " + str(partfile) + " which is a fatal error."
236          return
237       if self.partstat.st_size > self.free(newvol):
238          self.lasterror = "Part " + str(partfile) + " is too big: " \
239             + str(self.partstat.st_size) + ", free " + str(self.free(newvol))
240          return
241       if "DVD-RW" == self.disktype:
242          self.cmd = self.growcmd + " -R "
243          if newvol:
244             self.cmd += "-Z "
245          else:
246             self.cmd += "-M "
247          self.cmd += self.device + " " + str(partfile)
248          self.oldsig = signal.signal(signal.SIGTERM, self.term_handler)
249          self.proc = popen2.Popen4(self.cmd)
250          self.pid = self.proc.pid
251          self.status = self.proc.poll()
252          while -1 == self.status:
253             self.out = self.proc.fromchild.read(512)
254             while "" != self.out:
255                sys.stdout.write(self.out)
256                self.out = self.proc.fromchild.read(512)
257             time.sleep(1)
258             self.status = self.proc.poll()
259          self.pid = 0
260          print
261          signal.signal(signal.SIGTERM, self.oldsig)
262          if 0 != os.WEXITSTATUS(self.status):
263             self.lasterror = self.cmd + " exited with status " + str(os.WEXITSTATUS(self.status))
264             print self.cmd + " exited with signal/status " + hex(self.status)         
265       else:     # Other disk type
266          self.lasterror = "Can't write to " + self.disktype
267 # class disk ends here.
268
269
270 if len(sys.argv) < 3:
271    print "Wrong number of arguments."
272    print """
273 This program needs to be called with the following parameters.
274
275 device operation [more arguments]
276
277 where device is a device name like /dev/sr0 or /dev/dvd and
278 operation can be "test", "free" or "write".
279
280 Operations:
281 test       Scan the device and report the information found.
282            This operation needs no further arguments.
283 free       Scan the device and report the available space.
284            "free" needs one additional argument to determine
285            if data is to be appended or if the disk will be
286            started from the beginning: "0" means append,
287            everything else indicates a new volume.
288 write      Write a part file to disk.
289            This operation needs two additional arguments.
290            The first indicates to append or restart the
291            disk; see above. The second is the file to write.
292 """
293    sys.exit(1)
294
295 dvd = disk(sys.argv[1])
296
297 if "free" == sys.argv[2]:
298    if len(sys.argv) == 4:
299       newvol = 1
300       if "0" == sys.argv[3]:
301          newvol = 0
302       free = dvd.free(newvol)
303       print free
304       if free >= 0:
305          print "No Error reported."
306       else:
307          print dvd.lasterr()
308    else:
309       print "Wrong number of arguments."
310       sys.exit(1)
311 elif "test" == sys.argv[2]:
312    print str(dvd)
313    print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
314    print "Free for new volume: " + str(dvd.free(1))
315    print "Free for append:     " + str(dvd.free(0))
316 elif "write" == sys.argv[2]:
317    if len(sys.argv) == 5:
318       newvol = 1
319       if "0" == sys.argv[3]:
320          newvol = 0
321       dvd.write(newvol, sys.argv[4])
322       if "none" != dvd.lasterr():
323          print str(dvd.lasterr())
324          sys.exit(1)
325       else:
326          print "Part file " + sys.argv[4] + " successfully written to disk."
327    else:
328       print "Wrong number of arguments."
329       sys.exit(1)
330 else:
331    print "No operation - use test, free or write."
332    print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"
333 sys.exit(0)