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.
30 # Configurable values:
32 dvdrwmediainfo = "@DVDRWMEDIAINFO@"
33 growcmd = "@GROWISOFS@"
34 dvdrwformat = "@DVDRWFORMAT@"
35 margin = 10485760 # 10 mb security margin
37 # Comment the following line if you want the tray to be reloaded
39 growcmd += " -use-the-force-luke=notray"
41 # end of configurable values
43 ###############################################################################
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).
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
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"
78 self.next_session = -1
81 self.freespace_collected = 0
82 self.mediumtype_collected = 0
84 self.growcmd += " -quiet"
86 if self.is4gbsupported():
87 self.growcmd += " -use-the-force-luke=4gms"
89 self.growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
90 "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
95 return "disk(" + self.device + ") # This is an instance of class disk"
98 if not self.freespace_collected:
99 self.collect_freespace();
100 if not self.mediumtype_collected:
101 self.collect_mediumtype();
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"
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):
116 if os.WEXITSTATUS(status) != 0:
118 strres = processi.fromchild.readline()[0:-1]
119 version = re.search(r"Linux (\d+)\.(\d+)\.(\d+)", strres)
120 if not version: # Non-Linux: allow
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)):
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
140 self.freespace_collected = 1
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)
147 if next_sess and capa:
148 self.next_session = long(next_sess.group(1))
149 self.capacity = long(capa.group(1))
151 # testing cheat (emulate 4GB boundary at 100MB)
152 #if self.next_session > 100000000:
153 # self.capacity = self.next_session
155 raise DVDError(0, "Cannot get next_session and capacity from growisofs.\nReturned: " + result)
157 self.freespace_collected = 1
160 def collect_mediumtype(self): # Collects current medium type
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)
170 result = processi.fromchild.read()
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)
178 self.hardwaredevice = hardware.group(1)
181 self.disktype = mediatype.group(2)
183 raise DVDError("Media type not found in " + self.dvdrwmediainfo + " output")
185 if self.disktype == "DVD-RW":
187 self.diskmode = mediamode.group(1)
189 raise DVDError("Media mode not found for DVD-RW in " + self.dvdrwmediainfo + " output")
192 self.diskstatus = status.group(1)
194 raise DVDError("Disc status not found in " + self.dvdrwmediainfo + " output")
197 self.mediumtype_collected = 1
201 if not self.freespace_collected:
202 self.collect_freespace();
204 return 0 == self.next_session
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
211 def is_plus_RW(self):
212 if not self.mediumtype_collected:
213 self.collect_mediumtype();
214 return "DVD+RW" == self.disktype
216 def is_minus_RW(self):
217 if not self.mediumtype_collected:
218 self.collect_mediumtype();
219 return "DVD-RW" == self.disktype
221 def is_restricted_overwrite(self):
222 if not self.mediumtype_collected:
223 self.collect_mediumtype();
224 return self.diskmode == "Restricted Overwrite"
227 if not self.mediumtype_collected:
228 self.collect_mediumtype();
230 return self.diskstatus == "blank"
233 if not self.freespace_collected:
234 self.collect_freespace();
236 fr = self.capacity-self.next_session-self.margin
242 def term_handler(self, signum, frame):
243 print 'dvd-handler: Signal term_handler called with signal', signum
245 print "dvd-handler: Sending SIGTERM to pid", self.pid
246 os.kill(self.pid, signal.SIGTERM)
248 print "dvd-handler: Sending SIGKILL to pid", self.pid
249 os.kill(self.pid, signal.SIGKILL)
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."
257 print "Done, now writing the part file."
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."
264 cmd = self.growcmd + self.growparams
269 cmd += self.device + " " + str(partfile)
270 print "Running " + cmd
271 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
272 proc = popen2.Popen4(cmd)
276 line = proc.fromchild.readline()
279 line = proc.fromchild.readline()
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))
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)
296 line = proc.fromchild.readline()
299 line = proc.fromchild.readline()
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))
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)
316 line = proc.fromchild.readline()
319 line = proc.fromchild.readline()
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))
328 # class disk ends here.
330 class DVDError(Exception):
331 def __init__(self, errno, value):
334 if self.value[-1] == '\n':
335 self.value = self.value[0:-1]
337 return str(self.value) + " || errno = " + str(self.errno) + " (" + os.strerror(self.errno & 0x7F) + ")"
340 print "Wrong number of arguments."
344 dvd-handler DEVICE test
345 dvd-handler DEVICE free
346 dvd-handler DEVICE write APPEND FILE
348 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
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.
361 if len(sys.argv) < 3:
364 dvd = disk(sys.argv[1])
366 if "free" == sys.argv[2]:
367 if len(sys.argv) == 3:
378 print "No Error reported."
380 print "Wrong number of arguments for free operation."
382 elif "test" == sys.argv[2]:
385 print "Blank disk: " + str(dvd.is_blank()) + " ReWritable disk: " + str(dvd.is_RW())
386 print "Free space: " + str(dvd.free())
388 print "Error while getting informations: ", str(e)
389 elif "write" == sys.argv[2]:
390 if len(sys.argv) == 5:
392 dvd.write(long(sys.argv[3]), sys.argv[4])
394 print "Error while writing part file: ", str(e)
396 sys.exit(e.errno & 0x7F)
398 sys.exit(errno.EPIPE)
400 print "Part file " + sys.argv[4] + " successfully written to disk."
402 print "Wrong number of arguments for write operation."
406 print "No operation - use test, free or write."
407 print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"