Exemplo n.º 1
0
 def test_canAdaptToSFTPServer(self):
     avatar = self.makeCodehostingAvatar()
     # The adapter logs the SFTPStarted event, which gets the id of the
     # transport attribute of 'avatar'. Here we set transport to an
     # arbitrary object that can have its id taken.
     avatar.transport = object()
     server = ISFTPServer(avatar)
     self.assertIsInstance(server, TransportSFTPServer)
     product = self.factory.makeProduct()
     branch_name = self.factory.getUniqueString()
     deferred = server.makeDirectory(
         '~%s/%s/%s' % (avatar.username, product.name, branch_name),
         {'permissions': 0o777})
     return deferred
Exemplo n.º 2
0
 def test_canAdaptToSFTPServer(self):
     avatar = self.makeCodehostingAvatar()
     # The adapter logs the SFTPStarted event, which gets the id of the
     # transport attribute of 'avatar'. Here we set transport to an
     # arbitrary object that can have its id taken.
     avatar.transport = object()
     server = ISFTPServer(avatar)
     self.assertIsInstance(server, TransportSFTPServer)
     product = self.factory.makeProduct()
     branch_name = self.factory.getUniqueString()
     deferred = server.makeDirectory(
         '~%s/%s/%s' % (avatar.username, product.name, branch_name),
         {'permissions': 0777})
     return deferred
Exemplo n.º 3
0
 def __init__(self, data=None, avatar=None):
     FileTransferBase.__init__(self)
     self.client = ISFTPServer(avatar) # yay interfaces
     self.openFiles = {}
     self.openDirs = {}
