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
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.
29 # Configurable values:
30 dvdrwmediainfo = "@DVDRWMEDIAINFO@"
31 growcmd = "@GROWISOFS@"
32 margin = 10485760 # 10 mb security margin
34 # Comment the following line if you want the tray to be reloaded
36 growcmd += " -use-the-force-luke=notray"
38 # end of configurable values
40 ## Check if we want to allow growisofs to cross the 4gb boundary
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."
47 if os.WEXITSTATUS(status) != 0:
48 # print "dvd-writepart: Cannot execute uname, allowing to cross the 4gb boundary."
50 strres = processi.fromchild.readline()[0:-1]
51 res = strres.split(" ")
53 # print "dvd-writepart: Unable to parse uname (" + strres + "), allowing to cross the 4gb boundary."
56 # print "dvd-writepart: The current OS is no Linux, allowing to cross the 4gb boundary."
58 ver = res[1].split(".")
60 # print "dvd-writepart: Unable to parse version string (" + res[1] + "), allowing to cross the 4gb boundary."
62 subver = ver[2].split("-")
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."
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."
72 # print "dvd-writepart: Kernel version <2.6.8, not allowing to cross the 4gb boundary."
75 class DVDError(Exception):
76 def __init__(self, errno, value):
79 if self.value[-1] == '\n':
80 self.value = self.value[0:-1]
82 return str(self.value) + " || errno = " + str(self.errno) + " (" + os.strerror(self.errno & 0x7F) + ")"
85 ###############################################################################
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).
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
112 ###############################################################################
113 def __init__(self, devicename):
114 self.device = devicename
115 self.disktype = "none"
116 self.hardwaredevice = "none"
118 self.next_session = -1
121 self.freespace_collected = 0
122 self.mediumtype_collected = 0
127 return "disk(" + self.device + ") # This is an instance of class disk"
130 if not self.freespace_collected:
131 self.collect_freespace();
132 if not self.mediumtype_collected:
133 self.collect_mediumtype();
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"
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
153 self.freespace_collected = 1
156 raise DVDError(os.WEXITSTATUS(status), "growisofs returned with an error " + result + ". Please check your are using a patched version of dvd+rw-tools.")
157 next_sess = re.search(r"\snext_session=(\d+)\s", result, re.MULTILINE)
158 capa = re.search(r"\capacity=(\d+)\s", result, re.MULTILINE)
160 if next_sess and capa:
161 self.next_session = long(next_sess.group(1))
162 self.capacity = long(capa.group(1))
164 # testing cheat (emulate 4GB boundary at 100MB)
165 #if self.next_session > 100000000:
166 # self.capacity = self.next_session
168 raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
170 self.freespace_collected = 1
173 def collect_mediumtype(self): # Collects current medium type
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)
183 result = processi.fromchild.read()
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)
189 self.hardwaredevice = hardware.group(1)
191 self.disktype = mediatype.group(2)
193 raise DVDError("Media type not found in " + dvdrwmediainfo + " output")
194 self.mediumtype_collected = 1
198 if not self.freespace_collected:
199 self.collect_freespace();
201 return 0 == self.next_session
204 if not self.mediumtype_collected:
205 self.collect_mediumtype();
207 return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
210 if not self.freespace_collected:
211 self.collect_freespace();
213 return self.capacity-self.next_session-margin
215 def term_handler(self, signum, frame):
216 print 'dvd-handler: Signal term_handler called with signal', signum
218 print "dvd-handler: Sending SIGTERM to pid", self.pid
219 os.kill(self.pid, signal.SIGTERM)
221 print "dvd-handler: Sending SIGKILL to pid", self.pid
222 os.kill(self.pid, signal.SIGKILL)
225 def write(self, newvol, partfile):
226 # Blank DVD+/-RW/-RAM when there is no data on it
227 # Ideally, we should only have to do it once, but I don't know how to
228 # identify if a disk is blank of if it is brand-new.
229 if newvol and self.is_RW() and self.is_empty():
230 print "DVD looks brand-new, blank it to fix some DVD-writers bugs."
232 print "Done, now writing the real part file."
234 cmd = growcmd + growparams
239 cmd += self.device + " " + str(partfile)
240 print "Running " + cmd
241 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
242 proc = popen2.Popen4(cmd)
246 line = proc.fromchild.readline()
249 line = proc.fromchild.readline()
254 signal.signal(signal.SIGTERM, oldsig)
255 if os.WEXITSTATUS(status) != 0:
256 raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
259 cmd = growcmd + " -Z " + self.device + "=/dev/zero"
260 print "Running " + cmd
261 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
262 proc = popen2.Popen4(cmd)
266 line = proc.fromchild.readline()
269 line = proc.fromchild.readline()
274 signal.signal(signal.SIGTERM, oldsig)
275 if os.WEXITSTATUS(status) != 0:
276 raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
278 # class disk ends here.
281 print "Wrong number of arguments."
285 dvd-handler DEVICE test
286 dvd-handler DEVICE free
287 dvd-handler DEVICE write APPEND FILE
289 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
292 test Scan the device and report the information found.
293 This operation needs no further arguments.
294 free Scan the device and report the available space.
295 write Write a part file to disk.
296 This operation needs two additional arguments.
297 The first indicates to append (0) or restart the
298 disk (1). The second is the file to write.
302 if len(sys.argv) < 3:
308 growcmd += " -use-the-force-luke=4gms"
310 growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
311 "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
313 dvd = disk(sys.argv[1])
315 if "free" == sys.argv[2]:
316 if len(sys.argv) == 3:
327 print "No Error reported."
329 print "Wrong number of arguments for free operation."
331 elif "test" == sys.argv[2]:
334 print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
335 print "Free space: " + str(dvd.free())
337 print "Error while getting informations: ", str(e)
338 elif "write" == sys.argv[2]:
339 if len(sys.argv) == 5:
341 dvd.write(long(sys.argv[3]), sys.argv[4])
343 print "Error while writing part file: ", str(e)
345 sys.exit(e.errno & 0x7F)
347 sys.exit(errno.EPIPE)
349 print "Part file " + sys.argv[4] + " successfully written to disk."
351 print "Wrong number of arguments for write operation."
355 print "No operation - use test, free or write."
356 print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"