You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2021/07/15 20:26:42 UTC

[commons-vfs] branch master updated (8392df2 -> 12a2182)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git.


    from 8392df2  Remove unused imports.
     new f0fa4db  Sort members.
     new d6b7eff  Synchronized methods that read or write the thread-unsafe instance variable of type SftpATTRS.
     new 12a2182  Synchronized methods that read or write the thread-unsafe instance variable of type SftpATTRS.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../commons/vfs2/provider/sftp/SftpFileObject.java | 524 ++++++++++-----------
 src/changes/changes.xml                            |   3 +
 2 files changed, 264 insertions(+), 263 deletions(-)

[commons-vfs] 03/03: Synchronized methods that read or write the thread-unsafe instance variable of type SftpATTRS.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit 12a218242f51b6cf531eb5cc96ba43d52277ed72
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Jul 15 16:24:46 2021 -0400

    Synchronized methods that read or write the thread-unsafe instance
    variable of type SftpATTRS.
---
 .../java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java     | 2 --
 src/changes/changes.xml                                                | 3 +++
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
index 7cec9ad..45fe984 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
@@ -99,8 +99,6 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
 
     private final String relPath;
 
-    private boolean inRefresh;
-
     protected SftpFileObject(final AbstractFileName name, final SftpFileSystem fileSystem) throws FileSystemException {
         super(name, fileSystem);
         relPath = UriParser.decode(fileSystem.getRootName().getRelativeName(name));
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index a3172bc..bac9f96 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -111,6 +111,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="fix" issue="VFS-805" dev="ggregory" due-to="Claus Stadler, Gary Gregory">
         HTTP seek always exhausts response #186.
       </action>
+      <action type="fix" issue="VFS-805" dev="ggregory" due-to="Max Kellermann, Mark Thomas, Bernd, Gary Gregory">
+        SftpFileObject: Add synchronized to methods that read or write the thread-unsafe instance variable of type com.jcraft.jsch.SftpATTRS.
+      </action>
       <!-- UPDATES  -->
       <action type="update" dev="ggregory" due-to="Gary Gregory">
         Bump Log4j 2.14.0 -> 2.14.1.

[commons-vfs] 02/03: Synchronized methods that read or write the thread-unsafe instance variable of type SftpATTRS.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit d6b7eff01c6aff3adf4e62af8c6149b2eb4ec22f
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Jul 15 16:15:48 2021 -0400

    Synchronized methods that read or write the thread-unsafe instance
    variable of type SftpATTRS.
---
 .../commons/vfs2/provider/sftp/SftpFileObject.java | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
index 43c17be..7cec9ad 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
@@ -138,7 +138,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
 
     /** @since 2.0 */
     @Override
-    protected void doDetach() throws Exception {
+    protected synchronized void doDetach() throws Exception {
         attrs = null;
     }
 
@@ -146,7 +146,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
      * Returns the size of the file content (in bytes).
      */
     @Override
-    protected long doGetContentSize() throws Exception {
+    protected synchronized long doGetContentSize() throws Exception {
         if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_SIZE) == 0) {
             throw new FileSystemException("vfs.provider.sftp/unknown-size.error");
         }
@@ -200,7 +200,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     }
 
     @Override
-    protected long doGetLastModifiedTime() throws Exception {
+    protected synchronized long doGetLastModifiedTime() throws Exception {
         if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_ACMODTIME) == 0) {
             throw new FileSystemException("vfs.provider.sftp/unknown-modtime.error");
         }
@@ -231,7 +231,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
      * Determines the type of this file, returns null if the file does not exist.
      */
     @Override
-    protected FileType doGetType() throws Exception {
+    protected synchronized FileType doGetType() throws Exception {
         if (attrs == null) {
             statSelf();
         }
@@ -366,7 +366,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     }
 
     @Override
-    protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
+    protected synchronized boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
         final PosixPermissions permissions = getPermissions(false);
         final int newPermissions = permissions.makeExecutable(executable, ownerOnly);
         if (newPermissions == permissions.getPermissions()) {
@@ -387,7 +387,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
      *            at the moment jsch send them with second precision.
      */
     @Override
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
+    protected synchronized boolean doSetLastModifiedTime(final long modtime) throws Exception {
         final int newMTime = (int) (modtime / MOD_TIME_FACTOR);
         attrs.setACMODTIME(attrs.getATime(), newMTime);
         flushStat();
@@ -409,7 +409,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     }
 
     @Override
-    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
+    protected synchronized boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
         final PosixPermissions permissions = getPermissions(false);
         final int newPermissions = permissions.makeWritable(writable, ownerOnly);
         if (newPermissions == permissions.getPermissions()) {
@@ -422,7 +422,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
         return true;
     }
 
-    private void flushStat() throws IOException, SftpException {
+    private synchronized void flushStat() throws IOException, SftpException {
         final ChannelSftp channel = getAbstractFileSystem().getChannel();
         try {
             channel.setStat(relPath, attrs);
@@ -455,7 +455,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
      * @throws Exception If an error occurs
      * @since 2.1
      */
-    protected PosixPermissions getPermissions(final boolean checkIds) throws Exception {
+    protected synchronized PosixPermissions getPermissions(final boolean checkIds) throws Exception {
         statSelf();
         boolean isInGroup = false;
         if (checkIds) {
@@ -488,7 +488,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     /**
      * Sets attrs from listChildrenResolved
      */
-    private void setStat(final SftpATTRS attrs) {
+    private synchronized void setStat(final SftpATTRS attrs) {
         this.attrs = attrs;
     }
 
@@ -497,7 +497,7 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
      *
      * @throws IOException if an error occurs.
      */
-    private void statSelf() throws IOException {
+    private synchronized void statSelf() throws IOException {
         ChannelSftp channelSftp = null;
         try {
             channelSftp = getAbstractFileSystem().getChannel();

[commons-vfs] 01/03: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit f0fa4dbc810acdecea55c785ab4c7d47c3fef150
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Jul 15 16:12:11 2021 -0400

    Sort members.
---
 .../commons/vfs2/provider/sftp/SftpFileObject.java | 528 ++++++++++-----------
 1 file changed, 264 insertions(+), 264 deletions(-)

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
index 2bc3489..43c17be 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java
@@ -49,98 +49,61 @@ import com.jcraft.jsch.SftpException;
  */
 public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
 
-    private static final long MOD_TIME_FACTOR = 1000L;
-
-    private SftpATTRS attrs;
-    private final String relPath;
-
-    private boolean inRefresh;
-
-    protected SftpFileObject(final AbstractFileName name, final SftpFileSystem fileSystem) throws FileSystemException {
-        super(name, fileSystem);
-        relPath = UriParser.decode(fileSystem.getRootName().getRelativeName(name));
-    }
-
-    /** @since 2.0 */
-    @Override
-    protected void doDetach() throws Exception {
-        attrs = null;
-    }
-
     /**
-     * Determines the type of this file, returns null if the file does not exist.
+     * An InputStream that monitors for end-of-file.
      */
-    @Override
-    protected FileType doGetType() throws Exception {
-        if (attrs == null) {
-            statSelf();
-        }
+    private class SftpInputStream extends MonitorInputStream {
+        private final ChannelSftp channel;
 
-        if (attrs == null) {
-            return FileType.IMAGINARY;
+        public SftpInputStream(final ChannelSftp channel, final InputStream in) {
+            super(in);
+            this.channel = channel;
         }
 
-        if ((attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS) == 0) {
-            throw new FileSystemException("vfs.provider.sftp/unknown-permissions.error");
+        public SftpInputStream(final ChannelSftp channel, final InputStream in, final int bufferSize) {
+            super(in, bufferSize);
+            this.channel = channel;
         }
-        if (attrs.isDir()) {
-            return FileType.FOLDER;
+
+        /**
+         * Called after the stream has been closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            getAbstractFileSystem().putChannel(channel);
         }
-        return FileType.FILE;
     }
 
     /**
-     * Called when the type or content of this file changes.
+     * An OutputStream that wraps an sftp OutputStream, and closes the channel when the stream is closed.
      */
-    @Override
-    protected void onChange() throws Exception {
-        statSelf();
-    }
+    private class SftpOutputStream extends MonitorOutputStream {
+        private final ChannelSftp channel;
 
-    /**
-     * Fetches file attributes from server.
-     *
-     * @throws IOException if an error occurs.
-     */
-    private void statSelf() throws IOException {
-        ChannelSftp channelSftp = null;
-        try {
-            channelSftp = getAbstractFileSystem().getChannel();
-            setStat(channelSftp.stat(relPath));
-        } catch (final SftpException e) {
-            try {
-                // maybe the channel has some problems, so recreate the channel and retry
-                if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) {
-                    channelSftp.disconnect();
-                    channelSftp = getAbstractFileSystem().getChannel();
-                    setStat(channelSftp.stat(relPath));
-                } else {
-                    // Really does not exist
-                    attrs = null;
-                }
-            } catch (final SftpException innerEx) {
-                // TODO - not strictly true, but jsch 0.1.2 does not give us
-                // enough info in the exception. Should be using:
-                // if ( e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE )
-                // However, sometimes the exception has the correct id, and
-                // sometimes
-                // it does not. Need to look into why.
+        public SftpOutputStream(final ChannelSftp channel, final OutputStream out) {
+            super(out);
+            this.channel = channel;
+        }
 
-                // Does not exist
-                attrs = null;
-            }
-        } finally {
-            if (channelSftp != null) {
-                getAbstractFileSystem().putChannel(channelSftp);
-            }
+        /**
+         * Called after this stream is closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            getAbstractFileSystem().putChannel(channel);
         }
     }
+    private static final long MOD_TIME_FACTOR = 1000L;
 
-    /**
-     * Sets attrs from listChildrenResolved
-     */
-    private void setStat(final SftpATTRS attrs) {
-        this.attrs = attrs;
+    private SftpATTRS attrs;
+
+    private final String relPath;
+
+    private boolean inRefresh;
+
+    protected SftpFileObject(final AbstractFileName name, final SftpFileSystem fileSystem) throws FileSystemException {
+        super(name, fileSystem);
+        relPath = UriParser.decode(fileSystem.getRootName().getRelativeName(name));
     }
 
     /**
@@ -156,38 +119,6 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
         }
     }
 
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_ACMODTIME) == 0) {
-            throw new FileSystemException("vfs.provider.sftp/unknown-modtime.error");
-        }
-        return attrs.getMTime() * MOD_TIME_FACTOR;
-    }
-
-    /**
-     * Sets the last modified time of this file. Is only called if {@link #doGetType} does not return
-     * {@link FileType#IMAGINARY}.
-     *
-     * @param modtime is modification time in milliseconds. SFTP protocol can send times with nanosecond precision but
-     *            at the moment jsch send them with second precision.
-     */
-    @Override
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
-        final int newMTime = (int) (modtime / MOD_TIME_FACTOR);
-        attrs.setACMODTIME(attrs.getATime(), newMTime);
-        flushStat();
-        return true;
-    }
-
-    private void flushStat() throws IOException, SftpException {
-        final ChannelSftp channel = getAbstractFileSystem().getChannel();
-        try {
-            channel.setStat(relPath, attrs);
-        } finally {
-            getAbstractFileSystem().putChannel(channel);
-        }
-    }
-
     /**
      * Deletes the file.
      */
@@ -205,86 +136,117 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
         }
     }
 
+    /** @since 2.0 */
+    @Override
+    protected void doDetach() throws Exception {
+        attrs = null;
+    }
+
     /**
-     * Renames the file.
+     * Returns the size of the file content (in bytes).
      */
     @Override
-    protected void doRename(final FileObject newFile) throws Exception {
-        final ChannelSftp channel = getAbstractFileSystem().getChannel();
-        try {
-            final SftpFileObject newSftpFileObject = (SftpFileObject) FileObjectUtils.getAbstractFileObject(newFile);
-            channel.rename(relPath, newSftpFileObject.relPath);
-        } finally {
-            getAbstractFileSystem().putChannel(channel);
+    protected long doGetContentSize() throws Exception {
+        if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_SIZE) == 0) {
+            throw new FileSystemException("vfs.provider.sftp/unknown-size.error");
         }
+        return attrs.getSize();
     }
 
     /**
-     * Returns the POSIX type permissions of the file.
-     *
-     * @param checkIds {@code true} if user and group ID should be checked (needed for some access rights checks)
-     * @return A PosixPermission object
-     * @throws Exception If an error occurs
-     * @since 2.1
+     * Creates an input stream to read the file content from.
      */
-    protected PosixPermissions getPermissions(final boolean checkIds) throws Exception {
-        statSelf();
-        boolean isInGroup = false;
-        if (checkIds) {
-            if(getAbstractFileSystem().isExecDisabled()) {
-                // Exec is disabled, so we won't be able to ascertain the current user's UID and GID.
-                // Return "always-true" permissions as a workaround, knowing that the SFTP server won't
-                // let us perform unauthorized actions anyway.
-                return new UserIsOwnerPosixPermissions(attrs.getPermissions());
-            }
+    @SuppressWarnings("resource")
+    @Override
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
+        // VFS-113: avoid npe
+        synchronized (getAbstractFileSystem()) {
+            final ChannelSftp channel = getAbstractFileSystem().getChannel();
+            try {
+                // return channel.get(getName().getPath());
+                // hmmm - using the in memory method is soooo much faster ...
 
-            for (final int groupId : getAbstractFileSystem().getGroupsIds()) {
-                if (groupId == attrs.getGId()) {
-                    isInGroup = true;
-                    break;
+                // TODO - Don't read the entire file into memory. Use the
+                // stream-based methods on ChannelSftp once they work properly
+
+                /*
+                 * final ByteArrayOutputStream outstr = new ByteArrayOutputStream(); channel.get(relPath, outstr);
+                 * outstr.close(); return new ByteArrayInputStream(outstr.toByteArray());
+                 */
+
+                final InputStream inputStream;
+                try {
+                    // VFS-210: sftp allows to gather an input stream even from a directory and will
+                    // fail on first read. So we need to check the type anyway
+                    if (!getType().hasContent()) {
+                        throw new FileSystemException("vfs.provider/read-not-file.error", getName());
+                    }
+
+                    inputStream = channel.get(relPath);
+                } catch (final SftpException e) {
+                    if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+                        throw new FileNotFoundException(getName());
+                    }
+
+                    throw new FileSystemException(e);
                 }
+
+                return new SftpInputStream(channel, inputStream, bufferSize);
+
+            } finally {
+                // getAbstractFileSystem().putChannel(channel);
             }
         }
-        final boolean isOwner = checkIds && attrs.getUId() == getAbstractFileSystem().getUId();
-        return new PosixPermissions(attrs.getPermissions(), isOwner, isInGroup);
     }
 
     @Override
-    protected boolean doIsReadable() throws Exception {
-        return getPermissions(true).isReadable();
+    protected long doGetLastModifiedTime() throws Exception {
+        if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_ACMODTIME) == 0) {
+            throw new FileSystemException("vfs.provider.sftp/unknown-modtime.error");
+        }
+        return attrs.getMTime() * MOD_TIME_FACTOR;
     }
 
+    /**
+     * Creates an output stream to write the file content to.
+     */
     @Override
-    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception {
-        final PosixPermissions permissions = getPermissions(false);
-        final int newPermissions = permissions.makeReadable(readable, ownerOnly);
-        if (newPermissions == permissions.getPermissions()) {
-            return true;
-        }
-
-        attrs.setPERMISSIONS(newPermissions);
-        flushStat();
+    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
+        // TODO - Don't write the entire file into memory. Use the stream-based
+        // methods on ChannelSftp once the work properly
+        /*
+         * final ChannelSftp channel = getAbstractFileSystem().getChannel(); return new SftpOutputStream(channel);
+         */
 
-        return true;
+        final ChannelSftp channel = getAbstractFileSystem().getChannel();
+        return new SftpOutputStream(channel, channel.put(relPath, bAppend ? ChannelSftp.APPEND : ChannelSftp.OVERWRITE));
     }
 
     @Override
-    protected boolean doIsWriteable() throws Exception {
-        return getPermissions(true).isWritable();
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
+        return new SftpRandomAccessContent(this, mode);
     }
 
+    /**
+     * Determines the type of this file, returns null if the file does not exist.
+     */
     @Override
-    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
-        final PosixPermissions permissions = getPermissions(false);
-        final int newPermissions = permissions.makeWritable(writable, ownerOnly);
-        if (newPermissions == permissions.getPermissions()) {
-            return true;
+    protected FileType doGetType() throws Exception {
+        if (attrs == null) {
+            statSelf();
+        }
+
+        if (attrs == null) {
+            return FileType.IMAGINARY;
         }
 
-        attrs.setPERMISSIONS(newPermissions);
-        flushStat();
-
-        return true;
+        if ((attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS) == 0) {
+            throw new FileSystemException("vfs.provider.sftp/unknown-permissions.error");
+        }
+        if (attrs.isDir()) {
+            return FileType.FOLDER;
+        }
+        return FileType.FILE;
     }
 
     @Override
@@ -293,17 +255,22 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     }
 
     @Override
-    protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
-        final PosixPermissions permissions = getPermissions(false);
-        final int newPermissions = permissions.makeExecutable(executable, ownerOnly);
-        if (newPermissions == permissions.getPermissions()) {
-            return true;
-        }
+    protected boolean doIsReadable() throws Exception {
+        return getPermissions(true).isReadable();
+    }
 
-        attrs.setPERMISSIONS(newPermissions);
-        flushStat();
+    @Override
+    protected boolean doIsWriteable() throws Exception {
+        return getPermissions(true).isWritable();
+    }
 
-        return true;
+    /**
+     * Lists the children of this file.
+     */
+    @Override
+    protected String[] doListChildren() throws Exception {
+        // use doListChildrenResolved for performance
+        return null;
     }
 
     /**
@@ -385,28 +352,83 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     }
 
     /**
-     * Lists the children of this file.
+     * Renames the file.
      */
     @Override
-    protected String[] doListChildren() throws Exception {
-        // use doListChildrenResolved for performance
-        return null;
+    protected void doRename(final FileObject newFile) throws Exception {
+        final ChannelSftp channel = getAbstractFileSystem().getChannel();
+        try {
+            final SftpFileObject newSftpFileObject = (SftpFileObject) FileObjectUtils.getAbstractFileObject(newFile);
+            channel.rename(relPath, newSftpFileObject.relPath);
+        } finally {
+            getAbstractFileSystem().putChannel(channel);
+        }
+    }
+
+    @Override
+    protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
+        final PosixPermissions permissions = getPermissions(false);
+        final int newPermissions = permissions.makeExecutable(executable, ownerOnly);
+        if (newPermissions == permissions.getPermissions()) {
+            return true;
+        }
+
+        attrs.setPERMISSIONS(newPermissions);
+        flushStat();
+
+        return true;
     }
 
     /**
-     * Returns the size of the file content (in bytes).
+     * Sets the last modified time of this file. Is only called if {@link #doGetType} does not return
+     * {@link FileType#IMAGINARY}.
+     *
+     * @param modtime is modification time in milliseconds. SFTP protocol can send times with nanosecond precision but
+     *            at the moment jsch send them with second precision.
      */
     @Override
-    protected long doGetContentSize() throws Exception {
-        if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_SIZE) == 0) {
-            throw new FileSystemException("vfs.provider.sftp/unknown-size.error");
+    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
+        final int newMTime = (int) (modtime / MOD_TIME_FACTOR);
+        attrs.setACMODTIME(attrs.getATime(), newMTime);
+        flushStat();
+        return true;
+    }
+
+    @Override
+    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception {
+        final PosixPermissions permissions = getPermissions(false);
+        final int newPermissions = permissions.makeReadable(readable, ownerOnly);
+        if (newPermissions == permissions.getPermissions()) {
+            return true;
         }
-        return attrs.getSize();
+
+        attrs.setPERMISSIONS(newPermissions);
+        flushStat();
+
+        return true;
     }
 
     @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        return new SftpRandomAccessContent(this, mode);
+    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
+        final PosixPermissions permissions = getPermissions(false);
+        final int newPermissions = permissions.makeWritable(writable, ownerOnly);
+        if (newPermissions == permissions.getPermissions()) {
+            return true;
+        }
+
+        attrs.setPERMISSIONS(newPermissions);
+        flushStat();
+
+        return true;
+    }
+
+    private void flushStat() throws IOException, SftpException {
+        final ChannelSftp channel = getAbstractFileSystem().getChannel();
+        try {
+            channel.setStat(relPath, attrs);
+        } finally {
+            getAbstractFileSystem().putChannel(channel);
+        }
     }
 
     /**
@@ -426,108 +448,86 @@ public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
     }
 
     /**
-     * Creates an input stream to read the file content from.
+     * Returns the POSIX type permissions of the file.
+     *
+     * @param checkIds {@code true} if user and group ID should be checked (needed for some access rights checks)
+     * @return A PosixPermission object
+     * @throws Exception If an error occurs
+     * @since 2.1
      */
-    @SuppressWarnings("resource")
-    @Override
-    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
-        // VFS-113: avoid npe
-        synchronized (getAbstractFileSystem()) {
-            final ChannelSftp channel = getAbstractFileSystem().getChannel();
-            try {
-                // return channel.get(getName().getPath());
-                // hmmm - using the in memory method is soooo much faster ...
-
-                // TODO - Don't read the entire file into memory. Use the
-                // stream-based methods on ChannelSftp once they work properly
-
-                /*
-                 * final ByteArrayOutputStream outstr = new ByteArrayOutputStream(); channel.get(relPath, outstr);
-                 * outstr.close(); return new ByteArrayInputStream(outstr.toByteArray());
-                 */
-
-                final InputStream inputStream;
-                try {
-                    // VFS-210: sftp allows to gather an input stream even from a directory and will
-                    // fail on first read. So we need to check the type anyway
-                    if (!getType().hasContent()) {
-                        throw new FileSystemException("vfs.provider/read-not-file.error", getName());
-                    }
-
-                    inputStream = channel.get(relPath);
-                } catch (final SftpException e) {
-                    if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
-                        throw new FileNotFoundException(getName());
-                    }
+    protected PosixPermissions getPermissions(final boolean checkIds) throws Exception {
+        statSelf();
+        boolean isInGroup = false;
+        if (checkIds) {
+            if(getAbstractFileSystem().isExecDisabled()) {
+                // Exec is disabled, so we won't be able to ascertain the current user's UID and GID.
+                // Return "always-true" permissions as a workaround, knowing that the SFTP server won't
+                // let us perform unauthorized actions anyway.
+                return new UserIsOwnerPosixPermissions(attrs.getPermissions());
+            }
 
-                    throw new FileSystemException(e);
+            for (final int groupId : getAbstractFileSystem().getGroupsIds()) {
+                if (groupId == attrs.getGId()) {
+                    isInGroup = true;
+                    break;
                 }
-
-                return new SftpInputStream(channel, inputStream, bufferSize);
-
-            } finally {
-                // getAbstractFileSystem().putChannel(channel);
             }
         }
+        final boolean isOwner = checkIds && attrs.getUId() == getAbstractFileSystem().getUId();
+        return new PosixPermissions(attrs.getPermissions(), isOwner, isInGroup);
     }
 
     /**
-     * Creates an output stream to write the file content to.
+     * Called when the type or content of this file changes.
      */
     @Override
-    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
-        // TODO - Don't write the entire file into memory. Use the stream-based
-        // methods on ChannelSftp once the work properly
-        /*
-         * final ChannelSftp channel = getAbstractFileSystem().getChannel(); return new SftpOutputStream(channel);
-         */
-
-        final ChannelSftp channel = getAbstractFileSystem().getChannel();
-        return new SftpOutputStream(channel, channel.put(relPath, bAppend ? ChannelSftp.APPEND : ChannelSftp.OVERWRITE));
+    protected void onChange() throws Exception {
+        statSelf();
     }
 
     /**
-     * An InputStream that monitors for end-of-file.
+     * Sets attrs from listChildrenResolved
      */
-    private class SftpInputStream extends MonitorInputStream {
-        private final ChannelSftp channel;
-
-        public SftpInputStream(final ChannelSftp channel, final InputStream in) {
-            super(in);
-            this.channel = channel;
-        }
-
-        public SftpInputStream(final ChannelSftp channel, final InputStream in, final int bufferSize) {
-            super(in, bufferSize);
-            this.channel = channel;
-        }
-
-        /**
-         * Called after the stream has been closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            getAbstractFileSystem().putChannel(channel);
-        }
+    private void setStat(final SftpATTRS attrs) {
+        this.attrs = attrs;
     }
 
     /**
-     * An OutputStream that wraps an sftp OutputStream, and closes the channel when the stream is closed.
+     * Fetches file attributes from server.
+     *
+     * @throws IOException if an error occurs.
      */
-    private class SftpOutputStream extends MonitorOutputStream {
-        private final ChannelSftp channel;
-
-        public SftpOutputStream(final ChannelSftp channel, final OutputStream out) {
-            super(out);
-            this.channel = channel;
-        }
+    private void statSelf() throws IOException {
+        ChannelSftp channelSftp = null;
+        try {
+            channelSftp = getAbstractFileSystem().getChannel();
+            setStat(channelSftp.stat(relPath));
+        } catch (final SftpException e) {
+            try {
+                // maybe the channel has some problems, so recreate the channel and retry
+                if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+                    channelSftp.disconnect();
+                    channelSftp = getAbstractFileSystem().getChannel();
+                    setStat(channelSftp.stat(relPath));
+                } else {
+                    // Really does not exist
+                    attrs = null;
+                }
+            } catch (final SftpException innerEx) {
+                // TODO - not strictly true, but jsch 0.1.2 does not give us
+                // enough info in the exception. Should be using:
+                // if ( e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE )
+                // However, sometimes the exception has the correct id, and
+                // sometimes
+                // it does not. Need to look into why.
 
-        /**
-         * Called after this stream is closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            getAbstractFileSystem().putChannel(channel);
+                // Does not exist
+                attrs = null;
+            }
+        } finally {
+            if (channelSftp != null) {
+                getAbstractFileSystem().putChannel(channelSftp);
+            }
         }
     }