]> git.sur5r.net Git - bacula/bacula/blob - bacula/scripts/dvd-handler.in
fdae793164e70dc7533d6027a782664f3b35212a
[bacula/bacula] / bacula / scripts / dvd-handler.in
1 #!@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          Blank the device
111 #
112 ###############################################################################
113    def __init__(self, devicename):
114       self.device = devicename
115       self.disktype = "none"
116       self.diskstatus = "none"
117       self.hardwaredevice = "none"
118       self.pid = 0
119       self.next_session = -1
120       self.capacity = -1
121
122       self.freespace_collected = 0
123       self.mediumtype_collected = 0
124
125       return
126
127    def __repr__(self):
128       return "disk(" + self.device + ") # This is an instance of class disk"
129
130    def __str__(self):
131       if not self.freespace_collected:
132          self.collect_freespace();
133       if not self.mediumtype_collected:
134          self.collect_mediumtype();
135       
136       self.me  = "Class disk, initialized with device '" + self.device + "'\n"
137       self.me += "type = '" + self.disktype + "' status = '" + self.diskstatus + "'\n"
138       self.me += " next_session = " + str(self.next_session) + " capacity = " + str(self.capacity) + "\n"
139       self.me += "Hardware device is '" + self.hardwaredevice + "'\n"
140       return self.me
141
142    def collect_freespace(self): # Collects current free space
143       self.cmd = growcmd + " -F " + self.device
144       processi = popen2.Popen4(self.cmd)
145       status = processi.wait()
146       if not os.WIFEXITED(status):
147          raise DVDError(0, "growisofs process did not exit correctly.")
148       result = processi.fromchild.read()
149       if os.WEXITSTATUS(status) != 0:
150          if (os.WEXITSTATUS(status) & 0x7F) == errno.ENOSPC:
151             # Kludge to force dvd-handler to return a free space of 0
152             self.next_session = 1
153             self.capacity = 1
154             self.freespace_collected = 1
155             return
156          else:
157             raise DVDError(os.WEXITSTATUS(status), "growisofs returned with an error " + result + ". Please check your are using a patched version of dvd+rw-tools.")
158       next_sess = re.search(r"\snext_session=(\d+)\s", result, re.MULTILINE)
159       capa = re.search(r"\capacity=(\d+)\s", result, re.MULTILINE)
160    
161       if next_sess and capa:
162          self.next_session = long(next_sess.group(1))
163          self.capacity = long(capa.group(1))
164          
165          # testing cheat (emulate 4GB boundary at 100MB)
166          #if self.next_session > 100000000:
167          #   self.capacity = self.next_session
168       else:
169          raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
170       
171       self.freespace_collected = 1
172       return
173    
174    def collect_mediumtype(self): # Collects current medium type
175       self.lasterror = ""
176       cmd = dvdrwmediainfo + " " + self.device
177       processi = popen2.Popen4(cmd)
178       status = processi.wait()
179       if not os.WIFEXITED(status):
180          raise DVDError(dvdrwmediainfo + " process did not exit correctly.")
181       if os.WEXITSTATUS(status) != 0:
182          raise DVDError("Cannot get media info from " + dvdrwmediainfo)
183          return
184       result = processi.fromchild.read()
185       
186       hardware = re.search(r"INQUIRY:\s+(.*)\n", result, re.MULTILINE)
187       mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s", result, re.MULTILINE)
188       status = re.search(r"\sDisc status:\s+(.*)\n", result, re.MULTILINE)
189       
190       if hardware:
191          self.hardwaredevice = hardware.group(1)
192       
193       if mediatype:
194          self.disktype = mediatype.group(2)
195       else:
196          raise DVDError("Media type not found in " + dvdrwmediainfo + " output")
197       
198       if status:
199          self.diskstatus = status.group(1)
200       else:
201          raise DVDError("Disc status not found in " + dvdrwmediainfo + " output")
202
203       
204       self.mediumtype_collected = 1
205       return
206
207    def is_empty(self):
208       if not self.freespace_collected:
209          self.collect_freespace();
210       
211       return 0 == self.next_session
212
213    def is_RW(self):
214       if not self.mediumtype_collected:
215          self.collect_mediumtype();
216       
217       return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
218
219    def is_blank(self):
220       if not self.mediumtype_collected:
221          self.collect_mediumtype();
222       
223       return self.diskstatus == "blank"
224
225    def free(self):
226       if not self.freespace_collected:
227          self.collect_freespace();
228       
229       fr = self.capacity-self.next_session-margin
230       if fr < 0:
231          return 0
232       else:
233          return fr
234
235    def term_handler(self, signum, frame):
236       print 'dvd-handler: Signal term_handler called with signal', signum
237       if self.pid != 0:
238          print "dvd-handler: Sending SIGTERM to pid", self.pid
239          os.kill(self.pid, signal.SIGTERM)
240          time.sleep(10)
241          print "dvd-handler: Sending SIGKILL to pid", self.pid
242          os.kill(self.pid, signal.SIGKILL)
243          sys.exit(1)
244
245    def write(self, newvol, partfile):
246       # Blank DVD+/-RW/-RAM when there is no data on it
247       if newvol and self.is_RW() and self.is_blank():
248          print "DVD+/-RW looks brand-new, blank it to fix some DVD-writers bugs."
249          self.blank()
250          print "Done, now writing the real part file."
251       
252       cmd = growcmd + growparams
253       if newvol:
254          cmd += " -Z "
255       else:
256          cmd += " -M "
257       cmd += self.device + " " + str(partfile)
258       print "Running " + cmd
259       oldsig = signal.signal(signal.SIGTERM, self.term_handler)
260       proc = popen2.Popen4(cmd)
261       self.pid = proc.pid
262       status = proc.poll()
263       while status == -1:
264          line = proc.fromchild.readline()
265          while len(line) > 0:
266             print line,
267             line = proc.fromchild.readline()
268          time.sleep(1)
269          status = proc.poll()
270       self.pid = 0
271       print
272       signal.signal(signal.SIGTERM, oldsig)
273       if os.WEXITSTATUS(status) != 0:
274          raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
275
276    def blank(self):
277       cmd = growcmd + " -Z " + self.device + "=/dev/zero"
278       print "Running " + cmd
279       oldsig = signal.signal(signal.SIGTERM, self.term_handler)
280       proc = popen2.Popen4(cmd)
281       self.pid = proc.pid
282       status = proc.poll()
283       while status == -1:
284          line = proc.fromchild.readline()
285          while len(line) > 0:
286             print line,
287             line = proc.fromchild.readline()
288          time.sleep(1)
289          status = proc.poll()
290       self.pid = 0
291       print
292       signal.signal(signal.SIGTERM, oldsig)
293       if os.WEXITSTATUS(status) != 0:
294          raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
295
296 # class disk ends here.
297
298 def usage():
299    print "Wrong number of arguments."
300    print """
301 Usage:
302
303 dvd-handler DEVICE test
304 dvd-handler DEVICE free
305 dvd-handler DEVICE write APPEND FILE
306
307 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
308
309 Operations:
310 test       Scan the device and report the information found.
311            This operation needs no further arguments.
312 free       Scan the device and report the available space.
313 write      Write a part file to disk.
314            This operation needs two additional arguments.
315            The first indicates to append (0) or restart the
316            disk (1). The second is the file to write.
317 """
318    sys.exit(1)
319
320 if len(sys.argv) < 3:
321    usage()
322
323 growcmd += " -quiet"
324  
325 if is4gbsupported():
326    growcmd += " -use-the-force-luke=4gms"
327
328 growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
329               "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
330
331 dvd = disk(sys.argv[1])
332
333 if "free" == sys.argv[2]:
334    if len(sys.argv) == 3:
335       try:
336          free = dvd.free()
337       except DVDError, e:
338          if e.errno != 0:
339             print -e.errno
340          else:
341             print errno.EPIPE
342          print str(e)
343       else:
344          print free
345          print "No Error reported."
346    else:
347       print "Wrong number of arguments for free operation."
348       usage()
349 elif "test" == sys.argv[2]:
350    try:
351       print str(dvd)
352       print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
353       print "Free space: " + str(dvd.free())
354    except DVDError, e:
355       print "Error while getting informations: ", str(e)
356 elif "write" == sys.argv[2]:
357    if len(sys.argv) == 5:
358       try:
359          dvd.write(long(sys.argv[3]), sys.argv[4])
360       except DVDError, e:
361          print "Error while writing part file: ", str(e)
362          if e.errno != 0:
363             sys.exit(e.errno & 0x7F)
364          else:
365             sys.exit(errno.EPIPE)
366       else:
367          print "Part file " + sys.argv[4] + " successfully written to disk."
368    else:
369       print "Wrong number of arguments for write operation."
370       usage()
371       sys.exit(1)
372 else:
373    print "No operation - use test, free or write."
374    print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"
375 sys.exit(0)