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.diskstatus = "none"
117 self.hardwaredevice = "none"
119 self.next_session = -1
122 self.freespace_collected = 0
123 self.mediumtype_collected = 0
128 return "disk(" + self.device + ") # This is an instance of class disk"
131 if not self.freespace_collected:
132 self.collect_freespace();
133 if not self.mediumtype_collected:
134 self.collect_mediumtype();
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"
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
154 self.freespace_collected = 1
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)
161 if next_sess and capa:
162 self.next_session = long(next_sess.group(1))
163 self.capacity = long(capa.group(1))
165 # testing cheat (emulate 4GB boundary at 100MB)
166 #if self.next_session > 100000000:
167 # self.capacity = self.next_session
169 raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
171 self.freespace_collected = 1
174 def collect_mediumtype(self): # Collects current medium type
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)
184 result = processi.fromchild.read()
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)
191 self.hardwaredevice = hardware.group(1)
194 self.disktype = mediatype.group(2)
196 raise DVDError("Media type not found in " + dvdrwmediainfo + " output")
199 self.diskstatus = status.group(1)
201 raise DVDError("Disc status not found in " + dvdrwmediainfo + " output")
204 self.mediumtype_collected = 1
208 if not self.freespace_collected:
209 self.collect_freespace();
211 return 0 == self.next_session
214 if not self.mediumtype_collected:
215 self.collect_mediumtype();
217 return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
220 if not self.mediumtype_collected:
221 self.collect_mediumtype();
223 return self.diskstatus == "blank"
226 if not self.freespace_collected:
227 self.collect_freespace();
229 fr = self.capacity-self.next_session-margin
235 def term_handler(self, signum, frame):
236 print 'dvd-handler: Signal term_handler called with signal', signum
238 print "dvd-handler: Sending SIGTERM to pid", self.pid
239 os.kill(self.pid, signal.SIGTERM)
241 print "dvd-handler: Sending SIGKILL to pid", self.pid
242 os.kill(self.pid, signal.SIGKILL)
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."
250 print "Done, now writing the real part file."
252 cmd = growcmd + growparams
257 cmd += self.device + " " + str(partfile)
258 print "Running " + cmd
259 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
260 proc = popen2.Popen4(cmd)
264 line = proc.fromchild.readline()
267 line = proc.fromchild.readline()
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))
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)
284 line = proc.fromchild.readline()
287 line = proc.fromchild.readline()
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))
296 # class disk ends here.
299 print "Wrong number of arguments."
303 dvd-handler DEVICE test
304 dvd-handler DEVICE free
305 dvd-handler DEVICE write APPEND FILE
307 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
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.
320 if len(sys.argv) < 3:
326 growcmd += " -use-the-force-luke=4gms"
328 growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
329 "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
331 dvd = disk(sys.argv[1])
333 if "free" == sys.argv[2]:
334 if len(sys.argv) == 3:
345 print "No Error reported."
347 print "Wrong number of arguments for free operation."
349 elif "test" == sys.argv[2]:
352 print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
353 print "Free space: " + str(dvd.free())
355 print "Error while getting informations: ", str(e)
356 elif "write" == sys.argv[2]:
357 if len(sys.argv) == 5:
359 dvd.write(long(sys.argv[3]), sys.argv[4])
361 print "Error while writing part file: ", str(e)
363 sys.exit(e.errno & 0x7F)
365 sys.exit(errno.EPIPE)
367 print "Part file " + sys.argv[4] + " successfully written to disk."
369 print "Wrong number of arguments for write operation."
373 print "No operation - use test, free or write."
374 print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"