~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/vfs/adapters/sftp.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.vfs.test.test_sftp -*-
 
2
 
 
3
import os, time
 
4
 
 
5
import zope.interface
 
6
 
 
7
from twisted.python import components, log, util
 
8
from twisted.conch.avatar import ConchUser
 
9
from twisted.conch.interfaces import ISession, ISFTPFile
 
10
from twisted.conch.ssh.filetransfer import ISFTPServer, FileTransferServer
 
11
from twisted.conch.ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
 
12
from twisted.conch.ssh.filetransfer import SFTPError
 
13
from twisted.conch.ssh.filetransfer import FX_PERMISSION_DENIED, FX_FAILURE
 
14
from twisted.conch.ssh.filetransfer import FX_NO_SUCH_FILE, FX_OP_UNSUPPORTED
 
15
from twisted.conch.ssh.filetransfer import FX_NOT_A_DIRECTORY
 
16
from twisted.conch.ssh.filetransfer import FX_FILE_IS_A_DIRECTORY
 
17
from twisted.conch.ssh.filetransfer import FX_FILE_ALREADY_EXISTS
 
18
from twisted.conch.ssh import session
 
19
from twisted.conch.ls import lsLine
 
20
from twisted.internet import defer
 
21
 
 
22
from twisted.vfs import ivfs, pathutils
 
23
 
 
24
def translateErrors(function):
 
25
    """Decorator that catches VFSErrors and re-raises them as the corresponding
 
26
    SFTPErrors."""
 
27
    
 
28
    def f(*args, **kwargs):
 
29
        try:
 
30
            result = function(*args, **kwargs)
 
31
            if isinstance(result, defer.Deferred):
 
32
                result.addErrback(_ebtranslateErrors)
 
33
            return result
 
34
        except ivfs.PermissionError, e:
 
35
            raise SFTPError(FX_PERMISSION_DENIED, str(e))
 
36
        except ivfs.NotFoundError, e:
 
37
            raise SFTPError(FX_NO_SUCH_FILE, e.args[0])
 
38
        except ivfs.AlreadyExistsError, e:
 
39
            raise SFTPError(FX_FILE_ALREADY_EXISTS, e.args[0])
 
40
        except ivfs.VFSError, e:
 
41
            raise SFTPError(FX_FAILURE, str(e))
 
42
        except NotImplementedError, e:
 
43
            raise SFTPError(FX_OP_UNSUPPORTED, str(e))
 
44
 
 
45
    util.mergeFunctionMetadata(function, f)
 
46
    return f
 
47
 
 
48
 
 
49
def _ebtranslateErrors(failure):
 
50
    """This just re-raises the failure so that the translateErrors decorator
 
51
    around this errback can intercept it if it wants to."""
 
52
    failure.raiseException()
 
53
_ebtranslateErrors = translateErrors(_ebtranslateErrors)
 
54
 
 
55
 
 
56
class AdaptFileSystemUserToISFTP:
 
57
 
 
58
    zope.interface.implements(ISFTPServer)
 
59
 
 
60
    def __init__(self, avatar):
 
61
        self.avatar = avatar
 
62
        self.openFiles = {}
 
63
        self.openDirs = {}
 
64
        self.filesystem = avatar.filesystem
 
65
 
 
66
    def _setAttrs(self, path, attrs):
 
67
        """
 
68
        NOTE: this function assumes it runs as the logged-in user:
 
69
        i.e. under _runAsUser()
 
70
        """
 
71
        if attrs.has_key("uid") and attrs.has_key("gid"):
 
72
            os.lchown(path, attrs["uid"], attrs["gid"])
 
73
        if attrs.has_key("permissions"):
 
74
            os.chmod(path, attrs["permissions"])
 
75
        if attrs.has_key("atime") and attrs.has_key("mtime"):
 
76
            os.utime(path, (attrs["atime"]. attrs["mtime"]))
 
77
 
 
78
    def gotVersion(self, otherVersion, extData):
 
79
        return {}
 
80
 
 
81
    def openFile(self, filename, flags, attrs):
 
82
        createPlease = False
 
83
        exclusive = False
 
84
        openFlags = 0
 
85
        if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0:
 
86
            openFlags = os.O_RDONLY
 
87
        if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0:
 
88
            createPlease = True
 
89
            openFlags = os.O_WRONLY
 
90
        if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ:
 
91
            createPlease = True
 
92
            openFlags = os.O_RDWR
 
