3 # Modified version of dvd-handler used to simulate reading/writing
4 # to a DVD but using disk storage. This is a pretty crude implementation
5 # and a lot of the old code is still here and just sort of blunders
8 # called: dvd-simulator <dvd-device-name> operation args
10 # operations used by Bacula:
13 # Scan the device and report the available space.
16 # Write a part file to disk.
17 # This operation needs two additional arguments.
18 # The first (op) indicates to
20 # 1 -- first write to a blank disk
21 # 2 -- blank or truncate a disk
23 # The second is the filename to write
25 # operations available but not used by Bacula:
27 # test Scan the device and report the information found.
28 # This operation needs no further arguments.
29 # prepare Prepare a DVD+/-RW for being used by Bacula.
30 # Note: This is only useful if you already have some
31 # non-Bacula data on a medium, and you want to use
32 # it with Bacula. Don't run this on blank media, it
36 # in case of operation ``free'' returns:
37 # Prints on the first output line the free space available in bytes.
38 # If an error occurs, prints a negative number (-errno), followed,
39 # on the second line, by an error message.
56 # Configurable values:
58 dvdrwmediainfo = "@DVDRWMEDIAINFO@"
59 growcmd = "@GROWISOFS@"
60 dvdrwformat = "@DVDRWFORMAT@"
62 margin = 10485760 # 10 mb security margin
64 # Comment the following line if you want the tray to be reloaded
66 growcmd += " -use-the-force-luke=notray"
68 # end of configurable values
70 ###############################################################################
72 # This class represents DVD disk informations.
73 # When instantiated, it needs a device name.
74 # Status information about the device and the disk loaded is collected only when
75 # asked for (for example dvd-freespace doesn't need to know the media type, and
76 # dvd-writepart doesn't not always need to know the free space).
78 # The following methods are implemented:
79 # __init__ we need that...
80 # __repr__ this seems to be a good idea to have.
81 # Quite minimalistic implementation, though.
82 # __str__ For casts to string. Return the current disk information
83 # is_empty Returns TRUE if the disk is empty, blank... this needs more
84 # work, especially concerning non-RW media and blank vs. no
85 # filesystem considerations. Here, we should also look for
86 # other filesystems - probably we don't want to silently
87 # overwrite UDF or ext2 or anything not mentioned in fstab...
88 # (NB: I don't think it is a problem)
89 # free Returns the available free space.
90 # write Writes one part file to disk, either starting a new file
91 # system on disk, or appending to it.
92 # This method should also prepare a blank disk so that a
93 # certain part of the disk is used to allow detection of a
94 # used disk by all / more disk drives.
95 # blank Blank the device
97 ###############################################################################
98 def __init__(self, devicename):
99 self.device = devicename
100 self.disktype = "none"
101 self.diskmode = "none"
102 self.diskstatus = "none"
103 self.hardwaredevice = "none"
105 self.next_session = -1
107 self.maxcapacity = 4000000000
109 self.freespace_collected = 0
110 self.mediumtype_collected = 0
112 self.growcmd += " -quiet"
114 if self.is4gbsupported():
115 self.growcmd += " -use-the-force-luke=4gms"
117 self.growparams = " -A 'Bacula Data' -input-charset=default -iso-level 3 -pad " + \
118 "-p 'dvd-handler / growisofs' -sysid 'BACULADATA' -R"
123 return "disk(" + self.device + ") # This is an instance of class disk"
126 if not self.freespace_collected:
127 self.collect_freespace();
128 if not self.mediumtype_collected:
129 self.collect_mediumtype();
131 self.me = "Class disk, initialized with device '" + self.device + "'\n"
132 self.me += "type = '" + self.disktype + "' mode='" + self.diskmode + "' status = '" + self.diskstatus + "'\n"
133 self.me += " next_session = " + str(self.next_session) + " capacity = " + str(self.capacity) + "\n"
134 self.me += "Hardware device is '" + self.hardwaredevice + "'\n"
135 self.me += "growcmd = '" + self.growcmd + "'\ngrowparams = '" + self.growparams + "'\n"
138 ## Check if we want to allow growisofs to cross the 4gb boundary
139 def is4gbsupported(self):
140 processi = popen2.Popen4("uname -s -r")
141 status = processi.wait()
142 if not os.WIFEXITED(status):
144 if os.WEXITSTATUS(status) != 0:
146 strres = processi.fromchild.readline()[0:-1]
147 version = re.search(r"Linux (\d+)\.(\d+)\.(\d+)", strres)
148 if not version: # Non-Linux: allow
151 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)):
156 def collect_freespace(self): # Collects current free space
157 self.next_session = 0
158 self.capacity = 4000000000
159 self.freespace_collected = 1
161 cmd = "du -sb " + self.device
162 processi = popen2.Popen4(cmd)
163 status = processi.wait()
164 if not os.WIFEXITED(status):
166 if os.WEXITSTATUS(status) != 0:
168 result = processi.fromchild.read()
170 used = re.search(r"(\d+)\s", result, re.MULTILINE)
172 self.capacity = self.maxcapacity - long(used.group(1))
173 if self.capacity < 0:
178 def collect_mediumtype(self): # Collects current medium type
180 cmd = self.dvdrwmediainfo + " " + self.device
181 processi = popen2.Popen4(cmd)
182 status = processi.wait()
183 if not os.WIFEXITED(status):
184 raise DVDError(0, self.dvdrwmediainfo + " process did not exit correctly.")
185 if os.WEXITSTATUS(status) != 0:
186 raise DVDError(0, "Cannot get media info from " + self.dvdrwmediainfo)
188 result = processi.fromchild.read()
190 hardware = re.search(r"INQUIRY:\s+(.*)\n", result, re.MULTILINE)
191 mediatype = re.search(r"\sMounted Media:\s+([0-9A-F]{2})h, (\S*)\s", result, re.MULTILINE)
192 mediamode = re.search(r"\sMounted Media:\s+[0-9A-F]{2}h, \S* (.*)\n", result, re.MULTILINE)
193 status = re.search(r"\sDisc status:\s+(.*)\n", result, re.MULTILINE)
196 self.hardwaredevice = hardware.group(1)
199 self.disktype = mediatype.group(2)
201 raise DVDError(0, "Media type not found in " + self.dvdrwmediainfo + " output")
203 if self.disktype == "DVD-RW":
205 self.diskmode = mediamode.group(1)
207 raise DVDError(0, "Media mode not found for DVD-RW in " + self.dvdrwmediainfo + " output")
210 self.diskstatus = status.group(1)
212 raise DVDError(0, "Disc status not found in " + self.dvdrwmediainfo + " output")
215 self.mediumtype_collected = 1
219 if not self.freespace_collected:
220 self.collect_freespace();
222 return 0 == self.next_session
225 if not self.mediumtype_collected:
226 self.collect_mediumtype();
227 return "DVD-RW" == self.disktype or "DVD+RW" == self.disktype or "DVD-RAM" == self.disktype
229 def is_plus_RW(self):
230 if not self.mediumtype_collected:
231 self.collect_mediumtype();
232 return "DVD+RW" == self.disktype
234 def is_minus_RW(self):
235 if not self.mediumtype_collected:
236 self.collect_mediumtype();
237 return "DVD-RW" == self.disktype
239 def is_restricted_overwrite(self):
240 if not self.mediumtype_collected:
241 self.collect_mediumtype();
242 return self.diskmode == "Restricted Overwrite"
245 if not self.mediumtype_collected:
246 self.collect_mediumtype();
248 return self.diskstatus == "blank"
251 if not self.freespace_collected:
252 self.collect_freespace();
254 fr = self.capacity-self.next_session-self.margin
260 def term_handler(self, signum, frame):
261 print 'dvd-handler: Signal term_handler called with signal', signum
263 print "dvd-handler: Sending SIGTERM to pid", self.pid
264 os.kill(self.pid, signal.SIGTERM)
266 print "dvd-handler: Sending SIGKILL to pid", self.pid
267 os.kill(self.pid, signal.SIGKILL)
270 def write(self, newvol, partfile):
272 print "Newvol", newvol
273 print "Zap everything ..."
274 os.system("rm -f "+self.device+"/*")
275 print "cp", partfile, self.device
276 shutil.copy(partfile,self.device)
280 raise DVDError(0, "I won't prepare a non-rewritable medium")
282 # Blank DVD+RW when there is no data on it
283 if self.is_plus_RW() and self.is_blank():
284 print "DVD+RW looks brand-new, blank it to fix some DVD-writers bugs."
286 return # It has been completely blanked: Medium is ready to be used by Bacula
288 if self.is_minus_RW() and (not self.is_restricted_overwrite()):
289 print "DVD-RW is in " + self.diskmode + " mode, reformating it to Restricted Overwrite"
290 self.reformat_minus_RW()
291 return # Reformated: Medium is ready to be used by Bacula
293 # TODO: Check if /dev/fd/0 and /dev/zero exists, otherwise, run self.blank()
294 if not os.path.exists("/dev/fd/0") or not os.path.exists("/dev/zero"):
295 print "/dev/fd/0 or /dev/zero doesn't exist, blank the medium completely."
299 cmd = self.dd + " if=/dev/zero bs=1024 count=512 | " + self.growcmd + " -Z " + self.device + "=/dev/fd/0"
300 print "Running " + cmd
301 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
302 proc = popen2.Popen4(cmd)
306 line = proc.fromchild.readline()
309 line = proc.fromchild.readline()
314 signal.signal(signal.SIGTERM, oldsig)
315 if os.WEXITSTATUS(status) != 0:
316 raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
319 cmd = self.growcmd + " -Z " + self.device + "=/dev/zero"
320 print "Running " + cmd
321 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
322 proc = popen2.Popen4(cmd)
326 line = proc.fromchild.readline()
329 line = proc.fromchild.readline()
334 signal.signal(signal.SIGTERM, oldsig)
335 if os.WEXITSTATUS(status) != 0:
336 raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
338 def reformat_minus_RW(self):
339 cmd = self.dvdrwformat + " -force " + self.device
340 print "Running " + cmd
341 oldsig = signal.signal(signal.SIGTERM, self.term_handler)
342 proc = popen2.Popen4(cmd)
346 line = proc.fromchild.readline()
349 line = proc.fromchild.readline()
354 signal.signal(signal.SIGTERM, oldsig)
355 if os.WEXITSTATUS(status) != 0:
356 raise DVDError(os.WEXITSTATUS(status), cmd + " exited with status " + str(os.WEXITSTATUS(status)) + ", signal/status " + str(status))
358 # class disk ends here.
360 class DVDError(Exception):
361 def __init__(self, errno, value):
364 if self.value[-1] == '\n':
365 self.value = self.value[0:-1]
367 return str(self.value) + " || errno = " + str(self.errno) + " (" + os.strerror(self.errno & 0x7F) + ")"
370 print "Wrong number of arguments."
374 dvd-handler DEVICE test
375 dvd-handler DEVICE free
376 dvd-handler DEVICE write APPEND FILE
377 dvd-handler DEVICE blank
379 where DEVICE is a device name like /dev/sr0 or /dev/dvd.
382 test Scan the device and report the information found.
383 This operation needs no further arguments.
384 free Scan the device and report the available space.
385 write Write a part file to disk.
386 This operation needs two additional arguments.
387 The first indicates to append (0), restart the
388 disk (1) or restart existing disk (2). The second
389 is the file to write.
390 prepare Prepare a DVD+/-RW for being used by Bacula.
391 Note: This is only useful if you already have some
392 non-Bacula data on a medium, and you want to use
393 it with Bacula. Don't run this on blank media, it
398 if len(sys.argv) < 3:
401 dvd = disk(sys.argv[1])
403 if "free" == sys.argv[2]:
404 if len(sys.argv) == 3:
415 print "No Error reported."
417 print "Wrong number of arguments for free operation. Wanted 3 got", len(sys.argv)
418 print sys.argv[1], sys.argv[2], sys.argv[3]
420 elif "prepare" == sys.argv[2]:
421 if len(sys.argv) == 3:
425 print "Error while preparing medium: ", str(e)
427 sys.exit(e.errno & 0x7F)
429 sys.exit(errno.EPIPE)
431 print "Medium prepared successfully."
433 print "Wrong number of arguments for prepare operation."
435 elif "test" == sys.argv[2]:
438 print "Blank disk: " + str(dvd.is_blank()) + " ReWritable disk: " + str(dvd.is_RW())
439 print "Free space: " + str(dvd.free())
441 print "Error while getting informations: ", str(e)
442 elif "write" == sys.argv[2]:
443 if len(sys.argv) == 5:
445 dvd.write(long(sys.argv[3]), sys.argv[4])
447 print "Error while writing part file: ", str(e)
449 sys.exit(e.errno & 0x7F)
451 sys.exit(errno.EPIPE)
453 print "Part file " + sys.argv[4] + " successfully written to disk."
455 print "Wrong number of arguments for write operation."
459 print "No operation - use test, free, prepare or write."
460 print "THIS MIGHT BE A CASE OF DEBUGGING BACULA OR AN ERROR!"