Ticket #941: sftp-time-diff.txt

File sftp-time-diff.txt, 7.9 KB (added by davidsarah, at 2010-02-08T05:33:44Z)

Initial attempt at a fix (no tests, not ready to apply)

Line 
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):