--- old-tahoe/src/allmydata/frontends/sftpd.py	2010-02-08 05:27:41.176049400 +0000
+++ new-tahoe/src/allmydata/frontends/sftpd.py	2010-02-08 05:27:41.303049400 +0000
@@ -91,8 +91,20 @@
     def close(self):
         pass
 
-class FakeStat:
-    pass
+class _FakeStat:
+    def __init__(self, attrs):
+        # This is only used by twisted.conch.ls.lsLine, which doesn't require
+        # st_uid, st_gid or st_size to be numeric.
+        self.st_uid = "tahoe"
+        self.st_gid = "tahoe"
+        self.st_mtime = attrs.get("mtime", 0)
+        self.st_mode = attrs["permissions"]
+        # TODO: check that clients are okay with this being a "?".
+        # (They should be because the longname is intended for human
+        # consumption.)
+        self.st_size = attrs.get("size", "?")
+        # We don't know how many links there really are to this object.
+        self.st_nlink = 1
 
 class BadRemoveRequest(Exception):
     pass
@@ -160,15 +172,25 @@
             return d
 
         if flags & FXF_WRITE:
+            # FIXME: this is too restrictive. If the file does not already
+            # exist, then FXF_CREAT and FXF_TRUNC should not be needed.
+            # This might cause us to fail to interoperate with clients other
+            # than /usr/bin/sftp.
             if not (flags & FXF_CREAT) or not (flags & FXF_TRUNC):
                 raise NotImplementedError
             if not path:
-                raise PermissionError("cannot STOR to root directory")
+                raise PermissionError("cannot create file in root directory")
+
+            # We are also incorrectly ignoring FXF_EXCL when the file does
+            # already exist (we should fail in that case, although it's
+            # impossible to implement that completely reliably if there
+            # are uncoordinated writes).
+
             childname = path[-1]
             d = self._get_root(path)
             def _got_root((root, path)):
                 if not path:
-                    raise PermissionError("cannot STOR to root directory")
+                    raise PermissionError("cannot create file in root directory")
                 return root.get_child_at_path(path[:-1])
             d.addCallback(_got_root)
             def _got_parent(parent):
@@ -201,7 +223,8 @@
 
     def makeDirectory(self, path, attrs):
         print "MAKEDIRECTORY", path, attrs
-        # TODO: extract attrs["mtime"], use it to set the parent metadata.
+        # TODO: extract attrs["mtime"] and attrs["createtime"], use them
+        # to set the parent metadata.
         # Maybe also copy attrs["ext_*"] .
         path = self._convert_sftp_path(path)
         d = self._get_root(path)
@@ -212,7 +235,7 @@
     def _get_or_create_directories(self, node, path):
         if not IDirectoryNode.providedBy(node):
             # unfortunately it is too late to provide the name of the
-            # blocking directory in the error message.
+            # blocking file in the error message.
             raise ExistingChildError("cannot create directory because there "
                                      "is a file in the way") # close enough
         if not path:
@@ -259,30 +282,31 @@
         def _render(children):
             results = []
             for filename, (node, metadata) in children.iteritems():
-                s = FakeStat()
-                if IDirectoryNode.providedBy(node):
-                    s.st_mode = 040700
-                    s.st_size = 0
-                else:
-                    s.st_mode = 0100600
-                    s.st_size = node.get_size()
-                s.st_nlink = 1
-                s.st_uid = 0
-                s.st_gid = 0
-                s.st_mtime = int(metadata.get("mtime", 0))
-                longname = ls.lsLine(filename.encode("utf-8"), s)
+                # The file size may be cached or absent.
                 attrs = self._populate_attrs(node, metadata)
-                results.append( (filename.encode("utf-8"), longname, attrs) )
+                filename_utf8 = filename.encode("utf-8")
+                longname = ls.lsLine(filename_utf8, _FakeStat(attrs))
+                results.append( (filename_utf8, longname, attrs) )
             return StoppableList(results)
         d.addCallback(_render)
         return d
 
     def getAttrs(self, path, followLinks):
         print "GETATTRS", path, followLinks
-        # from ftp.stat
         d = self._get_node_and_metadata_for_path(self._convert_sftp_path(path))
-        def _render((node,metadata)):
-            return self._populate_attrs(node, metadata)
+        def _render((node, metadata)):
+            # When asked about a specific file, report its current size.
+            # TODO: the modification time for a mutable file should be
+            # reported as the update time of the best version. But that
+            # information isn't currently stored in mutable shares, I think.
+            d2 = node.get_current_size()
+            def _got_size(size):
+                attrs = self._populate_attrs(node, metadata)
+                if size is not None:
+                    attrs["size"] = size
+                return attrs
+            d2.addCallback(_got_size)
+            return d2
         d.addCallback(_render)
         d.addErrback(self._convert_error)
         def _done(res):
@@ -327,19 +351,53 @@
 
     def _populate_attrs(self, childnode, metadata):
         attrs = {}
-        attrs["uid"] = 1000
-        attrs["gid"] = 1000
-        attrs["atime"] = 0
-        attrs["mtime"] = int(metadata.get("mtime", 0))
-        isdir = bool(IDirectoryNode.providedBy(childnode))
-        if isdir:
-            attrs["size"] = 1
-            # the permissions must have the extra bits (040000 or 0100000),
-            # otherwise the client will not call openDirectory
-            attrs["permissions"] = 040700 # S_IFDIR
+
+        # see webapi.txt for what these times mean
+        if "tahoe" in metadata and "linkmotime" in metadata["tahoe"]:
+            attrs["mtime"] = int(metadata["tahoe"]["linkmotime"])
+        elif "mtime" in metadata:
+            attrs["mtime"] = int(metadata["mtime"])
+
+        if "tahoe" in metadata and "linkcrtime" in metadata["tahoe"]:
+            attrs["createtime"] = int(metadata["tahoe"]["linkcrtime"])
+
+        if "ctime" in metadata:
+            attrs["ctime"] = int(metadata["ctime"])
+
+        # We would prefer to omit atime, but SFTP version 3 can only
+        # accept mtime if atime is also set.
+        attrs["atime"] = attrs["mtime"]
+
+        # The permissions must have the extra bits (040000 or 0100000),
+        # otherwise the client will not call openDirectory.
+        # Directories have no size, and SFTP doesn't require us to make
+        # one up. For files and unknown nodes, omit the size if we don't
+        # immediately know it.
+
+        if childnode.is_unknown():
+            perms = 0
+        elif IDirectoryNode.providedBy(childnode):
+            perms = 040777  # S_IFDIR
         else:
-            attrs["size"] = childnode.get_size()
-            attrs["permissions"] = 0100600 # S_IFREG
+            size = childnode.get_size()
+            if size is not None:
+                assert isinstance(size, (int, long)), repr(size)
+                attrs["size"] = size
+            perms = 0100666  # S_IFREG
+
+        if not childnode.is_unknown() and childnode.is_readonly():
+            perms &= 0140555
+
+        # We could set the SSH_FILEXFER_ATTR_FLAGS here:
+        # ENCRYPTED would always be true ("The file is stored on disk
+        # using file-system level transparent encryption.")
+        # SYSTEM, HIDDEN, ARCHIVE and SYNC would always be false.
+        # READONLY and IMMUTABLE would be set according to
+        # is_readonly() and is_immutable().
+        # However, twisted.conch.ssh.filetransfer only implements
+        # SFTP version 3, which doesn't include these flags.
+
+        attrs["permissions"] = perms
         return attrs
 
     def _convert_error(self, f):
