]> git.sur5r.net Git - bacula/bacula/blob - bacula/scripts/dvd-handler.in
9e396bcca8465979bd2409780d384c03ba1cebd3
[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 # in case of operation ``free'' returns:
14 # Prints on the first output line the free space available in bytes.
15 # If an error occurs, prints a negative number (-errno), followed,
16 # on the second line, by an error message.
17
18 # $Id$
19 #
20
21 import popen2
22 import os
23 import errno
24 import sys
25 import re
26 import signal
27 import time
28
29 # Configurable values:
30 dvdrwmediainfo = "@DVDRWMEDIAINFO@"
31 growcmd = "@GROWISOFS@"
32 margin = 10485760 # 10 mb security margin
33
34 # Comment the following line if you want the tray to be reloaded
35 # when writing ends.
36 growcmd += " -use-the-force-luke=notray"
37
38 # end of configurable values
39
40 ## Check if we want to allow growisofs to cross the 4gb boundary
41 def is4gbsupported():
42    processi = popen2.Popen4("uname -s -r")
43    status = processi.wait()
44    if not os.WIFEXITED(status):
45 #      print "dvd-writepart: Cannot execute uname, allowing to cross the 4gb boundary."
46       return 1
47    if os.WEXITSTATUS(status) != 0:
48 #      print "dvd-writepart: Cannot execute uname, allowing to cross the 4gb boundary."
49       return 1
50    strres = processi.fromchild.readline()[0:-1]
51    res = strres.split(" ")
52    if len(res) != 2:
53 #      print "dvd-writepart: Unable to parse uname (" + strres + "), allowing to cross the 4gb boundary."
54       return 1
55    if res[0] != "Linux":
56 #      print "dvd-writepart: The current OS is no Linux, allowing to cross the 4gb boundary."
57       return 1
58    ver = res[1].split(".")
59    if len(ver) < 3:
60 #      print "dvd-writepart: Unable to parse version string (" + res[1] + "), allowing to cross the 4gb boundary."
61       return 1
62    subver = ver[2].split("-")
63    
64    if ((not ver[0].isdigit()) or (not ver[1].isdigit()) or (not subver[0].isdigit())):
65 #      print "dvd-writepart: Unable to parse version string (" + res[1] + "), allowing to cross the 4gb boundary."
66       return 1
67    
68    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)):
69 #      print "dvd-writepart: Kernel version >=2.6.8, allowing to cross the 4gb boundary."
70       return 1
71    else:
72 #      print "dvd-writepart: Kernel version <2.6.8, not allowing to cross the 4gb boundary."
73       return 0
74
75 class DVDError(Exception):
76    def __init__(self, errno, value):
77       self.errno = errno
78       self.value = value
79       if self.value[-1] == '\n':
80          self.value = self.value[0:-1]
81    def __str__(self):
82       return str(self.value) + " || errno = " + str(self.errno) + " (" + os.strerror(self.errno & 0x7F) + ")"
83
84 class disk:
85 ###############################################################################
86 #
87 # This class represents DVD disk informations.
88 # When instantiated, it needs a device name.
89 # Status information about the device and the disk loaded is collected only when
90 # asked for (for example dvd-freespace doesn't need to know the media type, and
91 # dvd-writepart doesn't not always need to know the free space).
92 #
93 # The following methods are implemented:
94 # __init__       we need that...
95 # __repr__       this seems to be a good idea to have.
96 #                Quite minimalistic implementation, though.
97 # __str__        For casts to string. Return the current disk information
98 # is_empty       Returns TRUE if the disk is empty, blank... this needs more
99 #                work, especially concerning non-RW media and blank vs. no
100 #                filesystem considerations. Here, we should also look for
101 #                other filesystems - probably we don't want to silently
102 #                overwrite UDF or ext2 or anything not mentioned in fstab...
103 #                (NB: I don't think it is a problem)
104 # free           Returns the available free space.
105 # write          Writes one part file to disk, either starting a new file
106 #                system on disk, or appending to it.
107 #                This method should also prepare a blank disk so that a
108 #                certain part of the disk is used to allow detection of a
109 #                used disk by all / more disk drives.
110 # blank          NOT IMPLEMENTED
111 #
112 ###############################################################################
113    def __init__(self, devicename):
114       self.device = devicename
115       self.disktype = "none"
116       self.hardwaredevice = "none"
117       self.pid = 0
118       self.next_session = -1
119       self.capacity = -1
120
121       self.freespace_collected = 0
122       self.mediumtype_collected = 0
123
124       return
125
126    def __repr__(self):
127       return "disk(" + self.device + ") # This is an instance of class disk"
128
129    def __str__(self):
130       if not self.freespace_collected:
131          self.collect_freespace();
132       if not self.mediumtype_collected:
133          self.collect_mediumtype();
134       
135       self.me  = "Class disk, initialized with device " + self.device + "\n"
136       self.me += "type = " + self.disktype
137       self.me += " next_session = " + str(self.next_session) + " capacity = " + str(self.capacity) + "\n"
138       self.me += "Hardware device is " + self.hardwaredevice + "\n"
139       return self.me
140
141    def collect_freespace(self): # Collects current free space
142       self.cmd = growcmd + " -F " + self.device
143       processi = popen2.Popen4(self.cmd)
144       status = processi.wait()
145       if not os.WIFEXITED(status):
146          raise DVDError(0, "growisofs process did not exit correctly.")
147       result = processi.fromchild.read()
148       if os.WEXITSTATUS(status) != 0:
149          if (os.WEXITSTATUS(status) & 0x7F) == errno.ENOSPC:
150             # Kludge to force dvd-handler to return a free space of 0
151             self.next_session = 1
152             self.capacity = 1
153             self.freespace_collected = 1
154             return
155          else:
156             raise DVDError(os.WEXITSTATUS(status), "growisofs returned with an error " + result)
157       next_sess = re.search(r"\snext_session=(\d+)\s", result, re.MULTILINE)
158       capa = re.search(r"\capacity=(\d+)\s", result, re.MULTILINE)
159    
160       if next_sess and capa:
161          # testing cheat (emulate 4GB boundary at 100MB)
162          #if long(next_session.group(1)) > 100000000:
163          #   return 0
164          
165          self.next_session = long(next_sess.group(1))
166          self.capacity = long(capa.group(1))
167       else:
168          raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
169       
170       self.freespace_collected = 1
171       return
172    
173    def collect_mediumtype(self): # Collects current medium type
174       self.lasterror = ""
175       cmd = dvdrwmediainfo + " " + self.device
176       processi = popen2.Popen4(cmd)
177       status = processi.wait()
178       if not os.WIFEXITED(status):
179          raise DVDError(dvdrwmediainfo + " process did not exit correctly.")
180       if os.WEXITSTATUS(status) != 0:
181          raise DVDError("Cannot get media info from " + dvdrwmediainfo)
182          return
183       result = processi.fromchild.read()
184       
185       hardware = re.search(r"INQUIRY:\s+(.*)\n", result, re.MULTILINE)
186       mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s", result, re.MULTILINE)
187       
188       if hardware:
189          self.hardwaredevice = hardware.group(1)
190       if mediatype:
191          self.disktype = mediatype.group(2)
192       else:
193          raise DVDError("Media type not found in " + dvdrwmediainfo + " output")
194       self.mediumtype_collected = 1
195       return
196
197    def is_empty(self):
198       if not self.freespace_collected:
199          self.collect_freespace();
200       
201       return 0 == self.next_session
202
203    def is_RW(self):
204       if not self.mediumtype_collected:
205          self.collect_mediumtype();
206       
207       return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
208
209    def free(self):
210       if not self.freespace_collected:
211          self.collect_freespace();
212       
213       return self.capacity-self.next_session-margin
214
215    def term_handler(self, signum, frame):
216       print 'dvd-handler: Signal term_handler called with signal', signum
217       if self.pid != 0:
218          print "dvd-handler: Sending SIGTERM to pid", self.pid
219          os.kill(self.pid, signal.SIGTERM)
220          time.sleep(10)
221          print "dvd-handler: Sending SIGKILL to pid", self.pid
222          os.kill(self.pid, signal.SIGKILL)
223          sys.exit(1)
224
225    def write(self, newvol, partfile):
226       ## TODO: blank DVD+RW when needed
227       
228       cmd = growcmd
229       if newvol:
230          cmd += " -Z "
231       else:
232          cmd += " -M "
233       cmd += self.device + " " + str(partfile)
234       oldsig = signal.signal(signal.SIGTERM, self.term_handler)
235       proc = popen2.Popen4(cmd)
236       self.pid = proc.pid
237       status = proc.poll()
238       while status == -1:
239          out = proc.fromchild.read(512)
240          while out != "":
241             sys.stdout.write(out)
242             out = proc.fromchild.read(512)
243          time.sleep(1)
244          status = proc.poll()
245       self.pid = 0
246       print
247       signal.signal(signal.SIGTERM, oldsig)
248       if os.WEXITSTATUS(status) != 0:
249          raise DVDError(os.WEXITSTATUS(status), growcmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
250
251 # class disk ends here.
252
253 def usage():
254    print "Wrong number of arguments."
255    print """
256 Usage:
257
258 dvd-handler DEVICE test
259 dvd-handler DEVICE free
260 dvd-handler DEVICE write APPEND FILE
261
262 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
263
264 Operations:
265 test       Scan the device and report the information found.
266            This operation needs no further arguments.
267 free       Scan the device and report the available space.
268 write      Write a part file to disk.
269            This operation needs two additional arguments.
270            The first indicates to append (0) or restart the
271            disk (1). The second is the file to write.
272 """
273    sys.exit(1)
274
275 if len(sys.argv) < 3:
276    usage()
277
278 growcmd += " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
279               "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
280
281 if is4gbsupported():
282    growcmd += " -use-the-force-luke=4gms"
283
284 dvd = disk(sys.argv[1])
285
286 if "free" == sys.argv[2]:
287    if len(sys.argv) == 3:
288       try:
289          free = dvd.free()
290       except DVDError, e:
291          if e.errno != 0:
292             print -e.errno
293          else:
294             print errno.EPIPE
295          print str(e)
296       else:
297          print free
298          print "No Error reported."
299    else:
300       print "Wrong number of arguments for free operation."
301       usage()
302 elif "test" == sys.argv[2]:
303    try:
304       print str(dvd)
305       print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
306       print "Free space: " + str(dvd.free())
307    except DVDError, e:
308       print "Error while getting informations: ", str(e)
309 elif "write" == sys.argv[2]:
310    if len(sys.argv) == 5:
311       try:
312          dvd.write(long(sys.argv[3]), sys.argv[4])
313       except DVDError, e:
314          print "Error while writing part file: ", str(e)
315          if e.errno != 0:
316             sys.exit(e.errno & 0x7F)
317          else:
318             sys.exit(errno.EPIPE)
319       else:
320          print "Part file " + sys.argv[4] + " successfully written to disk."
321    else:
322       print "Wrong number of arguments for write operation."
323       usage()
324       sys.exit(1)
325 else:
326    print "No operation - use test, free or write."
327    print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"
328 sys.exit(0)