~launchpad-pqm/launchpad/devel

10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
"""Twisted SFTP implementation of the Poppy upload server."""
5
6
__metaclass__ = type
7
__all__ = [
8
    'SFTPFile',
9
    'SFTPServer',
10
    ]
11
10935.2.2 by Steve Kowalik
* Fix the FileIsADirectory code to actually work.
12
import errno
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
13
import logging
14
import os
15
import tempfile
16
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
17
from twisted.conch.interfaces import (
18
    ISFTPFile,
19
    ISFTPServer,
20
    )
21
from zope.component import (
22
    adapter,
23
    provideHandler,
24
    )
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
25
from zope.interface import implements
26
27
from lp.poppy.filesystem import UploadFileSystem
28
from lp.poppy.hooks import Hooks
29
from lp.services.sshserver.events import SFTPClosed
11086.2.1 by James Westby
Move FileIsADirectory from codehosting.sftp to services.sshserver.
30
from lp.services.sshserver.sftp import FileIsADirectory
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
31
32
33
class SFTPServer:
34
    """An implementation of `ISFTPServer` that backs onto a Poppy filesystem.
35
    """
36
37
    implements(ISFTPServer)
38
39
    def __init__(self, avatar, fsroot):
40
        provideHandler(self.connectionClosed)
41
        self._avatar = avatar
42
        self._fs_root = fsroot
43
        self.uploadfilesystem = UploadFileSystem(tempfile.mkdtemp())
44
        self._current_upload = self.uploadfilesystem.rootpath
10935.2.1 by Steve Kowalik
* Export FileIsADirectory from codehosting.sftp, so I can use it for poppy-sftp.
45
        os.chmod(self._current_upload, 0770)
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
46
        self._log = logging.getLogger("poppy-sftp")
10935.2.1 by Steve Kowalik
* Export FileIsADirectory from codehosting.sftp, so I can use it for poppy-sftp.
47
        self.hook = Hooks(
48
            self._fs_root, self._log, "ubuntu", perms='g+rws', prefix='-sftp')
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
49
        self.hook.new_client_hook(self._current_upload, 0, 0)
50
        self.hook.auth_verify_hook(self._current_upload, None, None)
51
52
    def gotVersion(self, other_version, ext_data):
53
        return {}
54
55
    def openFile(self, filename, flags, attrs):
56
        self._create_missing_directories(filename)
57
        absfile = self._translate_path(filename)
58
        return SFTPFile(absfile)
59
60
    def removeFile(self, filename):
61
        pass
62
63
    def renameFile(self, old_path, new_path):
64
        abs_old = self._translate_path(old_path)
65
        abs_new = self._translate_path(new_path)
66
        os.rename(abs_old, abs_new)
67
68
    def makeDirectory(self, path, attrs):
69
        # XXX: We ignore attrs here
70
        self.uploadfilesystem.mkdir(path)
71
72
    def removeDirectory(self, path):
73
        self.uploadfilesystem.rmdir(path)
74
75
    def openDirectory(self, path):
76
        pass
77
78
    def getAttrs(self, path, follow_links):
79
        pass
80
81
    def setAttrs(self, path, attrs):
82
        pass
83
84
    def readLink(self, path):
85
        pass
86
87
    def makeLink(self, link_path, target_path):
88
        pass
89
90
    def realPath(self, path):
91
        return path
92
93
    def extendedRequest(self, extended_name, extended_data):
94
        pass
95
96
    def _create_missing_directories(self, filename):
97
        new_dir, new_file = os.path.split(
98
            self.uploadfilesystem._sanitize(filename))
99
        if new_dir != '':
100
            if not os.path.exists(
101
                os.path.join(self._current_upload, new_dir)):
102
                self.uploadfilesystem.mkdir(new_dir)
103
104
    def _translate_path(self, filename):
105
        return self.uploadfilesystem._full(
106
            self.uploadfilesystem._sanitize(filename))
107
108
    @adapter(SFTPClosed)
109
    def connectionClosed(self, event):
110
        if event.avatar is not self._avatar:
111
            return
112
        self.hook.client_done_hook(self._current_upload, 0, 0)
113
10935.2.1 by Steve Kowalik
* Export FileIsADirectory from codehosting.sftp, so I can use it for poppy-sftp.
114
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
115
class SFTPFile:
116
117
    implements(ISFTPFile)
118
119
    def __init__(self, filename):
120
        self.filename = filename
121
122
    def close(self):
123
        pass
124
125
    def readChunk(self, offset, length):
126
        pass
127
128
    def writeChunk(self, offset, data):
129
        try:
130
            chunk_file = os.open(
12392.7.23 by Julian Edwards
Fix permissions for sftp-written files
131
                self.filename, os.O_CREAT | os.O_WRONLY, 0664)
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
132
        except OSError, e:
133
            if e.errno != errno.EISDIR:
134
                raise
10935.2.2 by Steve Kowalik
* Fix the FileIsADirectory code to actually work.
135
            raise FileIsADirectory(self.filename)
10918.2.3 by Steve Kowalik
Merge in the code for poppy-sftp
136
        os.lseek(chunk_file, offset, 0)
137
        os.write(chunk_file, data)
138
        os.close(chunk_file)
139
140
    def getAttrs(self):
141
        pass
142
143
    def setAttrs(self, attr):
144
        pass