93
        if flags & FXF_APPEND == FXF_APPEND:
 
94
            createPlease = True
 
95
            openFlags |= os.O_APPEND
 
96
        if flags & FXF_CREAT == FXF_CREAT:
 
97
            createPlease = True
 
98
            openFlags |= os.O_CREAT
 
99
        if flags & FXF_TRUNC == FXF_TRUNC:
 
100
            openFlags |= os.O_TRUNC
 
101
        if flags & FXF_EXCL == FXF_EXCL:
 
102
            exclusive = True
 
103
 
 
104
        # XXX Once we change readChunk/writeChunk we'll have to wrap
 
105
        # child in something that implements those.
 
106
 
 
107
        pathSegments = self.filesystem.splitPath(filename)
 
108
        dirname, basename = pathSegments[:-1], pathSegments[-1]
 
109
        parentNode = self.filesystem.fetch('/'.join(dirname))
 
110
        if createPlease:
 
111
            child = parentNode.createFile(basename, exclusive)
 
112
        elif parentNode.exists(basename):
 
113
            child = parentNode.child(basename)
 
114
            if not ivfs.IFileSystemLeaf.providedBy(child):
 
115
                raise SFTPError(FX_FILE_IS_A_DIRECTORY, filename)
 
116
        else:
 
117
            raise SFTPError(FX_NO_SUCH_FILE, filename)
 
118
        child.open(openFlags)
 
119
        return AdaptFileSystemLeafToISFTPFile(child)
 
120
    openFile = translateErrors(openFile)
 
121
 
 
122
    def removeFile(self, filename):
 
123
        node = self.filesystem.fetch(filename)
 
124
        if not ivfs.IFileSystemLeaf.providedBy(node):
 
125
            raise SFTPError(FX_FILE_IS_A_DIRECTORY, filename)
 
126
        node.remove()
 
127
    removeFile = translateErrors(removeFile)
 
128
 
 
129
    def renameFile(self, oldpath, newpath):
 
130
        try:
 
131
            targetNode = self.filesystem.fetch(newpath)
 
132
        except (ivfs.NotFoundError, KeyError):
 
133
            # Something with the new name already exists.
 
134
            pass
 
135
        else:
 
136
            if ivfs.IFileSystemContainer(targetNode, None):
 
137
                # The target node is a container.  We assume the caller means to
 
138
                # move the source node into the container rather than replace
 
139
                # it, and adjust newpath accordingly.
 
140
                newpath = self.filesystem.joinPath(
 
141
                    newpath, self.filesystem.basename(oldpath))
 
142
        old = self.filesystem.fetch(oldpath)
 
143
        old.rename(newpath)
 
144
    renameFile = translateErrors(renameFile)
 
145
 
 
146
    def makeDirectory(self, path, attrs):
 
147
        dirname  = self.filesystem.dirname(path)
 
148
        basename = self.filesystem.basename(path)
 
149
        return self.filesystem.fetch(dirname).createDirectory(basename)
 
150
    makeDirectory = translateErrors(makeDirectory)
 
151
 
 
152
    def removeDirectory(self, path):
 
153
        self.filesystem.fetch(path).remove()
 
154
    removeDirectory = translateErrors(removeDirectory)
 
155
 
 
156
    def openDirectory(self, path):
 
157
        directory = self.filesystem.fetch(path)
 
158
        if not ivfs.IFileSystemContainer.providedBy(directory):
 
159
            raise SFTPError(FX_NOT_A_DIRECTORY, path)
 
160
        class DirList:
 
161
            def __init__(self, iter):
 
162
                self.iter = iter
 
163
            def __iter__(self):
 
164
                return self
 
165
 
 
166
            def next(self):
 
167
 
 
168
                (name, attrs) = self.iter.next()
 
169
 
 
170
                class st:
 
171
                    pass
 
172
 
 
173
                s = st()
 
174
                s.st_mode   = attrs["permissions"]
 
175
                s.st_uid    = attrs["uid"]
 
176
                s.st_gid    = attrs["gid"]
 
177
                s.st_size   = attrs["size"]
 
178
                s.st_mtime  = attrs["mtime"]
 
179
                s.st_nlink  = attrs["nlink"]
 
180
                return ( name, lsLine(name, s), attrs )
 
181
 
 
182
            def close(self):
 
183
                return
 
184
 
 
185
        return DirList(
 
186
            iter([(name, _attrify(file))
 
187
                  for (name, file) in self.filesystem.fetch(path).children()]))
 
