import os, tempfile, heapq, binascii, traceback, array, stat, struct
+from types import NoneType
from stat import S_IFREG, S_IFDIR
from time import time, strftime, localtime
def _direntry_for(filenode_or_parent, childname, filenode=None):
+ assert isinstance(childname, (unicode, NoneType)), childname
+
if childname is None:
filenode_or_parent = filenode
PrefixingLogMixin.__init__(self, facility="tahoe.sftp", prefix=userpath)
if noisy: self.log(".__init__(%r, %r, %r)" % (userpath, filenode, metadata), level=NOISY)
- assert IFileNode.providedBy(filenode), filenode
+ assert isinstance(userpath, str) and IFileNode.providedBy(filenode), (userpath, filenode)
self.filenode = filenode
self.metadata = metadata
self.async = download_to_data(filenode)
if noisy: self.log(".__init__(%r, %r = %r, %r, <convergence censored>)" %
(userpath, flags, _repr_flags(flags), close_notify), level=NOISY)
+ assert isinstance(userpath, str), userpath
self.userpath = userpath
self.flags = flags
self.close_notify = close_notify
self.log(".open(parent=%r, childname=%r, filenode=%r, metadata=%r)" %
(parent, childname, filenode, metadata), level=OPERATIONAL)
+ assert isinstance(childname, (unicode, NoneType)), childname
# If the file has been renamed, the new (parent, childname) takes precedence.
if self.parent is None:
self.parent = parent
self.filenode = filenode
self.metadata = metadata
- assert not self.closed
+ assert not self.closed, self
tempfile_maker = EncryptedTemporaryFile
if (self.flags & FXF_TRUNC) or not filenode:
if noisy: self.log("open done", level=NOISY)
return self
+ def get_userpath(self):
+ return self.userpath
+
+ def get_direntry(self):
+ return _direntry_for(self.parent, self.childname)
+
def rename(self, new_userpath, new_parent, new_childname):
self.log(".rename(%r, %r, %r)" % (new_userpath, new_parent, new_childname), level=OPERATIONAL)
+ assert isinstance(new_userpath, str) and isinstance(new_childname, unicode), (new_userpath, new_childname)
self.userpath = new_userpath
self.parent = new_parent
self.childname = new_childname
for f in files:
f.abandon()
- def _add_heisenfiles_by_path(self, userpath, files_to_add):
- self.log("._add_heisenfiles_by_path(%r, %r)" % (userpath, files_to_add), level=OPERATIONAL)
+ def _add_heisenfile_by_path(self, file):
+ self.log("._add_heisenfile_by_path(%r)" % (file,), level=OPERATIONAL)
+ userpath = file.get_userpath()
if userpath in self._heisenfiles:
- self._heisenfiles[userpath] += files_to_add
+ self._heisenfiles[userpath] += [file]
else:
- self._heisenfiles[userpath] = files_to_add
+ self._heisenfiles[userpath] = [file]
- def _add_heisenfiles_by_direntry(self, direntry, files_to_add):
- self.log("._add_heisenfiles_by_direntry(%r, %r)" % (direntry, files_to_add), level=OPERATIONAL)
+ def _add_heisenfile_by_direntry(self, file):
+ self.log("._add_heisenfile_by_direntry(%r)" % (file,), level=OPERATIONAL)
+ direntry = file.get_direntry()
if direntry:
if direntry in all_heisenfiles:
- all_heisenfiles[direntry] += files_to_add
+ all_heisenfiles[direntry] += [file]
else:
- all_heisenfiles[direntry] = files_to_add
+ all_heisenfiles[direntry] = [file]
def _abandon_any_heisenfiles(self, userpath, direntry):
request = "._abandon_any_heisenfiles(%r, %r)" % (userpath, direntry)
self.log(request, level=OPERATIONAL)
+
+ assert isinstance(userpath, str), userpath
# First we synchronously mark all heisenfiles matching the userpath or direntry
# as abandoned, and remove them from the two heisenfile dicts. Then we .sync()
(from_userpath, from_parent, from_childname, to_userpath, to_parent, to_childname, overwrite))
self.log(request, level=OPERATIONAL)
+ assert (isinstance(from_userpath, str) and isinstance(from_childname, unicode) and
+ isinstance(to_userpath, str) and isinstance(to_childname, unicode)), \
+ (from_userpath, from_childname, to_userpath, to_childname)
+
+ if noisy: self.log("all_heisenfiles = %r\nself._heisenfiles = %r" % (all_heisenfiles, self._heisenfiles), level=NOISY)
+
# First we synchronously rename all heisenfiles matching the userpath or direntry.
# Then we .sync() each file that we renamed.
#
from_direntry = _direntry_for(from_parent, from_childname)
to_direntry = _direntry_for(to_parent, to_childname)
+ if noisy: self.log("from_direntry = %r, to_direntry = %r in %r" %
+ (from_direntry, to_direntry, request), level=NOISY)
+
if not overwrite and (to_userpath in self._heisenfiles or to_direntry in all_heisenfiles):
def _existing(): raise SFTPError(FX_PERMISSION_DENIED, "cannot rename to existing path " + to_userpath)
+ if noisy: self.log("existing", level=NOISY)
return defer.execute(_existing)
from_files = []
if noisy: self.log("from_files = %r in %r" % (from_files, request), level=NOISY)
- self._add_heisenfiles_by_direntry(to_direntry, from_files)
- self._add_heisenfiles_by_path(to_userpath, from_files)
-
for f in from_files:
f.rename(to_userpath, to_parent, to_childname)
+ self._add_heisenfile_by_path(f)
+ self._add_heisenfile_by_direntry(f)
d = defer.succeed(None)
for f in from_files:
d.addBoth(f.sync)
def _done(ign):
- self.log("done %r" % (request,), level=OPERATIONAL)
+ if noisy:
+ self.log("done %r\nall_heisenfiles = %r\nself._heisenfiles = %r" % (request, all_heisenfiles, self._heisenfiles), level=OPERATIONAL)
+ else:
+ self.log("done %r" % (request,), level=OPERATIONAL)
return len(from_files) > 0
d.addBoth(_done)
return d
request = "._update_attrs_for_heisenfiles(%r, %r, %r)" % (userpath, direntry, attrs)
self.log(request, level=OPERATIONAL)
+ assert isinstance(userpath, str) and isinstance(direntry, str), (userpath, direntry)
+
files = []
if direntry in all_heisenfiles:
files = all_heisenfiles[direntry]
request = "._sync_heisenfiles(%r, %r, ignore=%r)" % (userpath, direntry, ignore)
self.log(request, level=OPERATIONAL)
+ assert isinstance(userpath, str) and isinstance(direntry, (str, NoneType)), (userpath, direntry)
+
files = []
if direntry in all_heisenfiles:
files = all_heisenfiles[direntry]
def _remove_heisenfile(self, userpath, parent, childname, file_to_remove):
if noisy: self.log("._remove_heisenfile(%r, %r, %r, %r)" % (userpath, parent, childname, file_to_remove), level=NOISY)
+ assert isinstance(userpath, str) and isinstance(childname, (unicode, NoneType)), (userpath, childname)
+
direntry = _direntry_for(parent, childname)
if direntry in all_heisenfiles:
all_old_files = all_heisenfiles[direntry]
else:
del self._heisenfiles[userpath]
+ if noisy: self.log("all_heisenfiles = %r\nself._heisenfiles = %r" % (all_heisenfiles, self._heisenfiles), level=NOISY)
+
def _make_file(self, existing_file, userpath, flags, parent=None, childname=None, filenode=None, metadata=None):
if noisy: self.log("._make_file(%r, %r, %r = %r, parent=%r, childname=%r, filenode=%r, metadata=%r)" %
(existing_file, userpath, flags, _repr_flags(flags), parent, childname, filenode, metadata),
level=NOISY)
- assert metadata is None or 'no-write' in metadata, metadata
+ assert (isinstance(userpath, str) and isinstance(childname, (unicode, NoneType)) and
+ (metadata is None or 'no-write' in metadata)), (userpath, childname, metadata)
writing = (flags & (FXF_WRITE | FXF_CREAT)) != 0
direntry = _direntry_for(parent, childname, filenode)
def _got_file(file):
file.open(parent=parent, childname=childname, filenode=filenode, metadata=metadata)
if writing:
- self._add_heisenfiles_by_direntry(direntry, [file])
+ self._add_heisenfile_by_direntry(file)
return file
d.addCallback(_got_file)
return d
if flags & (FXF_WRITE | FXF_CREAT):
file = GeneralSFTPFile(userpath, flags, self._remove_heisenfile, self._convergence)
- self._add_heisenfiles_by_path(userpath, [file])
+ self._add_heisenfile_by_path(file)
else:
# We haven't decided which file implementation to use yet.
file = None
def _path_from_string(self, pathstring):
if noisy: self.log("CONVERT %r" % (pathstring,), level=NOISY)
+ assert isinstance(pathstring, str), pathstring
+
# The home directory is the root directory.
pathstring = pathstring.strip("/")
if pathstring == "" or pathstring == ".":
if isinstance(res, Failure):
res.trap(sftp.SFTPError)
self.failUnlessReallyEqual(res.value.code, expected_code,
- "%s was supposed to raise SFTPError(%d), not SFTPError(%d): %s" %
+ "%s was supposed to raise SFTPError(%r), not SFTPError(%r): %s" %
(which, expected_code, res.value.code, res))
else:
print '@' + '@'.join(s)
- self.fail("%s was supposed to raise SFTPError(%d), not get %r" %
+ self.fail("%s was supposed to raise SFTPError(%r), not get %r" %
(which, expected_code, res))
d.addBoth(_done)
return d
self.handler.extendedRequest, "foo", "bar"))
return d
+
+ def test_memory_leak(self):
+ d0 = self._set_up("memory_leak")
+
+ def _leaky(ign, i):
+ new_i = "new_%r" % (i,)
+ d = defer.succeed(None)
+ # Copied from test_openFile_write above:
+
+ # it should be possible to rename even before the open has completed
+ def _open_and_rename_race(ign):
+ slow_open = defer.Deferred()
+ reactor.callLater(1, slow_open.callback, None)
+ d2 = self.handler.openFile("new", sftp.FXF_WRITE | sftp.FXF_CREAT, {}, delay=slow_open)
+
+ # deliberate race between openFile and renameFile
+ d3 = self.handler.renameFile("new", new_i)
+ del d3
+ return d2
+ d.addCallback(_open_and_rename_race)
+ def _write_rename_race(wf):
+ d2 = wf.writeChunk(0, "abcd")
+ d2.addCallback(lambda ign: wf.close())
+ return d2
+ d.addCallback(_write_rename_race)
+ d.addCallback(lambda ign: self.root.get(unicode(new_i)))
+ d.addCallback(lambda node: download_to_data(node))
+ d.addCallback(lambda data: self.failUnlessReallyEqual(data, "abcd"))
+ d.addCallback(lambda ign:
+ self.shouldFail(NoSuchChildError, "rename new while open", "new",
+ self.root.get, u"new"))
+ return d
+
+ for index in range(3):
+ d0.addCallback(_leaky, index)
+
+ d0.addCallback(lambda ign: self.failUnlessEqual(sftpd.all_heisenfiles, {}))
+ d0.addCallback(lambda ign: self.failUnlessEqual(self.handler._heisenfiles, {}))
+ return d0