1 | --- old-tahoe/src/allmydata/frontends/sftpd.py 2010-02-08 05:27:41.176049400 +0000 |
---|
2 | +++ new-tahoe/src/allmydata/frontends/sftpd.py 2010-02-08 05:27:41.303049400 +0000 |
---|
3 | @@ -91,8 +91,20 @@ |
---|
4 | def close(self): |
---|
5 | pass |
---|
6 | |
---|
7 | -class FakeStat: |
---|
8 | - pass |
---|
9 | +class _FakeStat: |
---|
10 | + def __init__(self, attrs): |
---|
11 | + # This is only used by twisted.conch.ls.lsLine, which doesn't require |
---|
12 | + # st_uid, st_gid or st_size to be numeric. |
---|
13 | + self.st_uid = "tahoe" |
---|
14 | + self.st_gid = "tahoe" |
---|
15 | + self.st_mtime = attrs.get("mtime", 0) |
---|
16 | + self.st_mode = attrs["permissions"] |
---|
17 | + # TODO: check that clients are okay with this being a "?". |
---|
18 | + # (They should be because the longname is intended for human |
---|
19 | + # consumption.) |
---|
20 | + self.st_size = attrs.get("size", "?") |
---|
21 | + # We don't know how many links there really are to this object. |
---|
22 | + self.st_nlink = 1 |
---|
23 | |
---|
24 | class BadRemoveRequest(Exception): |
---|
25 | pass |
---|
26 | @@ -160,15 +172,25 @@ |
---|
27 | return d |
---|
28 | |
---|
29 | if flags & FXF_WRITE: |
---|
30 | + # FIXME: this is too restrictive. If the file does not already |
---|
31 | + # exist, then FXF_CREAT and FXF_TRUNC should not be needed. |
---|
32 | + # This might cause us to fail to interoperate with clients other |
---|
33 | + # than /usr/bin/sftp. |
---|
34 | if not (flags & FXF_CREAT) or not (flags & FXF_TRUNC): |
---|
35 | raise NotImplementedError |
---|
36 | if not path: |
---|
37 | - raise PermissionError("cannot STOR to root directory") |
---|
38 | + raise PermissionError("cannot create file in root directory") |
---|
39 | + |
---|
40 | + # We are also incorrectly ignoring FXF_EXCL when the file does |
---|
41 | + # already exist (we should fail in that case, although it's |
---|
42 | + # impossible to implement that completely reliably if there |
---|
43 | + # are uncoordinated writes). |
---|
44 | + |
---|
45 | childname = path[-1] |
---|
46 | d = self._get_root(path) |
---|
47 | def _got_root((root, path)): |
---|
48 | if not path: |
---|
49 | - raise PermissionError("cannot STOR to root directory") |
---|
50 | + raise PermissionError("cannot create file in root directory") |
---|
51 | return root.get_child_at_path(path[:-1]) |
---|
52 | d.addCallback(_got_root) |
---|
53 | def _got_parent(parent): |
---|
54 | @@ -201,7 +223,8 @@ |
---|
55 | |
---|
56 | def makeDirectory(self, path, attrs): |
---|
57 | print "MAKEDIRECTORY", path, attrs |
---|
58 | - # TODO: extract attrs["mtime"], use it to set the parent metadata. |
---|
59 | + # TODO: extract attrs["mtime"] and attrs["createtime"], use them |
---|
60 | + # to set the parent metadata. |
---|
61 | # Maybe also copy attrs["ext_*"] . |
---|
62 | path = self._convert_sftp_path(path) |
---|
63 | d = self._get_root(path) |
---|
64 | @@ -212,7 +235,7 @@ |
---|
65 | def _get_or_create_directories(self, node, path): |
---|
66 | if not IDirectoryNode.providedBy(node): |
---|
67 | # unfortunately it is too late to provide the name of the |
---|
68 | - # blocking directory in the error message. |
---|
69 | + # blocking file in the error message. |
---|
70 | raise ExistingChildError("cannot create directory because there " |
---|
71 | "is a file in the way") # close enough |
---|
72 | if not path: |
---|
73 | @@ -259,30 +282,31 @@ |
---|
74 | def _render(children): |
---|
75 | results = [] |
---|
76 | for filename, (node, metadata) in children.iteritems(): |
---|
77 | - s = FakeStat() |
---|
78 | - if IDirectoryNode.providedBy(node): |
---|
79 | - s.st_mode = 040700 |
---|
80 | - s.st_size = 0 |
---|
81 | - else: |
---|
82 | - s.st_mode = 0100600 |
---|
83 | - s.st_size = node.get_size() |
---|
84 | - s.st_nlink = 1 |
---|
85 | - s.st_uid = 0 |
---|
86 | - s.st_gid = 0 |
---|
87 | - s.st_mtime = int(metadata.get("mtime", 0)) |
---|
88 | - longname = ls.lsLine(filename.encode("utf-8"), s) |
---|
89 | + # The file size may be cached or absent. |
---|
90 | attrs = self._populate_attrs(node, metadata) |
---|
91 | - results.append( (filename.encode("utf-8"), longname, attrs) ) |
---|
92 | + filename_utf8 = filename.encode("utf-8") |
---|
93 | + longname = ls.lsLine(filename_utf8, _FakeStat(attrs)) |
---|
94 | + results.append( (filename_utf8, longname, attrs) ) |
---|
95 | return StoppableList(results) |
---|
96 | d.addCallback(_render) |
---|
97 | return d |
---|
98 | |
---|
99 | def getAttrs(self, path, followLinks): |
---|
100 | print "GETATTRS", path, followLinks |
---|
101 | - # from ftp.stat |
---|
102 | d = self._get_node_and_metadata_for_path(self._convert_sftp_path(path)) |
---|
103 | - def _render((node,metadata)): |
---|
104 | - return self._populate_attrs(node, metadata) |
---|
105 | + def _render((node, metadata)): |
---|
106 | + # When asked about a specific file, report its current size. |
---|
107 | + # TODO: the modification time for a mutable file should be |
---|
108 | + # reported as the update time of the best version. But that |
---|
109 | + # information isn't currently stored in mutable shares, I think. |
---|
110 | + d2 = node.get_current_size() |
---|
111 | + def _got_size(size): |
---|
112 | + attrs = self._populate_attrs(node, metadata) |
---|
113 | + if size is not None: |
---|
114 | + attrs["size"] = size |
---|
115 | + return attrs |
---|
116 | + d2.addCallback(_got_size) |
---|
117 | + return d2 |
---|
118 | d.addCallback(_render) |
---|
119 | d.addErrback(self._convert_error) |
---|
120 | def _done(res): |
---|
121 | @@ -327,19 +351,53 @@ |
---|
122 | |
---|
123 | def _populate_attrs(self, childnode, metadata): |
---|
124 | attrs = {} |
---|
125 | - attrs["uid"] = 1000 |
---|
126 | - attrs["gid"] = 1000 |
---|
127 | - attrs["atime"] = 0 |
---|
128 | - attrs["mtime"] = int(metadata.get("mtime", 0)) |
---|
129 | - isdir = bool(IDirectoryNode.providedBy(childnode)) |
---|
130 | - if isdir: |
---|
131 | - attrs["size"] = 1 |
---|
132 | - # the permissions must have the extra bits (040000 or 0100000), |
---|
133 | - # otherwise the client will not call openDirectory |
---|
134 | - attrs["permissions"] = 040700 # S_IFDIR |
---|
135 | + |
---|
136 | + # see webapi.txt for what these times mean |
---|
137 | + if "tahoe" in metadata and "linkmotime" in metadata["tahoe"]: |
---|
138 | + attrs["mtime"] = int(metadata["tahoe"]["linkmotime"]) |
---|
139 | + elif "mtime" in metadata: |
---|
140 | + attrs["mtime"] = int(metadata["mtime"]) |
---|
141 | + |
---|
142 | + if "tahoe" in metadata and "linkcrtime" in metadata["tahoe"]: |
---|
143 | + attrs["createtime"] = int(metadata["tahoe"]["linkcrtime"]) |
---|
144 | + |
---|
145 | + if "ctime" in metadata: |
---|
146 | + attrs["ctime"] = int(metadata["ctime"]) |
---|
147 | + |
---|
148 | + # We would prefer to omit atime, but SFTP version 3 can only |
---|
149 | + # accept mtime if atime is also set. |
---|
150 | + attrs["atime"] = attrs["mtime"] |
---|
151 | + |
---|
152 | + # The permissions must have the extra bits (040000 or 0100000), |
---|
153 | + # otherwise the client will not call openDirectory. |
---|
154 | + # Directories have no size, and SFTP doesn't require us to make |
---|
155 | + # one up. For files and unknown nodes, omit the size if we don't |
---|
156 | + # immediately know it. |
---|
157 | + |
---|
158 | + if childnode.is_unknown(): |
---|
159 | + perms = 0 |
---|
160 | + elif IDirectoryNode.providedBy(childnode): |
---|
161 | + perms = 040777 # S_IFDIR |
---|
162 | else: |
---|
163 | - attrs["size"] = childnode.get_size() |
---|
164 | - attrs["permissions"] = 0100600 # S_IFREG |
---|
165 | + size = childnode.get_size() |
---|
166 | + if size is not None: |
---|
167 | + assert isinstance(size, (int, long)), repr(size) |
---|
168 | + attrs["size"] = size |
---|
169 | + perms = 0100666 # S_IFREG |
---|
170 | + |
---|
171 | + if not childnode.is_unknown() and childnode.is_readonly(): |
---|
172 | + perms &= 0140555 |
---|
173 | + |
---|
174 | + # We could set the SSH_FILEXFER_ATTR_FLAGS here: |
---|
175 | + # ENCRYPTED would always be true ("The file is stored on disk |
---|
176 | + # using file-system level transparent encryption.") |
---|
177 | + # SYSTEM, HIDDEN, ARCHIVE and SYNC would always be false. |
---|
178 | + # READONLY and IMMUTABLE would be set according to |
---|
179 | + # is_readonly() and is_immutable(). |
---|
180 | + # However, twisted.conch.ssh.filetransfer only implements |
---|
181 | + # SFTP version 3, which doesn't include these flags. |
---|
182 | + |
---|
183 | + attrs["permissions"] = perms |
---|
184 | return attrs |
---|
185 | |
---|
186 | def _convert_error(self, f): |
---|