188
    openDirectory = translateErrors(openDirectory)
 
189
 
 
190
    def getAttrs(self, path, followLinks):
 
191
        node = self.filesystem.fetch(path)
 
192
        return _attrify(node)
 
193
    getAttrs = translateErrors(getAttrs)
 
194
 
 
195
    def setAttrs(self, path, attrs):
 
196
        node = self.filesystem.fetch(path)
 
197
        try:
 
198
            # XXX: setMetadata isn't yet part of the IFileSystemNode interface
 
199
            # (but it should be).  So we catch AttributeError, and translate it
 
200
            # to NotImplementedError because it's slightly nicer for clients.
 
201
            node.setMetadata(attrs)
 
202
        except AttributeError:
 
203
            raise NotImplementedError("NO SETATTR")
 
204
    setAttrs = translateErrors(setAttrs)
 
205
 
 
206
    def readLink(self, path):
 
207
        raise NotImplementedError("NO LINK")
 
208
 
 
209
    def makeLink(self, linkPath, targetPath):
 
210
        raise NotImplementedError("NO LINK")
 
211
 
 
212
    def realPath(self, path):
 
213
        return self.filesystem.absPath(path)
 
214
 
 
215
 
 
216
class AdaptFileSystemLeafToISFTPFile:
 
217
    zope.interface.implements(ISFTPFile)
 
218
 
 
219
    def __init__(self, original):
 
220
        self.original = original
 
221
 
 
222
    def close(self):
 
223
        return self.original.close()
 
224
 
 
225
    def readChunk(self, offset, length):
 
226
        return self.original.readChunk(offset, length)
 
227
 
 
228
    def writeChunk(self, offset, data):
 
229
        return self.original.writeChunk(offset, data)
 
230
 
 
231
    def getAttrs(self):
 
232
        return _attrify(self.original)
 
233
        
 
234
    def setAttrs(self, attrs):
 
235
        try:
 
236
            # XXX: setMetadata isn't yet part of the IFileSystemNode interface
 
237
            # (but it should be).  So we catch AttributeError, and translate it
 
238
            # to NotImplementedError because it's slightly nicer for clients.
 
239
            self.original.setMetadata(attrs)
 
240
        except AttributeError:
 
241
            raise NotImplementedError("NO SETATTR")
 
242
 
 
243
 
 
244
class VFSConchSession:
 
245
    zope.interface.implements(ISession)
 
246
    def __init__(self, avatar):
 
247
        self.avatar = avatar
 
248
    def openShell(self, proto):
 
249
        self.avatar.conn.transport.transport.loseConnection()
 
250
    def getPty(self, term, windowSize, modes):
 
251
        pass
 
252
    def closed(self):
 
253
        log.msg('shell closed')
 
254
 
 
255
 
 
256
class VFSConchUser(ConchUser):
 
257
    def __init__(self, username, root):
 
258
        ConchUser.__init__(self)
 
259
        self.username = username
 
260
        self.filesystem = pathutils.FileSystem(root)
 
261
 
 
262
        self.listeners = {}  # dict mapping (interface, port) -> listener
 
263
        self.channelLookup.update(
 
264
                {"session": session.SSHSession})
 
265
        self.subsystemLookup.update(
 
266
                {"sftp": FileTransferServer})
 
267
 
 
268
    def logout(self):
 
269
        # XXX - this may be broken
 
270
        log.msg('avatar %s logging out (%i)' % (self.username, len(self.listeners)))
 
271
 
 
272
 
 
273
def _attrify(node):
 
274
    meta = node.getMetadata()
 
275
    permissions = meta.get('permissions', None)
 
276
    if permissions is None:
 
277
        if ivfs.IFileSystemContainer.providedBy(node):
 
278
            permissions = 16877
 
279
        else:
 
280
            permissions = 33188
 
281
 
 
282
    return {'permissions': permissions,
 
283
            'size': meta.get('size', 0),
 
284
            'uid': meta.get('uid', 0),
 
285
            'gid': meta.get('gid', 0),
 
286
            'atime': meta.get('atime', time.time()),
 
287
            'mtime': meta.get('mtime', time.time()),
 
288
            'nlink': meta.get('nlink', 1)
 
289
            }
 
290
 
 
291
 
 
292
 
 
293
components.registerAdapter(AdaptFileSystemUserToISFTP, VFSConchUser, ISFTPServer)
 
294
components.registerAdapter(VFSConchSession, VFSConchUser, ISession)
 
295