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 NOT IMPLEMENTED
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)
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 # testing cheat (emulate 4GB boundary at 100MB)
162 #if long(next_session.group(1)) > 100000000:
165 self.next_session = long(next_sess.group(1))
166 self.capacity = long(capa.group(1))
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 ## TODO: blank DVD+RW when needed
233 cmd += self.device + " " + str(partfile)
234 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
235 proc = popen2.Popen4(cmd)
239 out = proc.fromchild.read(512)
241 sys.stdout.write(out)
242 out = proc.fromchild.read(512)
247 signal.signal(signal.SIGTERM, oldsig)
248 if os.WEXITSTATUS(status) != 0:
249 raise DVDError(os.WEXITSTATUS(status), growcmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
251 # class disk ends here.
254 print "Wrong number of arguments."
258 dvd-handler DEVICE test
259 dvd-handler DEVICE free
260 dvd-handler DEVICE write APPEND FILE
262 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
265 test Scan the device and report the information found.
266 This operation needs no further arguments.
267 free Scan the device and report the available space.
268 write Write a part file to disk.
269 This operation needs two additional arguments.
270 The first indicates to append (0) or restart the
271 disk (1). The second is the file to write.
275 if len(sys.argv) < 3:
278 growcmd += " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
279 "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
282 growcmd += " -use-the-force-luke=4gms"
284 dvd = disk(sys.argv[1])
286 if "free" == sys.argv[2]:
287 if len(sys.argv) == 3:
298 print "No Error reported."
300 print "Wrong number of arguments for free operation."
302 elif "test" == sys.argv[2]:
305 print "Empty disk: " + str(dvd.is_empty()) + " ReWritable disk: " + str(dvd.is_RW())
306 print "Free space: " + str(dvd.free())
308 print "Error while getting informations: ", str(e)
309 elif "write" == sys.argv[2]:
310 if len(sys.argv) == 5:
312 dvd.write(long(sys.argv[3]), sys.argv[4])
314 print "Error while writing part file: ", str(e)
316 sys.exit(e.errno & 0x7F)
318 sys.exit(errno.EPIPE)
320 print "Part file " + sys.argv[4] + " successfully written to disk."
322 print "Wrong number of arguments for write operation."
326 print "No operation - use test, free or write."
327 print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"