]> git.sur5r.net Git - bacula/bacula/blob - bacula/scripts/dvd-handler.in
- Add tests for dvd+rw-format in configure script.
[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 class disk:
30 # Configurable values:
31    
32    dvdrwmediainfo = "@DVDRWMEDIAINFO@"
33    growcmd = "@GROWISOFS@"
34    dvdrwformat = "@DVDRWFORMAT@"
35    margin = 10485760 # 10 mb security margin
36
37    # Comment the following line if you want the tray to be reloaded
38    # when writing ends.
39    growcmd += " -use-the-force-luke=notray"
40
41 # end of configurable values
42
43 ###############################################################################
44 #
45 # This class represents DVD disk informations.
46 # When instantiated, it needs a device name.
47 # Status information about the device and the disk loaded is collected only when
48 # asked for (for example dvd-freespace doesn't need to know the media type, and
49 # dvd-writepart doesn't not always need to know the free space).
50 #
51 # The following methods are implemented:
52 # __init__       we need that...
53 # __repr__       this seems to be a good idea to have.
54 #                Quite minimalistic implementation, though.
55 # __str__        For casts to string. Return the current disk information
56 # is_empty       Returns TRUE if the disk is empty, blank... this needs more
57 #                work, especially concerning non-RW media and blank vs. no
58 #                filesystem considerations. Here, we should also look for
59 #                other filesystems - probably we don't want to silently
60 #                overwrite UDF or ext2 or anything not mentioned in fstab...
61 #                (NB: I don't think it is a problem)
62 # free           Returns the available free space.
63 # write          Writes one part file to disk, either starting a new file
64 #                system on disk, or appending to it.
65 #                This method should also prepare a blank disk so that a
66 #                certain part of the disk is used to allow detection of a
67 #                used disk by all / more disk drives.
68 # blank          Blank the device
69 #
70 ###############################################################################
71    def __init__(self, devicename):
72       self.device = devicename
73       self.disktype = "none"
74       self.diskmode = "none"
75       self.diskstatus = "none"
76       self.hardwaredevice = "none"
77       self.pid = 0
78       self.next_session = -1
79       self.capacity = -1
80
81       self.freespace_collected = 0
82       self.mediumtype_collected = 0
83
84       self.growcmd += " -quiet"
85
86       if self.is4gbsupported():
87          self.growcmd += " -use-the-force-luke=4gms"
88
89       self.growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
90                         "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
91
92       return
93
94    def __repr__(self):
95       return "disk(" + self.device + ") # This is an instance of class disk"
96
97    def __str__(self):
98       if not self.freespace_collected:
99          self.collect_freespace();
100       if not self.mediumtype_collected:
101          self.collect_mediumtype();
102       
103       self.me  = "Class disk, initialized with device '" + self.device + "'\n"
104       self.me += "type = '" + self.disktype + "' mode='" + self.diskmode + "' status = '" + self.diskstatus + "'\n"
105       self.me += " next_session = " + str(self.next_session) + " capacity = " + str(self.capacity) + "\n"
106       self.me += "Hardware device is '" + self.hardwaredevice + "'\n"
107       self.me += "growcmd = '" + self.growcmd + "'\ngrowparams = '" + self.growparams + "'\n"
108       return self.me
109
110    ## Check if we want to allow growisofs to cross the 4gb boundary
111    def is4gbsupported(self):
112       processi = popen2.Popen4("uname -s -r")
113       status = processi.wait()
114       if not os.WIFEXITED(status):
115          return 1
116       if os.WEXITSTATUS(status) != 0:
117          return 1
118       strres = processi.fromchild.readline()[0:-1]
119       version = re.search(r"Linux (\d+)\.(\d+)\.(\d+)", strres)
120       if not version: # Non-Linux: allow
121          return 1
122       
123       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)):
124          return 1
125       else:
126          return 0
127
128    def collect_freespace(self): # Collects current free space
129       self.cmd = self.growcmd + " -F " + self.device
130       processi = popen2.Popen4(self.cmd)
131       status = processi.wait()
132       if not os.WIFEXITED(status):
133          raise DVDError(0, "growisofs process did not exit correctly.")
134       result = processi.fromchild.read()
135       if os.WEXITSTATUS(status) != 0:
136          if (os.WEXITSTATUS(status) & 0x7F) == errno.ENOSPC:
137             # Kludge to force dvd-handler to return a free space of 0
138             self.next_session = 1
139             self.capacity = 1
140             self.freespace_collected = 1
141             return
142          else:
143             raise DVDError(os.WEXITSTATUS(status), "growisofs returned with an error " + result + ". Please check your are using a patched version of dvd+rw-tools.")
144       next_sess = re.search(r"\snext_session=(\d+)\s", result, re.MULTILINE)
145       capa = re.search(r"\scapacity=(\d+)\s", result, re.MULTILINE)
146    
147       if next_sess and capa:
148          self.next_session = long(next_sess.group(1))
149          self.capacity = long(capa.group(1))
150          
151          # testing cheat (emulate 4GB boundary at 100MB)
152          #if self.next_session > 100000000:
153          #   self.capacity = self.next_session
154       else:
155          raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
156       
157       self.freespace_collected = 1
158       return
159    
160    def collect_mediumtype(self): # Collects current medium type
161       self.lasterror = ""
162       cmd = self.dvdrwmediainfo + " " + self.device
163       processi = popen2.Popen4(cmd)
164       status = processi.wait()
165       if not os.WIFEXITED(status):
166          raise DVDError(self.dvdrwmediainfo + " process did not exit correctly.")
167       if os.WEXITSTATUS(status) != 0:
168          raise DVDError("Cannot get media info from " + self.dvdrwmediainfo)
169          return
170       result = processi.fromchild.read()
171       
172       hardware = re.search(r"INQUIRY:\s+(.*)\n", result, re.MULTILINE)
173       mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s", result, re.MULTILINE)
174       mediamode = re.search(r"\sMounted Media:\s+[0-9A-F]{2}h, \S* (.*)\n", result, re.MULTILINE)
175       status = re.search(r"\sDisc status:\s+(.*)\n", result, re.MULTILINE)
176       
177       if hardware:
178          self.hardwaredevice = hardware.group(1)
179       
180       if mediatype:
181          self.disktype = mediatype.group(2)
182       else:
183          raise DVDError("Media type not found in " + self.dvdrwmediainfo + " output")
184       
185       if self.disktype == "DVD-RW":
186          if mediamode:
187             self.diskmode = mediamode.group(1)
188          else:
189             raise DVDError("Media mode not found for DVD-RW in " + self.dvdrwmediainfo + " output")
190       
191       if status:
192          self.diskstatus = status.group(1)
193       else:
194          raise DVDError("Disc status not found in " + self.dvdrwmediainfo + " output")
195
196       
197       self.mediumtype_collected = 1
198       return
199
200    def is_empty(self):
201       if not self.freespace_collected:
202          self.collect_freespace();
203       
204       return 0 == self.next_session
205
206    def is_RW(self):
207       if not self.mediumtype_collected:
208          self.collect_mediumtype();
209       return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
210
211    def is_plus_RW(self):
212       if not self.mediumtype_collected:
213          self.collect_mediumtype();
214       return "DVD+RW" == self.disktype
215
216    def is_minus_RW(self):
217       if not self.mediumtype_collected:
218          self.collect_mediumtype();
219       return "DVD-RW" == self.disktype
220       
221    def is_restricted_overwrite(self):
222       if not self.mediumtype_collected:
223          self.collect_mediumtype();
224       return self.diskmode == "Restricted Overwrite"
225
226    def is_blank(self):
227       if not self.mediumtype_collected:
228          self.collect_mediumtype();
229       
230       return self.diskstatus == "blank"
231
232    def free(self):
233       if not self.freespace_collected:
234          self.collect_freespace();
235       
236       fr = self.capacity-self.next_session-self.margin
237       if fr < 0:
238          return 0
239       else:
240          return fr
241
242    def term_handler(self, signum, frame):
243       print 'dvd-handler: Signal term_handler called with signal', signum
244       if self.pid != 0:
245          print "dvd-handler: Sending SIGTERM to pid", self.pid
246          os.kill(self.pid, signal.SIGTERM)
247          time.sleep(10)
248          print "dvd-handler: Sending SIGKILL to pid", self.pid
249          os.kill(self.pid, signal.SIGKILL)
250          sys.exit(1)
251
252    def write(self, newvol, partfile):
253       # Blank DVD+RW when there is no data on it
254       if newvol and self.is_plus_RW() and self.is_blank():
255          print "DVD+RW looks brand-new, blank it to fix some DVD-writers bugs."
256          self.blank()
257          print "Done, now writing the part file."
258       
259       if newvol and self.is_minus_RW() and (not self.is_restricted_overwrite()):
260          print "DVD-RW is in " + self.diskmode + " mode, reformating it to Restricted Overwrite"
261          self.reformat_minus_RW()
262          print "Done, now writing the part file."
263       
264       cmd = self.growcmd + self.growparams
265       if newvol:
266          cmd += " -Z "
267       else:
268          cmd += " -M "
269       cmd += self.device + " " + str(partfile)
270       print "Running " + cmd
271       oldsig = signal.signal(signal.SIGTERM, self.term_handler)
272       proc = popen2.Popen4(cmd)
273       self.pid = proc.pid
274       status = proc.poll()
275       while status == -1:
276          line = proc.fromchild.readline()
277          while len(line) > 0:
278             print line,
279             line = proc.fromchild.readline()
280          time.sleep(1)
281          status = proc.poll()
282       self.pid = 0
283       print
284       signal.signal(signal.SIGTERM, oldsig)
285       if os.WEXITSTATUS(status) != 0:
286          raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
287
288    def blank(self):
289       cmd = self.growcmd + " -Z " + self.device + "=/dev/zero"
290       print "Running " + cmd
291       oldsig = signal.signal(signal.SIGTERM, self.term_handler)
292       proc = popen2.Popen4(cmd)
293       self.pid = proc.pid
294       status = proc.poll()
295       while status == -1:
296          line = proc.fromchild.readline()
297          while len(line) > 0:
298             print line,
299             line = proc.fromchild.readline()
300          time.sleep(1)
301          status = proc.poll()
302       self.pid = 0
303       print
304       signal.signal(signal.SIGTERM, oldsig)
305       if os.WEXITSTATUS(status) != 0:
306          raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
307
308    def reformat_minus_RW(self):
309       cmd = self.dvdrwformat + " -force " + self.device
310       print "Running " + cmd
311       oldsig = signal.signal(signal.SIGTERM, self.term_handler)
312       proc = popen2.Popen4(cmd)
313       self.pid = proc.pid
314       status = proc.poll()
315       while status == -1:
316          line = proc.fromchild.readline()
317          while len(line) > 0:
318             print line,
319             line = proc.fromchild.readline()
320          time.sleep(1)
321          status = proc.poll()
322       self.pid = 0
323       print
324       signal.signal(signal.SIGTERM, oldsig)
325       if os.WEXITSTATUS(status) != 0:
326          raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
327
328 # class disk ends here.
329
330 class DVDError(Exception):
331    def __init__(self, errno, value):
332       self.errno = errno
333       self.value = value
334       if self.value[-1] == '\n':
335          self.value = self.value[0:-1]
336    def __str__(self):
337       return str(self.value) + " || errno = " + str(self.errno) + " (" + os.strerror(self.errno & 0x7F) + ")"
338
339 def usage():
340    print "Wrong number of arguments."
341    print """
342 Usage:
343
344 dvd-handler DEVICE test
345 dvd-handler DEVICE free
346 dvd-handler DEVICE write APPEND FILE
347
348 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
349
350 Operations:
351 test       Scan the device and report the information found.
352            This operation needs no further arguments.
353 free       Scan the device and report the available space.
354 write      Write a part file to disk.
355            This operation needs two additional arguments.
356            The first indicates to append (0) or restart the
357            disk (1). The second is the file to write.
358 """
359    sys.exit(1)
360
361 if len(sys.argv) < 3:
362    usage()
363
364 dvd = disk(sys.argv[1])
365
366 if "free" == sys.argv[2]:
367    if len(sys.argv) == 3:
368       try:
369          free = dvd.free()
370       except DVDError, e:
371          if e.errno != 0:
372             print -e.errno
373          else:
374             print errno.EPIPE
375          print str(e)
376       else:
377          print free
378          print "No Error reported."
379    else:
380       print "Wrong number of arguments for free operation."
381       usage()
382 elif "test" == sys.argv[2]:
383    try:
384       print str(dvd)
385       print "Blank disk: " + str(dvd.is_blank()) + " ReWritable disk: " + str(dvd.is_RW())
386       print "Free space: " + str(dvd.free())
387    except DVDError, e:
388       print "Error while getting informations: ", str(e)
389 elif "write" == sys.argv[2]:
390    if len(sys.argv) == 5:
391       try:
392          dvd.write(long(sys.argv[3]), sys.argv[4])
393       except DVDError, e:
394          print "Error while writing part file: ", str(e)
395          if e.errno != 0:
396             sys.exit(e.errno & 0x7F)
397          else:
398             sys.exit(errno.EPIPE)
399       else:
400          print "Part file " + sys.argv[4] + " successfully written to disk."
401    else:
402       print "Wrong number of arguments for write operation."
403       usage()
404       sys.exit(1)
405 else:
406    print "No operation - use test, free or write."
407    print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"
408 sys.exit(0)