Exemplo n.º 4
0
class FileTransferServer(FileTransferBase):

    def __init__(self, data=None, avatar=None):
        FileTransferBase.__init__(self)
        self.client = ISFTPServer(avatar) # yay interfaces
        self.openFiles = {}
        self.openDirs = {}

    def packet_INIT(self, data):
        version ,= struct.unpack('!L', data[:4])
        self.version = min(list(self.versions) + [version])
        data = data[4:]
        ext = {}
        while data:
            ext_name, data = getNS(data)
            ext_data, data = getNS(data)
            ext[ext_name] = ext_data
        our_ext = self.client.gotVersion(version, ext)
        our_ext_data = ""
        for (k,v) in our_ext.items():
            our_ext_data += NS(k) + NS(v)
        self.sendPacket(FXP_VERSION, struct.pack('!L', self.version) + \
                                     our_ext_data)

    def packet_OPEN(self, data):
        requestId = data[:4]
        data = data[4:]
        filename, data = getNS(data)
        flags ,= struct.unpack('!L', data[:4])
        data = data[4:]
        attrs, data = self._parseAttributes(data)
        assert data == '', 'still have data in OPEN: %s' % repr(data)
        d = defer.maybeDeferred(self.client.openFile, filename, flags, attrs)
        d.addCallback(self._cbOpenFile, requestId)
        d.addErrback(self._ebStatus, requestId, "open failed")

    def _cbOpenFile(self, fileObj, requestId):
        fileId = str(hash(fileObj))
        if fileId in self.openFiles:
            raise KeyError, 'id already open'
        self.openFiles[fileId] = fileObj
        self.sendPacket(FXP_HANDLE, requestId + NS(fileId))

    def packet_CLOSE(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        assert data == '', 'still have data in CLOSE: %s' % repr(data)
        if handle in self.openFiles:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.close)
            d.addCallback(self._cbClose, handle, requestId)
            d.addErrback(self._ebStatus, requestId, "close failed")
        elif handle in self.openDirs:
            dirObj = self.openDirs[handle][0]
            d = defer.maybeDeferred(dirObj.close)
            d.addCallback(self._cbClose, handle, requestId, 1)
            d.addErrback(self._ebStatus, requestId, "close failed")
        else:
            self._ebClose(failure.Failure(KeyError()), requestId)

    def _cbClose(self, result, handle, requestId, isDir = 0):
        if isDir:
            del self.openDirs[handle]
        else:
            del self.openFiles[handle]
        self._sendStatus(requestId, FX_OK, 'file closed')

    def packet_READ(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        (offset, length), data = struct.unpack('!QL', data[:12]), data[12:]
        assert data == '', 'still have data in READ: %s' % repr(data)
        if handle not in self.openFiles:
            self._ebRead(failure.Failure(KeyError()), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.readChunk, offset, length)
            d.addCallback(self._cbRead, requestId)
            d.addErrback(self._ebStatus, requestId, "read failed")

    def _cbRead(self, result, requestId):
        if result == '': # python's read will return this for EOF
            raise EOFError()
        self.sendPacket(FXP_DATA, requestId + NS(result))

    def packet_WRITE(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        offset, = struct.unpack('!Q', data[:8])
        data = data[8:]
        writeData, data = getNS(data)
        assert data == '', 'still have data in WRITE: %s' % repr(data)
        if handle not in self.openFiles:
            self._ebWrite(failure.Failure(KeyError()), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.writeChunk, offset, writeData)
            d.addCallback(self._cbStatus, requestId, "write succeeded")
            d.addErrback(self._ebStatus, requestId, "write failed")

    def packet_REMOVE(self, data):
        requestId = data[:4]
        data = data[4:]
        filename, data = getNS(data)
        assert data == '', 'still have data in REMOVE: %s' % repr(data)
        d = defer.maybeDeferred(self.client.removeFile, filename)
        d.addCallback(self._cbStatus, requestId, "remove succeeded")
        d.addErrback(self._ebStatus, requestId, "remove failed")

    def packet_RENAME(self, data):
        requestId = data[:4]
        data = data[4:]
        oldPath, data = getNS(data)
        newPath, data = getNS(data)
        assert data == '', 'still have data in RENAME: %s' % repr(data)
        d = defer.maybeDeferred(self.client.renameFile, oldPath, newPath)
        d.addCallback(self._cbStatus, requestId, "rename succeeded")
        d.addErrback(self._ebStatus, requestId, "rename failed")

    def packet_MKDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        attrs, data = self._parseAttributes(data)
        assert data == '', 'still have data in MKDIR: %s' % repr(data)
        d = defer.maybeDeferred(self.client.makeDirectory, path, attrs)
        d.addCallback(self._cbStatus, requestId, "mkdir succeeded")
        d.addErrback(self._ebStatus, requestId, "mkdir failed")

    def packet_RMDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == '', 'still have data in RMDIR: %s' % repr(data)
        d = defer.maybeDeferred(self.client.removeDirectory, path)
        d.addCallback(self._cbStatus, requestId, "rmdir succeeded")
        d.addErrback(self._ebStatus, requestId, "rmdir failed")

    def packet_OPENDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == '', 'still have data in OPENDIR: %s' % repr(data)
        d = defer.maybeDeferred(self.client.openDirectory, path)
        d.addCallback(self._cbOpenDirectory, requestId)
        d.addErrback(self._ebStatus, requestId, "opendir failed")

    def _cbOpenDirectory(self, dirObj, requestId):
        handle = str(hash(dirObj))
        if handle in self.openDirs:
            raise KeyError, "already opened this directory"
        self.openDirs[handle] = [dirObj, iter(dirObj)]
        self.sendPacket(FXP_HANDLE, requestId + NS(handle))

    def packet_READDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        assert data == '', 'still have data in READDIR: %s' % repr(data)
        if handle not in self.openDirs:
            self._ebStatus(failure.Failure(KeyError()), requestId)
        else:
            dirObj, dirIter = self.openDirs[handle]
            d = defer.maybeDeferred(self._scanDirectory, dirIter, [])
            d.addCallback(self._cbSendDirectory, requestId)
            d.addErrback(self._ebStatus, requestId, "scan directory failed")

    def _scanDirectory(self, dirIter, f):
        while len(f) < 250:
            try:
                info = dirIter.next()
            except StopIteration:
                if not f:
                    raise EOFError
                return f
            if isinstance(info, defer.Deferred):
                info.addCallback(self._cbScanDirectory, dirIter, f)
                return
            else:
                f.append(info)
        return f

    def _cbScanDirectory(self, result, dirIter, f):
        f.append(result)
        return self._scanDirectory(dirIter, f)

    def _cbSendDirectory(self, result, requestId):
        data = ''
        for (filename, longname, attrs) in result:
            data += NS(filename)
            data += NS(longname)
            data += self._packAttributes(attrs)
        self.sendPacket(FXP_NAME, requestId +
                        struct.pack('!L', len(result))+data)

    def packet_STAT(self, data, followLinks = 1):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == '', 'still have data in STAT/LSTAT: %s' % repr(data)
        d = defer.maybeDeferred(self.client.getAttrs, path, followLinks)
        d.addCallback(self._cbStat, requestId)
        d.addErrback(self._ebStatus, requestId, 'stat/lstat failed')

    def packet_LSTAT(self, data):
        self.packet_STAT(data, 0)

    def packet_FSTAT(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        assert data == '', 'still have data in FSTAT: %s' % repr(data)
        if handle not in self.openFiles:
            self._ebStatus(failure.Failure(KeyError('%s not in self.openFiles'
                                        % handle)), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.getAttrs)
            d.addCallback(self._cbStat, requestId)
            d.addErrback(self._ebStatus, requestId, 'fstat failed')

    def _cbStat(self, result, requestId):
        data = requestId + self._packAttributes(result)
        self.sendPacket(FXP_ATTRS, data)

    def packet_SETSTAT(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        attrs, data = self._parseAttributes(data)
        if data != '':
            log.msg('WARN: still have data in SETSTAT: %s' % repr(data))
        d = defer.maybeDeferred(self.client.setAttrs, path, attrs)
        d.addCallback(self._cbStatus, requestId, 'setstat succeeded')
        d.addErrback(self._ebStatus, requestId, 'setstat failed')

    def packet_FSETSTAT(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        attrs, data = self._parseAttributes(data)
        assert data == '', 'still have data in FSETSTAT: %s' % repr(data)
        if handle not in self.openFiles:
            self._ebStatus(failure.Failure(KeyError()), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.setAttrs, attrs)
            d.addCallback(self._cbStatus, requestId, 'fsetstat succeeded')
            d.addErrback(self._ebStatus, requestId, 'fsetstat failed')

    def packet_READLINK(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == '', 'still have data in READLINK: %s' % repr(data)
        d = defer.maybeDeferred(self.client.readLink, path)
        d.addCallback(self._cbReadLink, requestId)
        d.addErrback(self._ebStatus, requestId, 'readlink failed')

    def _cbReadLink(self, result, requestId):
        self._cbSendDirectory([(result, '', {})], requestId)

    def packet_SYMLINK(self, data):
        requestId = data[:4]
        data = data[4:]
        linkPath, data = getNS(data)
        targetPath, data = getNS(data)
        d = defer.maybeDeferred(self.client.makeLink, linkPath, targetPath)
        d.addCallback(self._cbStatus, requestId, 'symlink succeeded')
        d.addErrback(self._ebStatus, requestId, 'symlink failed')

    def packet_REALPATH(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == '', 'still have data in REALPATH: %s' % repr(data)
        d = defer.maybeDeferred(self.client.realPath, path)
        d.addCallback(self._cbReadLink, requestId) # same return format
        d.addErrback(self._ebStatus, requestId, 'realpath failed')

    def packet_EXTENDED(self, data):
        requestId = data[:4]
        data = data[4:]
        extName, extData = getNS(data)
        d = defer.maybeDeferred(self.client.extendedRequest, extName, extData)
        d.addCallback(self._cbExtended, requestId)
        d.addErrback(self._ebStatus, requestId, 'extended %s failed' % extName)

    def _cbExtended(self, data, requestId):
        self.sendPacket(FXP_EXTENDED_REPLY, requestId + data)

    def _cbStatus(self, result, requestId, msg = "request succeeded"):
        self._sendStatus(requestId, FX_OK, msg)

    def _ebStatus(self, reason, requestId, msg = "request failed"):
        code = FX_FAILURE
        message = msg
        if reason.type in (IOError, OSError):
            if reason.value.errno == errno.ENOENT: # no such file
                code = FX_NO_SUCH_FILE
                message = reason.value.strerror
            elif reason.value.errno == errno.EACCES: # permission denied
                code = FX_PERMISSION_DENIED
                message = reason.value.strerror
            elif reason.value.errno == errno.EEXIST:
                code = FX_FILE_ALREADY_EXISTS
            else:
                log.err(reason)
        elif reason.type == EOFError: # EOF
            code = FX_EOF
            if reason.value.args:
                message = reason.value.args[0]
        elif reason.type == NotImplementedError:
            code = FX_OP_UNSUPPORTED
            if reason.value.args:
                message = reason.value.args[0]
        elif reason.type == SFTPError:
            code = reason.value.code
            message = reason.value.message
        else:
            log.err(reason)
        self._sendStatus(requestId, code, message)

    def _sendStatus(self, requestId, code, message, lang = ''):
        """
        Helper method to send a FXP_STATUS message.
        """
        data = requestId + struct.pack('!L', code)
        data += NS(message)
        data += NS(lang)
        self.sendPacket(FXP_STATUS, data)


    def connectionLost(self, reason):
        """
        Clean all opened files and directories.
        """
        for fileObj in self.openFiles.values():
            fileObj.close()
        self.openFiles = {}
        for (dirObj, dirIter) in self.openDirs.values():
            dirObj.close()
        self.openDirs = {}
Exemplo n.º 5
0
 def __init__(self, data=None, avatar=None):
     FileTransferBase.__init__(self)
     self.client = ISFTPServer(avatar)  # yay interfaces
     self.openFiles = {}
     self.openDirs = {}
Exemplo n.º 6
0
class FileTransferServer(FileTransferBase):
    def __init__(self, data=None, avatar=None):
        FileTransferBase.__init__(self)
        self.client = ISFTPServer(avatar)  # yay interfaces
        self.openFiles = {}
        self.openDirs = {}

    def packet_INIT(self, data):
        (version, ) = struct.unpack('!L', data[:4])
        self.version = min(list(self.versions) + [version])
        data = data[4:]
        ext = {}
        while data:
            extName, data = getNS(data)
            extData, data = getNS(data)
            ext[extName] = extData
        ourExt = self.client.gotVersion(version, ext)
        ourExtData = b""
        for (k, v) in ourExt.items():
            ourExtData += NS(k) + NS(v)
        self.sendPacket(FXP_VERSION,
                        struct.pack('!L', self.version) + ourExtData)

    def packet_OPEN(self, data):
        requestId = data[:4]
        data = data[4:]
        filename, data = getNS(data)
        (flags, ) = struct.unpack('!L', data[:4])
        data = data[4:]
        attrs, data = self._parseAttributes(data)
        assert data == b'', 'still have data in OPEN: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.openFile, filename, flags, attrs)
        d.addCallback(self._cbOpenFile, requestId)
        d.addErrback(self._ebStatus, requestId, b"open failed")

    def _cbOpenFile(self, fileObj, requestId):
        fileId = networkString(str(hash(fileObj)))
        if fileId in self.openFiles:
            raise KeyError('id already open')
        self.openFiles[fileId] = fileObj
        self.sendPacket(FXP_HANDLE, requestId + NS(fileId))

    def packet_CLOSE(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        assert data == b'', 'still have data in CLOSE: {!r}'.format(data)
        if handle in self.openFiles:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.close)
            d.addCallback(self._cbClose, handle, requestId)
            d.addErrback(self._ebStatus, requestId, b"close failed")
        elif handle in self.openDirs:
            dirObj = self.openDirs[handle][0]
            d = defer.maybeDeferred(dirObj.close)
            d.addCallback(self._cbClose, handle, requestId, 1)
            d.addErrback(self._ebStatus, requestId, b"close failed")
        else:
            self._ebClose(failure.Failure(KeyError()), requestId)

    def _cbClose(self, result, handle, requestId, isDir=0):
        if isDir:
            del self.openDirs[handle]
        else:
            del self.openFiles[handle]
        self._sendStatus(requestId, FX_OK, b'file closed')

    def packet_READ(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        (offset, length), data = struct.unpack('!QL', data[:12]), data[12:]
        assert data == b'', 'still have data in READ: {!r}'.format(data)
        if handle not in self.openFiles:
            self._ebRead(failure.Failure(KeyError()), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.readChunk, offset, length)
            d.addCallback(self._cbRead, requestId)
            d.addErrback(self._ebStatus, requestId, b"read failed")

    def _cbRead(self, result, requestId):
        if result == b'':  # Python's read will return this for EOF
            raise EOFError()
        self.sendPacket(FXP_DATA, requestId + NS(result))

    def packet_WRITE(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        offset, = struct.unpack('!Q', data[:8])
        data = data[8:]
        writeData, data = getNS(data)
        assert data == b'', 'still have data in WRITE: {!r}'.format(data)
        if handle not in self.openFiles:
            self._ebWrite(failure.Failure(KeyError()), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.writeChunk, offset, writeData)
            d.addCallback(self._cbStatus, requestId, b"write succeeded")
            d.addErrback(self._ebStatus, requestId, b"write failed")

    def packet_REMOVE(self, data):
        requestId = data[:4]
        data = data[4:]
        filename, data = getNS(data)
        assert data == b'', 'still have data in REMOVE: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.removeFile, filename)
        d.addCallback(self._cbStatus, requestId, b"remove succeeded")
        d.addErrback(self._ebStatus, requestId, b"remove failed")

    def packet_RENAME(self, data):
        requestId = data[:4]
        data = data[4:]
        oldPath, data = getNS(data)
        newPath, data = getNS(data)
        assert data == b'', 'still have data in RENAME: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.renameFile, oldPath, newPath)
        d.addCallback(self._cbStatus, requestId, b"rename succeeded")
        d.addErrback(self._ebStatus, requestId, b"rename failed")

    def packet_MKDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        attrs, data = self._parseAttributes(data)
        assert data == b'', 'still have data in MKDIR: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.makeDirectory, path, attrs)
        d.addCallback(self._cbStatus, requestId, b"mkdir succeeded")
        d.addErrback(self._ebStatus, requestId, b"mkdir failed")

    def packet_RMDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == b'', 'still have data in RMDIR: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.removeDirectory, path)
        d.addCallback(self._cbStatus, requestId, b"rmdir succeeded")
        d.addErrback(self._ebStatus, requestId, b"rmdir failed")

    def packet_OPENDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == b'', 'still have data in OPENDIR: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.openDirectory, path)
        d.addCallback(self._cbOpenDirectory, requestId)
        d.addErrback(self._ebStatus, requestId, b"opendir failed")

    def _cbOpenDirectory(self, dirObj, requestId):
        handle = networkString((str(hash(dirObj))))
        if handle in self.openDirs:
            raise KeyError("already opened this directory")
        self.openDirs[handle] = [dirObj, iter(dirObj)]
        self.sendPacket(FXP_HANDLE, requestId + NS(handle))

    def packet_READDIR(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        assert data == b'', 'still have data in READDIR: {!r}'.format(data)
        if handle not in self.openDirs:
            self._ebStatus(failure.Failure(KeyError()), requestId)
        else:
            dirObj, dirIter = self.openDirs[handle]
            d = defer.maybeDeferred(self._scanDirectory, dirIter, [])
            d.addCallback(self._cbSendDirectory, requestId)
            d.addErrback(self._ebStatus, requestId, b"scan directory failed")

    def _scanDirectory(self, dirIter, f):
        while len(f) < 250:
            try:
                info = next(dirIter)
            except StopIteration:
                if not f:
                    raise EOFError
                return f
            if isinstance(info, defer.Deferred):
                info.addCallback(self._cbScanDirectory, dirIter, f)
                return
            else:
                f.append(info)
        return f

    def _cbScanDirectory(self, result, dirIter, f):
        f.append(result)
        return self._scanDirectory(dirIter, f)

    def _cbSendDirectory(self, result, requestId):
        data = b''
        for (filename, longname, attrs) in result:
            data += NS(filename)
            data += NS(longname)
            data += self._packAttributes(attrs)
        self.sendPacket(FXP_NAME,
                        requestId + struct.pack('!L', len(result)) + data)

    def packet_STAT(self, data, followLinks=1):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == b'', 'still have data in STAT/LSTAT: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.getAttrs, path, followLinks)
        d.addCallback(self._cbStat, requestId)
        d.addErrback(self._ebStatus, requestId, b'stat/lstat failed')

    def packet_LSTAT(self, data):
        self.packet_STAT(data, 0)

    def packet_FSTAT(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        assert data == b'', 'still have data in FSTAT: {!r}'.format(data)
        if handle not in self.openFiles:
            self._ebStatus(
                failure.Failure(
                    KeyError('{} not in self.openFiles'.format(handle))),
                requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.getAttrs)
            d.addCallback(self._cbStat, requestId)
            d.addErrback(self._ebStatus, requestId, b'fstat failed')

    def _cbStat(self, result, requestId):
        data = requestId + self._packAttributes(result)
        self.sendPacket(FXP_ATTRS, data)

    def packet_SETSTAT(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        attrs, data = self._parseAttributes(data)
        if data != b'':
            log.msg('WARN: still have data in SETSTAT: {!r}'.format(data))
        d = defer.maybeDeferred(self.client.setAttrs, path, attrs)
        d.addCallback(self._cbStatus, requestId, b'setstat succeeded')
        d.addErrback(self._ebStatus, requestId, b'setstat failed')

    def packet_FSETSTAT(self, data):
        requestId = data[:4]
        data = data[4:]
        handle, data = getNS(data)
        attrs, data = self._parseAttributes(data)
        assert data == b'', 'still have data in FSETSTAT: {!r}'.format(data)
        if handle not in self.openFiles:
            self._ebStatus(failure.Failure(KeyError()), requestId)
        else:
            fileObj = self.openFiles[handle]
            d = defer.maybeDeferred(fileObj.setAttrs, attrs)
            d.addCallback(self._cbStatus, requestId, b'fsetstat succeeded')
            d.addErrback(self._ebStatus, requestId, b'fsetstat failed')

    def packet_READLINK(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == b'', 'still have data in READLINK: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.readLink, path)
        d.addCallback(self._cbReadLink, requestId)
        d.addErrback(self._ebStatus, requestId, b'readlink failed')

    def _cbReadLink(self, result, requestId):
        self._cbSendDirectory([(result, b'', {})], requestId)

    def packet_SYMLINK(self, data):
        requestId = data[:4]
        data = data[4:]
        linkPath, data = getNS(data)
        targetPath, data = getNS(data)
        d = defer.maybeDeferred(self.client.makeLink, linkPath, targetPath)
        d.addCallback(self._cbStatus, requestId, b'symlink succeeded')
        d.addErrback(self._ebStatus, requestId, b'symlink failed')

    def packet_REALPATH(self, data):
        requestId = data[:4]
        data = data[4:]
        path, data = getNS(data)
        assert data == b'', 'still have data in REALPATH: {!r}'.format(data)
        d = defer.maybeDeferred(self.client.realPath, path)
        d.addCallback(self._cbReadLink, requestId)  # Same return format
        d.addErrback(self._ebStatus, requestId, b'realpath failed')

    def packet_EXTENDED(self, data):
        requestId = data[:4]
        data = data[4:]
        extName, extData = getNS(data)
        d = defer.maybeDeferred(self.client.extendedRequest, extName, extData)
        d.addCallback(self._cbExtended, requestId)
        d.addErrback(self._ebStatus, requestId,
                     b'extended ' + extName + b' failed')

    def _cbExtended(self, data, requestId):
        self.sendPacket(FXP_EXTENDED_REPLY, requestId + data)

    def _cbStatus(self, result, requestId, msg=b"request succeeded"):
        self._sendStatus(requestId, FX_OK, msg)

    def _ebStatus(self, reason, requestId, msg=b"request failed"):
        code = FX_FAILURE
        message = msg
        if isinstance(reason.value, (IOError, OSError)):
            if reason.value.errno == errno.ENOENT:  # No such file
                code = FX_NO_SUCH_FILE
                message = networkString(reason.value.strerror)
            elif reason.value.errno == errno.EACCES:  # Permission denied
                code = FX_PERMISSION_DENIED
                message = networkString(reason.value.strerror)
            elif reason.value.errno == errno.EEXIST:
                code = FX_FILE_ALREADY_EXISTS
            else:
                log.err(reason)
        elif isinstance(reason.value, EOFError):  # EOF
            code = FX_EOF
            if reason.value.args:
                message = networkString(reason.value.args[0])
        elif isinstance(reason.value, NotImplementedError):
            code = FX_OP_UNSUPPORTED
            if reason.value.args:
                message = networkString(reason.value.args[0])
        elif isinstance(reason.value, SFTPError):
            code = reason.value.code
            message = networkString(reason.value.message)
        else:
            log.err(reason)
        self._sendStatus(requestId, code, message)

    def _sendStatus(self, requestId, code, message, lang=b''):
        """
        Helper method to send a FXP_STATUS message.
        """
        data = requestId + struct.pack('!L', code)
        data += NS(message)
        data += NS(lang)
        self.sendPacket(FXP_STATUS, data)

    def connectionLost(self, reason):
        """
        Clean all opened files and directories.
        """
        for fileObj in self.openFiles.values():
            fileObj.close()
        self.openFiles = {}
        for (dirObj, dirIter) in self.openDirs.values():
            dirObj.close()
        self.openDirs = {}