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...)
7 # called: dvd-handler <dvd-device-name> operation args
9 # where operation is one of
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
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.
27 # end of configurable values
38 # Configurable values:
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'"
51 ###############################################################################
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.
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
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
87 ###############################################################################
88 def __init__(self, devicename):
90 self.disktype = "none"
95 self.lasterror = "none"
96 self.hardwaredevice = "none"
99 # first, we collect information about the media as reported by
101 # we need an indication of the usable total size.
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."
109 if os.WEXITSTATUS(self.status) != 0:
110 self.lasterror = "Cannot get media info from " + self.dvdrwmediainfo
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)
122 self.hardwaredevice = self.hardware.group(1)
124 self.disktype = self.mediatype.group(2)
126 self.lasterror = "Media type not found."
128 self.leadout = long(self.leadout.group(1))*2048
130 self.lasterror = "Lead-out block not found."
132 self.track = long(self.tracksize.group(1))*2048
134 self.lasterror = "Track size not found."
136 if ( "DVD+RW" == self.disktype ):
137 if self.leadout > self.track:
138 self.result = self.leadout
140 self.result = self.track
142 self.lasterror = "Unsupported media: " + self.disktype
143 self.maximum = self.result - self.margin
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.
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."
163 self.exitstat = os.WEXITSTATUS(self.status) & ~0x80
164 if self.exitstat == errno.ENOSPC:
166 elif self.exitstat != 0:
167 self.lasterror = os.strerror(self.exitstat)
169 self.dftext = self.process.fromchild.read()
170 self.blocks = re.search(self.device + r"\s+(\d+)\s+",
171 self.dftext, re.MULTILINE)
173 self.used = long(self.blocks.group(1))*1024
176 self.lasterror = "No blocks found in " + self.cmd + " output:\n"
177 self.lasterror += self.dftext
181 return "disk(" + self.device + ") # This is an instance of class disk"
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"
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.
200 return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
202 def free(self, newvol):
203 if self.used < 0 or self.maximum <= 0:
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
211 self.lasterror = self.disktype + " can not be overwritten and is already used"
213 self.ret = self.maximum - self.used
214 if self.used > 4278190080: # if more than 4GB-16MB are already used
219 return self.lasterror
221 def term_handler(self, signum, frame):
222 print 'dvd-handler: Signal term_handler called with signal', signum
224 print "dvd-handler: Sending SIGTERM to pid", self.pid
225 os.kill(self.pid, signal.SIGTERM)
227 print "dvd-handler: Sending SIGKILL to pid", self.pid
228 os.kill(self.pid, signal.SIGKILL)
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."
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))
241 if "DVD-RW" == self.disktype:
242 self.cmd = self.growcmd + " -R "
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)
258 self.status = self.proc.poll()
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.
270 if len(sys.argv) < 3:
271 print "Wrong number of arguments."
273 This program needs to be called with the following parameters.
275 device operation [more arguments]
277 where device is a device name like /dev/sr0 or /dev/dvd and
278 operation can be "test", "free" or "write".
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.
295 dvd = disk(sys.argv[1])
297 if "free" == sys.argv[2]:
298 if len(sys.argv) == 4:
300 if "0" == sys.argv[3]:
302 free = dvd.free(newvol)
305 print "No Error reported."
309 print "Wrong number of arguments."
311 elif "test" == sys.argv[2]:
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:
319 if "0" == sys.argv[3]:
321 dvd.write(newvol, sys.argv[4])
322 if "none" != dvd.lasterr():
323 print str(dvd.lasterr())
326 print "Part file " + sys.argv[4] + " successfully written to disk."
328 print "Wrong number of arguments."
331 print "No operation - use test, free or write."
332 print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"