You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/07/07 10:07:47 UTC

mina-sshd git commit: [SSHD-530] Separate buffer decoding from processing in SFTP subsytem implementation

Repository: mina-sshd
Updated Branches:
  refs/heads/master 823fcee22 -> f60fb919c


[SSHD-530] Separate buffer decoding from processing in SFTP subsytem implementation


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/f60fb919
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/f60fb919
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/f60fb919

Branch: refs/heads/master
Commit: f60fb919c4a9e5f314dcf7d4fe052f54a201c5dc
Parents: 823fcee
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Tue Jul 7 11:07:38 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Tue Jul 7 11:07:38 2015 +0300

----------------------------------------------------------------------
 .../subsystem/sftp/SftpFileSystemProvider.java  |    2 +-
 .../sshd/common/util/buffer/BufferUtils.java    |   19 +-
 .../server/subsystem/sftp/SftpSubsystem.java    | 1270 +++++++++++-------
 .../sshd/client/subsystem/sftp/SftpTest.java    |   12 +-
 4 files changed, 775 insertions(+), 528 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f60fb919/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index bcac6ae..e041cf4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -626,7 +626,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         
         SftpFileSystem fs = p.getFileSystem();
         if (x || (w && fs.isReadOnly())) {
-            throw new AccessDeniedException(path.toString());
+            throw new AccessDeniedException("Filesystem is read-only: " + path.toString());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f60fb919/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
index 0e3cb09..4991ebb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
@@ -294,7 +294,7 @@ public class BufferUtils {
      * and then moves the write position it back to its original value.
      * @param buffer The {@link Buffer}
      * @param lenPos The offset in the buffer where the length placeholder is
-     * to be update - <B>Note:</B> assumption is that the encoded data start
+     * to be update - <B>Note:</B> assumption is that the encoded data starts
      * <U>immediately</U> after the placeholder
      * @return The amount of data that has been encoded
      */
@@ -307,4 +307,21 @@ public class BufferUtils {
         buffer.wpos(endPos);
         return dataLength;
     }
+    
+    /**
+     * Updates a 32-bit &quot;placeholder&quot location for data length - moves
+     * the write position to the specified placeholder position, updates the length
+     * value and then moves the write position it back to its original value.
+     * @param buffer The {@link Buffer}
+     * @param lenPos The offset in the buffer where the length placeholder is
+     * to be update - <B>Note:</B> assumption is that the encoded data starts
+     * <U>immediately</U> after the placeholder
+     * @param dataLength The length to update
+     */
+    public static void updateLengthPlaceholder(Buffer buffer, int lenPos, int dataLength) {
+        int curPos = buffer.wpos();
+        buffer.wpos(lenPos);
+        buffer.putInt(dataLength);
+        buffer.wpos(curPos);
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f60fb919/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 9445b35..eaf5f43 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -42,6 +42,7 @@ import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.NoSuchFileException;
+import java.nio.file.NotDirectoryException;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
@@ -68,6 +69,7 @@ import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -188,28 +190,28 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         ALL_SFTP_IMPL = sb.toString();
     }
 
-    private ExitCallback callback;
-    private InputStream in;
-    private OutputStream out;
-    private OutputStream err;
-    private Environment env;
-    private Random randomizer;
-    private int fileHandleSize = DEFAULT_FILE_HANDLE_SIZE;
-    private int maxFileHandleRounds = DEFAULT_FILE_HANDLE_ROUNDS;
-    private ServerSession session;
-    private boolean closed;
-	private ExecutorService executors;
-	private boolean shutdownExecutor;
-	private Future<?> pendingFuture;
-	private byte[] workBuf = new byte[Math.max(DEFAULT_FILE_HANDLE_SIZE, Integer.SIZE / Byte.SIZE)]; // TODO in JDK-8 use Integer.BYTES
-    private FileSystem fileSystem = FileSystems.getDefault();
-    private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
-    private long requestsCount;
-    private int version;
-    private final Map<String, byte[]> extensions = new HashMap<>();
-    private final Map<String, Handle> handles = new HashMap<>();
-
-    private final UnsupportedAttributePolicy unsupportedAttributePolicy;
+    protected ExitCallback callback;
+    protected InputStream in;
+    protected OutputStream out;
+    protected OutputStream err;
+    protected Environment env;
+    protected Random randomizer;
+    protected int fileHandleSize = DEFAULT_FILE_HANDLE_SIZE;
+    protected int maxFileHandleRounds = DEFAULT_FILE_HANDLE_ROUNDS;
+    protected ServerSession session;
+    protected boolean closed;
+    protected ExecutorService executors;
+	protected boolean shutdownExecutor;
+	protected Future<?> pendingFuture;
+	protected byte[] workBuf = new byte[Math.max(DEFAULT_FILE_HANDLE_SIZE, Integer.SIZE / Byte.SIZE)]; // TODO in JDK-8 use Integer.BYTES
+	protected FileSystem fileSystem = FileSystems.getDefault();
+    protected Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
+    protected long requestsCount;
+    protected int version;
+    protected final Map<String, byte[]> extensions = new HashMap<>();
+    protected final Map<String, Handle> handles = new HashMap<>();
+
+    protected final UnsupportedAttributePolicy unsupportedAttributePolicy;
 
     protected static abstract class Handle implements java.io.Closeable {
         private Path file;
@@ -233,6 +235,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
     }
 
+    protected static class InvalidHandleException extends IOException {
+        private static final long serialVersionUID = -1686077114375131889L;
+
+        public InvalidHandleException(String handle, Handle h, Class<? extends Handle> expected) {
+            super(handle + "[" + h + "] is not a " + expected.getSimpleName());
+        }
+    }
+
     protected static class DirectoryHandle extends Handle implements Iterator<Path> {
         private boolean done, sendDotDot, sendDot=true;
         // the directory should be read once at "open directory"
@@ -693,13 +703,25 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
     protected void doTextSeek(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         long line = buffer.getLong();
+        try {
+            // TODO : implement text-seek - see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-6.3
+            doTextSeek(id, handle, line);
+        } catch(IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doTextSeek(int id, String handle, long line) throws IOException {
         Handle h = handles.get(handle);
         if (log.isDebugEnabled()) {
             log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}[{}], line={})", handle, h, Long.valueOf(line));
         }
 
-        // TODO : implement text-seek - see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-6.3
-        sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(text-seek) is unsupported or not implemented");
+        FileHandle fileHandle = validateHandle(handle, h, FileHandle.class);
+        throw new UnsupportedOperationException("doTextSeek(" + fileHandle + ")");
     }
 
     protected void doMD5Hash(Buffer buffer, int id, String targetType) throws IOException {
@@ -707,152 +729,151 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         long startOffset = buffer.getLong();
         long length = buffer.getLong();
         byte[] quickCheckHash = buffer.getBytes();
+        
+        try {
+            byte[] hashValue = doMD5Hash(id, targetType, target, startOffset, length, quickCheckHash);
+            buffer.clear();
+
+            buffer.putByte((byte) SSH_FXP_EXTENDED_REPLY);
+            buffer.putInt(id);
+            buffer.putString(targetType);
+            buffer.putBytes(hashValue);
+            send(buffer);
+        } catch(Exception e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected byte[] doMD5Hash(int id, String targetType, String target, long startOffset, long length, byte[] quickCheckHash) throws Exception {
         if (log.isDebugEnabled()) {
             log.debug("doMD5Hash({})[{}] offset={}, length={}, quick-hash={}",
                       targetType, target, Long.valueOf(startOffset), Long.valueOf(length), BufferUtils.printHex(':', quickCheckHash));
         }
-        
-        try {
-            Path path;
-            if (SftpConstants.EXT_MD5HASH_HANDLE.equalsIgnoreCase(targetType)) {
-                Handle p = handles.get(target);
-                if (p == null) {
-                    throw new FileNotFoundException("Unknown handle: " + target);
-                }
-                
-                if (!(p instanceof FileHandle)) {
-                    throw new IOException("Not a file: " + p);
-                }
-                
-                path = p.getFile();
-
-                /*
-                 * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1:
-                 * 
-                 *      The handle MUST be a file handle, and ACE4_READ_DATA MUST
-                 *      have been included in the desired-access when the file
-                 *      was opened
-                 */
-                int access = ((FileHandle) p).getAccessMask();
-                if ((access & ACE4_READ_DATA) == 0) {
-                    throw new AccessDeniedException(path.toString(), path.toString(), "File not opened for read");
-                }
-            } else {
-                path = resolveFile(target);
-                if (Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
-                    throw new IOException("Not a file: " + path);
-                }
-            }
+
+        Path path;
+        if (SftpConstants.EXT_MD5HASH_HANDLE.equalsIgnoreCase(targetType)) {
+            Handle h = handles.get(target);
+            FileHandle fileHandle = validateHandle(target, h, FileHandle.class); 
+            path = fileHandle.getFile();
 
             /*
              * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1:
-             *
-             *      If both start-offset and length are zero, the entire file should be included
+             * 
+             *      The handle MUST be a file handle, and ACE4_READ_DATA MUST
+             *      have been included in the desired-access when the file
+             *      was opened
              */
-            long effectiveLength = length;
-            if ((startOffset == 0L) && (length == 0L)) {
-                effectiveLength = Files.size(path);
+            int access = fileHandle.getAccessMask();
+            if ((access & ACE4_READ_DATA) == 0) {
+                throw new AccessDeniedException("File not opened for read: " + path);
             }
-            
-            Digest digest = BuiltinDigests.md5.create();
-            digest.init();
-
-            byte[] digestBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
-            ByteBuffer bb = ByteBuffer.wrap(digestBuf);
-            boolean hashMatches = false;
-            byte[] hashValue = null;
-
-            try(FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
-                channel.position(startOffset);
-
-                /*
-                 * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1:
-                 * 
-                 *      If this is a zero length string, the client does not have the
-                 *      data, and is requesting the hash for reasons other than comparing
-                 *      with a local file.  The server MAY return SSH_FX_OP_UNSUPPORTED in
-                 *      this case.
-                 */
-                if (GenericUtils.length(quickCheckHash) <= 0) {
-                    // TODO consider limiting it - e.g., if the requested effective length is <= than some (configurable) threshold
-                    hashMatches = true;
-                } else {
-                    int readLen = channel.read(bb);
-                    effectiveLength -= readLen;
-                    digest.update(digestBuf, 0, readLen);
-    
-                    hashValue = digest.digest();
-                    hashMatches = Arrays.equals(quickCheckHash, hashValue);
-                    if (hashMatches) {
-                        /*
-                         * Need to re-initialize the digester due to the Javadoc:
-                         * 
-                         *      "The digest method can be called once for a given number
-                         *       of updates. After digest has been called, the MessageDigest
-                         *       object is reset to its initialized state." 
-                         */
-                        if (effectiveLength > 0L) {
-                            digest = BuiltinDigests.md5.create();
-                            digest.init();
-                            digest.update(digestBuf, 0, readLen);
-                            hashValue = null;   // start again
-                        }
-                    } else {
-                        if (log.isTraceEnabled()) {
-                            log.trace("doMD5Hash({})[{}] offset={}, length={} - quick-hash mismatched expected={}, actual={}",
-                                      targetType, target, Long.valueOf(startOffset), Long.valueOf(length),
-                                      BufferUtils.printHex(':', quickCheckHash), BufferUtils.printHex(':', hashValue));
-                        }
-                    }
-                }
+        } else {
+            path = resolveFile(target);
+            if (Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
+                throw new NotDirectoryException(path.toString());
+            }
+        }
+
+        /*
+         * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1:
+         *
+         *      If both start-offset and length are zero, the entire file should be included
+         */
+        long effectiveLength = length;
+        if ((startOffset == 0L) && (length == 0L)) {
+            effectiveLength = Files.size(path);
+        }
+        
+        Digest digest = BuiltinDigests.md5.create();
+        digest.init();
+
+        byte[] digestBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
+        ByteBuffer bb = ByteBuffer.wrap(digestBuf);
+        boolean hashMatches = false;
+        byte[] hashValue = null;
 
+        try(FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
+            channel.position(startOffset);
+
+            /*
+             * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1:
+             * 
+             *      If this is a zero length string, the client does not have the
+             *      data, and is requesting the hash for reasons other than comparing
+             *      with a local file.  The server MAY return SSH_FX_OP_UNSUPPORTED in
+             *      this case.
+             */
+            if (GenericUtils.length(quickCheckHash) <= 0) {
+                // TODO consider limiting it - e.g., if the requested effective length is <= than some (configurable) threshold
+                hashMatches = true;
+            } else {
+                int readLen = channel.read(bb);
+                effectiveLength -= readLen;
+                digest.update(digestBuf, 0, readLen);
+
+                hashValue = digest.digest();
+                hashMatches = Arrays.equals(quickCheckHash, hashValue);
                 if (hashMatches) {
-                    while(effectiveLength > 0L) {
-                        bb.clear();
-                        int readLen = channel.read(bb); 
-                        effectiveLength -= readLen;
+                    /*
+                     * Need to re-initialize the digester due to the Javadoc:
+                     * 
+                     *      "The digest method can be called once for a given number
+                     *       of updates. After digest has been called, the MessageDigest
+                     *       object is reset to its initialized state." 
+                     */
+                    if (effectiveLength > 0L) {
+                        digest = BuiltinDigests.md5.create();
+                        digest.init();
                         digest.update(digestBuf, 0, readLen);
-                    }
-                    
-                    if (hashValue == null) {    // check if did any more iterations after the quick hash
-                        hashValue = digest.digest();
+                        hashValue = null;   // start again
                     }
                 } else {
-                    hashValue = GenericUtils.EMPTY_BYTE_ARRAY;
+                    if (log.isTraceEnabled()) {
+                        log.trace("doMD5Hash({})[{}] offset={}, length={} - quick-hash mismatched expected={}, actual={}",
+                                  targetType, target, Long.valueOf(startOffset), Long.valueOf(length),
+                                  BufferUtils.printHex(':', quickCheckHash), BufferUtils.printHex(':', hashValue));
+                    }
                 }
             }
 
-            if (log.isDebugEnabled()) {
-                log.debug("doMD5Hash({})[{}] offset={}, length={}, quick-hash={} - match={}, hash={}",
-                          targetType, target, Long.valueOf(startOffset), Long.valueOf(length), BufferUtils.printHex(':', quickCheckHash),
-                          Boolean.valueOf(hashMatches), BufferUtils.printHex(':', hashValue));
+            if (hashMatches) {
+                while(effectiveLength > 0L) {
+                    bb.clear();
+                    int readLen = channel.read(bb); 
+                    effectiveLength -= readLen;
+                    digest.update(digestBuf, 0, readLen);
+                }
+                
+                if (hashValue == null) {    // check if did any more iterations after the quick hash
+                    hashValue = digest.digest();
+                }
+            } else {
+                hashValue = GenericUtils.EMPTY_BYTE_ARRAY;
             }
+        }
 
-            buffer.clear();
-
-            buffer.putByte((byte) SSH_FXP_EXTENDED_REPLY);
-            buffer.putInt(id);
-            buffer.putString(targetType);
-            buffer.putBytes(hashValue);
-            send(buffer);
-        } catch(Exception e) {
-            sendStatus(id, e);
+        if (log.isDebugEnabled()) {
+            log.debug("doMD5Hash({})[{}] offset={}, length={}, quick-hash={} - match={}, hash={}",
+                      targetType, target, Long.valueOf(startOffset), Long.valueOf(length), BufferUtils.printHex(':', quickCheckHash),
+                      Boolean.valueOf(hashMatches), BufferUtils.printHex(':', hashValue));
         }
+        
+        return hashValue;
     }
 
     protected void doVersionSelect(Buffer buffer, int id) throws IOException {
+        String proposed = buffer.getString();
         /*
          * The 'version-select' MUST be the first request from the client to the
          * server; if it is not, the server MUST fail the request and close the
          * channel.
          */
         if (requestsCount > 0L) {
-           sendStatus(id, SSH_FX_FAILURE, "Version selection not the 1st request");
+           sendStatus(id, SSH_FX_FAILURE, "Version selection not the 1st request for proposal = " + proposed);
            session.close(true);
            return;
         }
 
-        String proposed = buffer.getString();
         Boolean result = validateProposedVersion(id, proposed);
         /*
          * "MUST then close the channel without processing any further requests"
@@ -948,102 +969,120 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         int mask = buffer.getInt();
         
         try {
-            Handle p = handles.get(handle);
-            if (log.isDebugEnabled()) {
-                log.debug("Received SSH_FXP_BLOCK (handle={}[{}], offset={}, length={}, mask=0x{})",
-                          handle, p, Long.valueOf(offset), Long.valueOf(length), Integer.toHexString(mask));
-            }
+            doBlock(id, handle, offset, length, mask);
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
 
-            if (!(p instanceof FileHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-                return;
-            }
+        sendStatus(id, SSH_FX_OK, "");
+    }
 
-            FileHandle fileHandle = (FileHandle) p;
-            fileHandle.lock(offset, length, mask);
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (IOException | OverlappingFileLockException e) {
-            sendStatus(id, e);
+    protected void doBlock(int id, String handle, long offset, long length, int mask) throws IOException {
+        Handle p = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_BLOCK (handle={}[{}], offset={}, length={}, mask=0x{})",
+                      handle, p, Long.valueOf(offset), Long.valueOf(length), Integer.toHexString(mask));
         }
+        
+        FileHandle fileHandle = validateHandle(handle, p, FileHandle.class);
+        fileHandle.lock(offset, length, mask);
     }
 
     protected void doUnblock(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         long offset = buffer.getLong();
         long length = buffer.getLong();
+        boolean found;
         try {
-            Handle p = handles.get(handle);
-            if (log.isDebugEnabled()) {
-                log.debug("Received SSH_FXP_UNBLOCK (handle={}[{}], offset={}, length={})",
-                          handle, p, Long.valueOf(offset), Long.valueOf(length));
-            }
+            found = doUnblock(id, handle, offset, length);
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
 
-            if (!(p instanceof FileHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-                return;
-            }
+        sendStatus(id, found ? SSH_FX_OK : SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK, "");
+    }
 
-            FileHandle fileHandle = (FileHandle) p;
-            boolean found = fileHandle.unlock(offset, length);
-            sendStatus(id, found ? SSH_FX_OK : SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK, "");
-        } catch (IOException e) {
-            sendStatus(id, e);
+    protected boolean doUnblock(int id, String handle, long offset, long length) throws IOException {
+        Handle p = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_UNBLOCK (handle={}[{}], offset={}, length={})",
+                      handle, p, Long.valueOf(offset), Long.valueOf(length));
         }
+
+        FileHandle fileHandle = validateHandle(handle, p, FileHandle.class);
+        return fileHandle.unlock(offset, length);
     }
 
     protected void doLink(Buffer buffer, int id) throws IOException {
-        String targetpath = buffer.getString();
-        String linkpath = buffer.getString();
+        String targetPath = buffer.getString();
+        String linkPath = buffer.getString();
         boolean symLink = buffer.getBoolean();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})",
-                      linkpath, targetpath, Boolean.valueOf(symLink));
-        }
 
         try {
-            Path link = resolveFile(linkpath);
-            Path target = fileSystem.getPath(targetpath);
-            if (symLink) {
-                Files.createSymbolicLink(link, target);
-            } else {
-                Files.createLink(link, target);
-            }
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (UnsupportedOperationException e) {
-            sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
-        } catch (IOException e) {
+            doLink(id, targetPath, linkPath, symLink);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
+        }
+        
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doLink(int id, String targetPath, String linkPath, boolean symLink) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})",
+                      linkPath, targetPath, Boolean.valueOf(symLink));
+        }
+        
+        Path link = resolveFile(linkPath);
+        Path target = fileSystem.getPath(targetPath);
+        if (symLink) {
+            Files.createSymbolicLink(link, target);
+        } else {
+            Files.createLink(link, target);
         }
     }
 
     protected void doSymLink(Buffer buffer, int id) throws IOException {
-        String targetpath = buffer.getString();
-        String linkpath = buffer.getString();
-        log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
+        String targetPath = buffer.getString();
+        String linkPath = buffer.getString();
         try {
-            Path link = resolveFile(linkpath);
-            Path target = fileSystem.getPath(targetpath);
-            Files.createSymbolicLink(link, target);
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (UnsupportedOperationException e) {
-            sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
-        } catch (IOException e) {
+            doSymLink(id, targetPath, linkPath);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doSymLink(int id, String targetPath, String linkPath) throws IOException {
+        log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", targetPath, linkPath);
+        Path link = resolveFile(linkPath);
+        Path target = fileSystem.getPath(targetPath);
+        Files.createSymbolicLink(link, target);
     }
 
     protected void doReadLink(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        log.debug("Received SSH_FXP_READLINK (path={})", path);
+        String path = buffer.getString(), l;
         try {
-            Path f = resolveFile(path);
-            String l = Files.readSymbolicLink(f).toString();
-            sendLink(id, l);
-        } catch (UnsupportedOperationException e) {
-            sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_READLINK is unsupported or not implemented");
-        } catch (IOException e) {
+             l = doReadLink(id, path);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+
+        sendLink(id, l);
+    }
+
+    protected String doReadLink(int id, String path) throws IOException {
+        Path f = resolveFile(path);
+        log.debug("Received SSH_FXP_READLINK (path={}[{}])", path, f);
+        
+        Path t = Files.readSymbolicLink(f);
+        return t.toString();
     }
 
     protected void doRename(Buffer buffer, int id) throws IOException {
@@ -1053,26 +1092,40 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         if (version >= SFTP_V5) {
             flags = buffer.getInt();
         }
+        try {
+            doRename(id, oldPath, newPath, flags);
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doRename(int id, String oldPath, String newPath, int flags) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})",
                        oldPath, newPath, Integer.toHexString(flags));
         }
 
-        List<CopyOption> opts = new ArrayList<>();
-        if ((flags & SSH_FXP_RENAME_ATOMIC) != 0) {
-            opts.add(StandardCopyOption.ATOMIC_MOVE);
-        }
-        if ((flags & SSH_FXP_RENAME_OVERWRITE) != 0) {
-            opts.add(StandardCopyOption.REPLACE_EXISTING);
-        }
-        try {
-            Path o = resolveFile(oldPath);
-            Path n = resolveFile(newPath);
-            Files.move(o, n, opts.toArray(new CopyOption[opts.size()]));
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (IOException e) {
-            sendStatus(id, e);
+        Collection<CopyOption> opts = Collections.emptyList();
+        if (flags != 0) {
+            opts = new ArrayList<>();
+            if ((flags & SSH_FXP_RENAME_ATOMIC) == SSH_FXP_RENAME_ATOMIC) {
+                opts.add(StandardCopyOption.ATOMIC_MOVE);
+            }
+            if ((flags & SSH_FXP_RENAME_OVERWRITE) == SSH_FXP_RENAME_OVERWRITE) {
+                opts.add(StandardCopyOption.REPLACE_EXISTING);
+            }
         }
+        
+        doRename(id, oldPath, newPath, opts);
+    }
+    
+    protected void doRename(int id, String oldPath, String newPath, Collection<CopyOption> opts) throws IOException {
+        Path o = resolveFile(oldPath);
+        Path n = resolveFile(newPath);
+        Files.move(o, n, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
     }
 
     // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-6
@@ -1080,24 +1133,33 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         String srcFile = buffer.getString();
         String dstFile = buffer.getString();
         boolean overwriteDestination = buffer.getBoolean();
+
+        try {
+            doCopyFile(id, srcFile, dstFile, overwriteDestination);
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doCopyFile(int id, String srcFile, String dstFile, boolean overwriteDestination) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("SSH_FXP_EXTENDED[{}] (src={}, dst={}, overwrite=0x{})",
                        SftpConstants.EXT_COPYFILE, srcFile, dstFile, Boolean.valueOf(overwriteDestination));
         }
         
-        CopyOption[] opts = overwriteDestination
-                ? new CopyOption[] { StandardCopyOption.REPLACE_EXISTING }
-                : IoUtils.EMPTY_COPY_OPTIONS
-                ;
+        doCopyFile(id, srcFile, dstFile,
+                   overwriteDestination
+                  ? Collections.<CopyOption>singletonList(StandardCopyOption.REPLACE_EXISTING)
+                  : Collections.<CopyOption>emptyList());
+    }
 
-        try {
-            Path src = resolveFile(srcFile);
-            Path dst = resolveFile(dstFile);
-            Files.copy(src, dst, opts);
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
+    protected void doCopyFile(int id, String srcFile, String dstFile, Collection<CopyOption> opts) throws IOException {
+        Path src = resolveFile(srcFile);
+        Path dst = resolveFile(dstFile);
+        Files.copy(src, dst, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
     }
 
     protected void doStat(Buffer buffer, int id) throws IOException {
@@ -1106,15 +1168,24 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         if (version >= SFTP_V4) {
             flags = buffer.getInt();
         }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_STAT (path={}, flags=0x{})", path, Integer.toHexString(flags));
-        }
+
+        Map<String,Object> attrs;
         try {
-            Path p = resolveFile(path);
-            sendAttrs(id, p, flags, true);
-        } catch (IOException e) {
+             attrs = doStat(id, path, flags);
+        } catch(IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+        
+        sendAttrs(id, attrs);
+    }
+
+    protected Map<String,Object> doStat(int id, String path, int flags) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_STAT (path={}, flags=0x{})", path, Integer.toHexString(flags));
+        }
+        Path p = resolveFile(path);
+        return resolveFileAttributes(p, flags, true);
     }
 
     protected void doRealPath(Buffer buffer, int id) throws IOException {
@@ -1125,49 +1196,69 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             path = ".";
         }
 
+        Map<String,?> attrs = Collections.<String, Object>emptyMap();
+        Path p;
         try {
             if (version < SFTP_V6) {
-                Path f = resolveFile(path);
-                Path abs = f.toAbsolutePath();
-                Path p = abs.normalize();
-                Boolean status = IoUtils.checkFileExists(p, IoUtils.EMPTY_LINK_OPTIONS);
-                if (status == null) {
-                    p = handleUnknownRealPathStatus(path, abs, p);
-                } else if (!status.booleanValue()) {
-                    throw new FileNotFoundException(p.toString());
-                }
-                sendPath(id, p, Collections.<String, Object>emptyMap());
+                p = doRealPathV6(id, path);
             } else {
                 // Read control byte
                 int control = 0;
                 if (buffer.available() > 0) {
                     control = buffer.getUByte();
                 }
-                List<String> paths = new ArrayList<>();
+
+                Collection<String> extraPaths = new LinkedList<>();
                 while (buffer.available() > 0) {
-                    paths.add(buffer.getString());
-                }
-                // Resolve path
-                Path p = resolveFile(path);
-                for (String p2 : paths) {
-                    p = p.resolve(p2);
+                    extraPaths.add(buffer.getString());
                 }
-                p = p.toAbsolutePath().normalize();
 
-                Map<String, Object> attrs = Collections.emptyMap();
+                p = doRealPathV345(id, path, extraPaths);
                 if (control == SSH_FXP_REALPATH_STAT_IF) {
                     try {
                         attrs = getAttributes(p, false);
                     } catch (IOException e) {
-                        // ignore
+                        if (log.isDebugEnabled()) {
+                            log.debug("Failed ({}) to retrieve attributes of {}: {}",
+                                      e.getClass().getSimpleName(), p, e.getMessage());
+                        }
                     }
                 } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
                     attrs = getAttributes(p, false);
                 }
-                sendPath(id, p, attrs);
             }
-        } catch (IOException e) {
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
+        }
+
+        sendPath(id, p, attrs);
+    }
+
+    protected Path doRealPathV345(int id, String path, Collection<String> extraPaths) throws IOException {
+        Path p = resolveFile(path);
+        
+        if (GenericUtils.size(extraPaths) > 0) {
+            for (String p2 : extraPaths) {
+                p = p.resolve(p2);
+            }
+        }
+
+        p = p.toAbsolutePath();
+        return p.normalize();
+    }
+
+    protected Path doRealPathV6(int id, String path) throws IOException {
+        Path f = resolveFile(path);
+        Path abs = f.toAbsolutePath();
+        Path p = abs.normalize();
+        Boolean status = IoUtils.checkFileExists(p, IoUtils.EMPTY_LINK_OPTIONS);
+        if (status == null) {
+            return handleUnknownRealPathStatus(path, abs, p);
+        } else if (status.booleanValue()) {
+            return p;
+        } else {
+            throw new FileNotFoundException(path);
         }
     }
 
@@ -1191,88 +1282,103 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
     protected void doRemoveDirectory(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
-        log.debug("Received SSH_FXP_RMDIR (path={})", path);
-        // attrs
         try {
-            Path p = resolveFile(path);
-            if (Files.isDirectory(p, IoUtils.getLinkOptions(false))) {
-                Files.delete(p);
-                sendStatus(id, SSH_FX_OK, "");
-            } else {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-            }
-        } catch (IOException e) {
+            doRemoveDirectory(id, path, IoUtils.getLinkOptions(false));
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
+        }
+        
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doRemoveDirectory(int id, String path, LinkOption ... options) throws IOException {
+        Path p = resolveFile(path);
+        log.debug("Received SSH_FXP_RMDIR (path={})[{}]", path, p);
+        if (Files.isDirectory(p, options)) {
+            Files.delete(p);
+        } else {
+            throw new NotDirectoryException(p.toString());
         }
     }
 
     protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
         Map<String, Object> attrs = readAttrs(buffer);
-
-        log.debug("Received SSH_FXP_MKDIR (path={}, attrs={})", path, attrs);
-        // attrs
         try {
-            Path            p = resolveFile(path);
-            LinkOption[]    options = IoUtils.getLinkOptions(false);
-            Boolean         status = IoUtils.checkFileExists(p, options);
-            if (status == null) {
-                throw new AccessDeniedException("Cannot make-directory existence for " + p);
-            }
-            if (status.booleanValue()) {
-                if (Files.isDirectory(p, options)) {
-                    sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
-                } else {
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-                }
+            doMakeDirectory(id, path, attrs, IoUtils.getLinkOptions(false));
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doMakeDirectory(int id, String path, Map<String,?> attrs, LinkOption ... options) throws IOException {
+        Path p = resolveFile(path);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_MKDIR (path={}[{}], attrs={})", path, p, attrs);
+        }
+        
+        Boolean  status = IoUtils.checkFileExists(p, options);
+        if (status == null) {
+            throw new AccessDeniedException("Cannot validate make-directory existence for " + p);
+        }
+
+        if (status.booleanValue()) {
+            if (Files.isDirectory(p, options)) {
+                throw new FileAlreadyExistsException(p.toString(), p.toString(), "Target directory already exists");
             } else {
-                Files.createDirectory(p);
-                setAttributes(p, attrs);
-                sendStatus(id, SSH_FX_OK, "");
+                throw new FileNotFoundException(p.toString() + " already exists as a file");
             }
-        } catch (IOException e) {
-            sendStatus(id, e);
+        } else {
+            Files.createDirectory(p);
+            setAttributes(p, attrs);
         }
     }
 
     protected void doRemove(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
-        log.debug("Received SSH_FXP_REMOVE (path={})", path);
         try {
-            Path            p = resolveFile(path);
-            LinkOption[]    options = IoUtils.getLinkOptions(false);
-            Boolean         status = IoUtils.checkFileExists(p, options);
-            if (status == null) {
-                throw new AccessDeniedException("Cannot determine existence of remove candidate: " + p);
-            }
-            if (!status.booleanValue()) {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-            } else if (Files.isDirectory(p, options)) {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-            } else {
-                Files.delete(p);
-                sendStatus(id, SSH_FX_OK, "");
-            }
-        } catch (IOException e) {
+            doRemove(id, path, IoUtils.getLinkOptions(false));
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
+        }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doRemove(int id, String path, LinkOption ... options) throws IOException {
+        Path p = resolveFile(path);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_REMOVE (path={}[{}])", path, p);
+        }
+        
+        Boolean status = IoUtils.checkFileExists(p, options);
+        if (status == null) {
+            throw new AccessDeniedException("Cannot determine existence of remove candidate: " + p);
+        }
+        if (!status.booleanValue()) {
+            throw new FileNotFoundException(p.toString());
+        } else if (Files.isDirectory(p, options)) {
+            throw new FileNotFoundException(p.toString() + " is as a folder");
+        } else {
+            Files.delete(p);
         }
     }
 
     protected void doReadDir(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
-        Handle p = handles.get(handle);
-        log.debug("Received SSH_FXP_READDIR (handle={}[{}])", handle, p);
+        Handle h = handles.get(handle);
+        log.debug("Received SSH_FXP_READDIR (handle={}[{}])", handle, h);
 
+        Buffer reply = null;
         try {
-            if (!(p instanceof DirectoryHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-                return;
-            }
-            
-            DirectoryHandle dh = (DirectoryHandle) p;
+            DirectoryHandle dh = validateHandle(handle, h, DirectoryHandle.class);
             if (dh.isDone()) {
-                sendStatus(id, SSH_FX_EOF, "", "");
-                return;
+                throw new EOFException("Directory reading is done");
             }
 
             Path            file = dh.getFile();
@@ -1283,92 +1389,126 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             }
 
             if (!status.booleanValue()) {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, file.toString());
+                throw new FileNotFoundException(file.toString());
             } else if (!Files.isDirectory(file, options)) {
-                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, file.toString());
+                throw new NotDirectoryException(file.toString());
             } else if (!Files.isReadable(file)) {
-                sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
-            } else {
-                if (dh.isSendDotDot() || dh.hasNext()) {
-                    // There is at least one file in the directory or we need to send the "..".
-                    // Send only a few files at a time to not create packets of a too
-                    // large size or have a timeout to occur.
-                    sendDirEntries(id, dh);
-                    if ((!dh.isSendDot()) && (!dh.isSendDotDot()) && (!dh.hasNext())) {
-                        // if no more files to send
-                        dh.markDone();
-                        dh.clearFileList();
-                    }
-                } else {
-                    // empty directory
+                throw new AccessDeniedException("Not readable: " + file.toString());
+            }
+
+            if (dh.isSendDotDot() || dh.hasNext()) {
+                // There is at least one file in the directory or we need to send the "..".
+                // Send only a few files at a time to not create packets of a too
+                // large size or have a timeout to occur.
+                
+                reply = new ByteArrayBuffer();
+                reply.putByte((byte) SSH_FXP_NAME);
+                reply.putInt(id);
+                int lenPos = reply.wpos();
+                reply.putInt(0);
+
+                int count = doReadDir(id, dh, reply, FactoryManagerUtils.getIntProperty(session, MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH));
+                BufferUtils.updateLengthPlaceholder(reply, lenPos, count);
+                if (log.isTraceEnabled()) {
+                    log.trace("doReadDir({})[{}] - sent {} entries", handle, h, Integer.valueOf(count));
+                }
+                if ((!dh.isSendDot()) && (!dh.isSendDotDot()) && (!dh.hasNext())) {
+                    // if no more files to send
                     dh.markDone();
                     dh.clearFileList();
-                    sendStatus(id, SSH_FX_EOF, "", "");
                 }
+            } else {
+                // empty directory
+                dh.markDone();
+                dh.clearFileList();
+                throw new EOFException("Empty directory");
             }
-        } catch (IOException e) {
+            
+            ValidateUtils.checkNotNull(reply, "No reply buffer created", GenericUtils.EMPTY_OBJECT_ARRAY);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+        
+        send(reply);
     }
 
     protected void doOpenDir(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
+        String path = buffer.getString(), handle;
+
+        try {
+            handle = doOpenDir(id, path, IoUtils.getLinkOptions(false));
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendHandle(id, handle);
+    }
+
+    protected String doOpenDir(int id, String path, LinkOption ... options) throws IOException {
         Path f = resolveFile(path);
         Path abs = f.toAbsolutePath();
         Path p = abs.normalize();
         log.debug("Received SSH_FXP_OPENDIR (path={})[{}]", path, p);
+        
+        Boolean status = IoUtils.checkFileExists(p, options);
+        if (status == null) {
+            throw new AccessDeniedException("Cannot determine open-dir existence for " + p);
+        }
 
-        try {
-            LinkOption[] options = IoUtils.getLinkOptions(false);
-            Boolean status = IoUtils.checkFileExists(p, options);
-            if (status == null) {
-                throw new AccessDeniedException("Cannot determine open-dir existence of " + p);
-            }
-
-            if (!status.booleanValue()) {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
-            } else if (!Files.isDirectory(p, options)) {
-                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
-            } else if (!Files.isReadable(p)) {
-                sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
-            } else {
-                String handle = generateFileHandle(p);
-                handles.put(handle, new DirectoryHandle(p));
-                sendHandle(id, handle);
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
+        if (!status.booleanValue()) {
+            throw new FileNotFoundException(path);
+        } else if (!Files.isDirectory(p, options)) {
+            throw new NotDirectoryException(path);
+        } else if (!Files.isReadable(p)) {
+            throw new AccessDeniedException("Not readable: " + p);
+        } else {
+            String handle = generateFileHandle(p);
+            handles.put(handle, new DirectoryHandle(p));
+            return handle;
         }
     }
 
     protected void doFSetStat(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         Map<String, Object> attrs = readAttrs(buffer);
-        log.debug("Received SSH_FXP_FSETSTAT (handle={}, attrs={})", handle, attrs);
         try {
-            Handle p = handles.get(handle);
-            if (p == null) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-            } else {
-                setAttributes(p.getFile(), attrs);
-                sendStatus(id, SSH_FX_OK, "");
-            }
-        } catch (IOException | UnsupportedOperationException e) {
+            doFSetStat(id, handle, attrs);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+        
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doFSetStat(int id, String handle, Map<String,?> attrs) throws IOException {
+        Handle h = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_FSETSTAT (handle={}[{}], attrs={})", handle, h, attrs);
+        }
+
+        setAttributes(validateHandle(handle, h, Handle.class).getFile(), attrs);
     }
 
     protected void doSetStat(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
         Map<String, Object> attrs = readAttrs(buffer);
-        log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
         try {
-            Path p = resolveFile(path);
-            setAttributes(p, attrs);
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (IOException | UnsupportedOperationException e) {
+            doSetStat(id, path, attrs);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doSetStat(int id, String path, Map<String,?> attrs) throws IOException {
+        log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
+        Path p = resolveFile(path);
+        setAttributes(p, attrs);
     }
 
     protected void doFStat(Buffer buffer, int id) throws IOException {
@@ -1377,19 +1517,25 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         if (version >= SFTP_V4) {
             flags = buffer.getInt();
         }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_FSTAT (handle={}, flags=0x{})", handle, Integer.toHexString(flags));
-        }
+        
+        Map<String,?> attrs;
         try {
-            Handle p = handles.get(handle);
-            if (p == null) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-            } else {
-                sendAttrs(id, p.getFile(), flags, true);
-            }
-        } catch (IOException e) {
+            attrs = doFStat(id, handle, flags);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
+        }
+
+        sendAttrs(id, attrs);
+    }
+
+    protected Map<String,Object> doFStat(int id, String handle, int flags) throws IOException {
+        Handle h = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_FSTAT (handle={}[{}], flags=0x{})", handle, h, Integer.toHexString(flags));
         }
+        
+        return resolveFileAttributes(validateHandle(handle, h, Handle.class).getFile(), flags, true);
     }
 
     protected void doLStat(Buffer buffer, int id) throws IOException {
@@ -1398,112 +1544,118 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         if (version >= SFTP_V4) {
             flags = buffer.getInt();
         }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_LSTAT (path={}, flags=0x{})", path, Integer.toHexString(flags));
-        }
+        
+        Map<String,?> attrs;
         try {
-            Path p = resolveFile(path);
-            sendAttrs(id, p, flags, false);
-        } catch (IOException e) {
+            attrs = doLStat(id, path, flags);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+
+        sendAttrs(id, attrs);
+    }
+
+    protected Map<String,Object> doLStat(int id, String path, int flags) throws IOException {
+        Path p = resolveFile(path);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_LSTAT (path={}[{}], flags=0x{})", path, p, Integer.toHexString(flags));
+        }
+
+        return resolveFileAttributes(p, flags, false);
     }
 
     protected void doWrite(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         long offset = buffer.getLong();
         int length = buffer.getInt();
+        try {
+            doWrite(id, handle, offset, length, buffer.array(), buffer.rpos(), buffer.available());
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    protected void doWrite(int id, String handle, long offset, int length, byte[] data, int doff, int remaining) throws IOException {
+        Handle h = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_WRITE (handle={}[{}], offset={}, data=byte[{}])",
+                      handle, h, Long.valueOf(offset), Integer.valueOf(length));
+        }
+        
+        FileHandle fh = validateHandle(handle, h, FileHandle.class);
         if (length < 0) {
-            throw new IllegalStateException("Bad length (" + length + ") for writing to " + handle);
+            throw new IllegalStateException("Bad length (" + length + ") for writing to " + fh);
         }
 
-        int remaining = buffer.available();
         if (remaining < length) {
-            throw new IllegalStateException("Not enough buffer data for writing to " + handle + ": required=" + length + ", available=" + remaining);
+            throw new IllegalStateException("Not enough buffer data for writing to " + fh + ": required=" + length + ", available=" + remaining);
         }
 
-        byte[] data = buffer.array();
-        int doff = buffer.rpos();
-        try {
-            Handle p = handles.get(handle);
-            if (log.isDebugEnabled()) {
-                log.debug("Received SSH_FXP_WRITE (handle={}[{}], offset={}, data=byte[{}])",
-                          handle, p, Long.valueOf(offset), Integer.valueOf(length));
-            }
-
-            if (!(p instanceof FileHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-            } else {
-                FileHandle fh = (FileHandle) p;
-                fh.write(data, doff, length, offset);
-                sendStatus(id, SSH_FX_OK, "");
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
+        fh.write(data, doff, length, offset);
     }
 
     protected void doRead(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         long offset = buffer.getLong();
-        int len = buffer.getInt();
+        int readLen = buffer.getInt();
+        Buffer buf = new ByteArrayBuffer(readLen + Short.SIZE /* some extra */);
         try {
-            Handle p = handles.get(handle);
-            if (log.isDebugEnabled()) {
-                log.debug("Received SSH_FXP_READ (handle={}[{}], offset={}, length={})",
-                          handle, p, Long.valueOf(offset), Integer.valueOf(len));
-            }
-
-            if (!(p instanceof FileHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-            } else {
-                FileHandle fh = (FileHandle) p;
-                Buffer buf = new ByteArrayBuffer(len + 9);
-                buf.putByte((byte) SSH_FXP_DATA);
-                buf.putInt(id);
-                int pos = buf.wpos();
-                buf.putInt(0);
-                len = fh.read(buf.array(), buf.wpos(), len, offset);
-                if (len >= 0) {
-                    buf.wpos(pos);
-                    buf.putInt(len);
-                    buf.wpos(pos + 4 + len);
-                    send(buf);
-                } else {
-                    sendStatus(id, SSH_FX_EOF, "");
-                }
-            }
-        } catch (IOException e) {
+            buf.putByte((byte) SSH_FXP_DATA);
+            buf.putInt(id);
+            int lenPos = buf.wpos();
+            buf.putInt(0);
+
+            int startPos = buf.wpos();
+            int len = doRead(id, handle, offset, readLen, buf.array(), startPos);
+            if (len < 0) {
+                throw new EOFException("Unable to read " + readLen + " bytes from offset=" + offset + " of " + handle);
+            }
+            buf.wpos(startPos + len);
+            BufferUtils.updateLengthPlaceholder(buf, lenPos, len);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+        
+        send(buf);
+    }
+
+    protected int doRead(int id, String handle, long offset, int length, byte[] data, int doff) throws IOException {
+        Handle h = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_READ (handle={}[{}], offset={}, length={})",
+                      handle, h, Long.valueOf(offset), Integer.valueOf(length));
+        }
+        ValidateUtils.checkTrue(length > 0, "Invalid read length: %d", length);
+        FileHandle fh = validateHandle(handle, h, FileHandle.class);
+        
+        return fh.read(data, doff, length, offset);
     }
 
     protected void doClose(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
-        Handle h = handles.remove(handle);
-        log.debug("Received SSH_FXP_CLOSE (handle={}[{}])", handle, h);
         try {
-            if (h == null) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
-            } else {
-                h.close();
-                sendStatus(id, SSH_FX_OK, "", "");
-            }
-        } catch (IOException e) {
+            doClose(id, handle);
+        } catch (IOException | RuntimeException e) {
             sendStatus(id, e);
+            return;
         }
+
+        sendStatus(id, SSH_FX_OK, "", "");
     }
 
-    protected void doOpen(Buffer buffer, int id) throws IOException {
-        int curHandleCount = handles.size();
-        int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, DEFAULT_MAX_OPEN_HANDLES);
-        if (curHandleCount > maxHandleCount) {
-            sendStatus(id, SSH_FX_FAILURE, "Too many open handles: current=" + curHandleCount + ", max.=" + maxHandleCount);
-            return;
-        }
+    protected void doClose(int id, String handle) throws IOException {
+        Handle h = handles.remove(handle);
+        log.debug("Received SSH_FXP_CLOSE (handle={}[{}])", handle, h);
+        validateHandle(handle, h, Handle.class).close();
+    }
 
+    protected void doOpen(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
-
         /*
          * Be consistent with FileChannel#open - if no mode specified then READ is assumed
          */
@@ -1554,19 +1706,43 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                 }
             }
         }
+
         Map<String, Object> attrs = readAttrs(buffer);
+        String handle;
+        try {
+            handle = doOpen(id, path, pflags, access, attrs);
+        } catch (IOException | RuntimeException e) {
+            sendStatus(id, e);
+            return;
+        }
+
+        sendHandle(id, handle);
+    }
+
+    /**
+     * @param id Request id
+     * @param path Path
+     * @param pflags Open mode flags - see {@code SSH_FXF_XXX} flags
+     * @param access Access mode flags - see {@code ACE4_XXX} flags
+     * @param attrs Requested attributes
+     * @return The assigned (opaque) handle
+     * @throws IOException if failed to execute
+     */
+    protected String doOpen(int id, String path, int pflags, int access, Map<String, Object> attrs) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Received SSH_FXP_OPEN (path={}, access=0x{}, pflags=0x{}, attrs={})",
                       path, Integer.toHexString(access), Integer.toHexString(pflags), attrs);
         }
-        try {
-            Path file = resolveFile(path);
-            String handle = generateFileHandle(file);
-            handles.put(handle, new FileHandle(file, pflags, access, attrs));
-            sendHandle(id, handle);
-        } catch (IOException e) {
-            sendStatus(id, e);
+        int curHandleCount = handles.size();
+        int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, DEFAULT_MAX_OPEN_HANDLES);
+        if (curHandleCount > maxHandleCount) {
+            throw new IllegalStateException("Too many open handles: current=" + curHandleCount + ", max.=" + maxHandleCount);
         }
+        
+        Path file = resolveFile(path);
+        String handle = generateFileHandle(file);
+        handles.put(handle, new FileHandle(file, pflags, access, attrs));
+        return handle;
     }
 
     // we stringify our handles and treat them as such on decoding as well as it is easier to use as a map key
@@ -1768,23 +1944,32 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
     }
 
     protected void sendHandle(int id, String handle) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
+        sendHandle(new ByteArrayBuffer(GenericUtils.length(handle) + Long.SIZE), id, handle);
+    }
+
+    protected void sendHandle(Buffer buffer, int id, String handle) throws IOException {
         buffer.putByte((byte) SSH_FXP_HANDLE);
         buffer.putInt(id);
         buffer.putString(handle);
         send(buffer);
     }
 
-    protected void sendAttrs(int id, Path file, int flags, boolean followLinks) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
+    protected void sendAttrs(int id, Map<String,?> attributes) throws IOException {
+        sendAttrs(new ByteArrayBuffer(), id, attributes);
+    }
+
+    protected void sendAttrs(Buffer buffer, int id, Map<String,?> attributes) throws IOException {
         buffer.putByte((byte) SSH_FXP_ATTRS);
         buffer.putInt(id);
-        writeAttrs(buffer, file, flags, followLinks);
+        writeAttrs(buffer, attributes);
         send(buffer);
     }
 
-    protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
+    protected void sendPath(int id, Path f, Map<String,?> attrs) throws IOException {
+        sendPath(new ByteArrayBuffer(), id, f, attrs);
+    }
+
+    protected void sendPath(Buffer buffer, int id, Path f, Map<String,?> attrs) throws IOException {
         buffer.putByte((byte) SSH_FXP_NAME);
         buffer.putInt(id);
         buffer.putInt(1);
@@ -1812,7 +1997,11 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
     }
 
     protected void sendLink(int id, String link) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
+        int linkSize = GenericUtils.length(link);
+        sendLink(new ByteArrayBuffer((2 * linkSize) + Long.SIZE), id , link);
+    }
+
+    protected void sendLink(Buffer buffer, int id, String link) throws IOException {
         buffer.putByte((byte) SSH_FXP_NAME);
         buffer.putInt(id);
         buffer.putInt(1);
@@ -1823,54 +2012,62 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         send(buffer);
     }
 
-    protected void sendDirEntries(int id, DirectoryHandle files) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putByte((byte) SSH_FXP_NAME);
-        buffer.putInt(id);
-        int wpos = buffer.wpos();
-        buffer.putInt(0);
-
-        int nb = 0, maxSize = FactoryManagerUtils.getIntProperty(session, MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH);
-        while((files.isSendDot() || files.isSendDotDot() || files.hasNext()) && (buffer.wpos() < maxSize)) {
-            Path f;
-            String  shortName;
-            if (files.isSendDot()) {
-                f = files.getFile();
-                shortName = ".";
-                files.markDotSent();    // do not send it again
-            } else if (files.isSendDotDot()) {
-                f = files.getFile().getParent();
-                shortName = "..";
-                files.markDotDotSent(); // do not send it again
+    /**
+     * @param id Request id
+     * @param dir The {@link DirectoryHandle}
+     * @param buffer The {@link Buffer} to write the results
+     * @param maxSize Max. buffer size
+     * @return Number of written entries
+     * @throws IOException If failed to generate an entry
+     */
+    protected int doReadDir(int id, DirectoryHandle dir, Buffer buffer, int maxSize) throws IOException {
+        int nb = 0;
+        while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && (buffer.wpos() < maxSize)) {
+            if (dir.isSendDot()) {
+                writeDirEntry(id, dir, buffer, nb, dir.getFile(), ".");
+                dir.markDotSent();    // do not send it again
+            } else if (dir.isSendDotDot()) {
+                writeDirEntry(id, dir, buffer, nb, dir.getFile().getParent(), "..");
+                dir.markDotDotSent(); // do not send it again
             } else {
-                f = files.next();
-                shortName = getShortName(f);
+                Path f = dir.next();
+                writeDirEntry(id, dir, buffer, nb, f, getShortName(f));
             }
 
-            buffer.putString(shortName);
-            if (version == SFTP_V3) {
-                String  longName = getLongName(f);
-                buffer.putString(longName);
-                if (log.isTraceEnabled()) {
-                    log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName + " [" + longName + "]");
-                }
-            } else {
-                if (log.isTraceEnabled()) {
-                    log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName);
-                }
-            }
-            writeAttrs(buffer, f, SSH_FILEXFER_ATTR_ALL, false);
             nb++;
         }
+        
+        return nb;
+    }
 
-        int oldpos = buffer.wpos();
-        buffer.wpos(wpos);
-        buffer.putInt(nb);
-        buffer.wpos(oldpos);
-        send(buffer);
+    /**
+     * @param id Request id
+     * @param dir The {@link DirectoryHandle}
+     * @param buffer The {@link Buffer} to write the results
+     * @param index Zero-based index of the entry to be written
+     * @param f The entry {@link Path}
+     * @param shortName The entry short name
+     * @throws IOException If failed to generate the entry data
+     */
+    protected void writeDirEntry(int id, DirectoryHandle dir, Buffer buffer, int index, Path f, String shortName) throws IOException {
+        buffer.putString(shortName);
+        if (version == SFTP_V3) {
+            String  longName = getLongName(f);
+            buffer.putString(longName);
+            if (log.isTraceEnabled()) {
+                log.trace("writeDirEntry(id=" + id + ")[" + index + "] - " + shortName + " [" + longName + "]");
+            }
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("writeDirEntry(id=" + id + ")[" + index + "] - " + shortName);
+            }
+        }
+        
+        Map<String,?> attrs = resolveFileAttributes(f, SSH_FILEXFER_ATTR_ALL, false);
+        writeAttrs(buffer, attrs);
     }
 
-    private String getLongName(Path f) throws IOException {
+    protected String getLongName(Path f) throws IOException {
         return getLongName(f, true);
     }
 
@@ -1884,10 +2081,10 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return getLongName(f, attributes);
     }
 
-    private String getLongName(Path f, Map<String, Object> attributes) throws IOException {
+    private String getLongName(Path f, Map<String,?> attributes) throws IOException {
         String username;
         if (attributes.containsKey("owner")) {
-            username = attributes.get("owner").toString();
+            username = Objects.toString(attributes.get("owner"));
         } else {
             username = "owner";
         }
@@ -1900,7 +2097,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
         String group;
         if (attributes.containsKey("group")) {
-            group = attributes.get("group").toString();
+            group = Objects.toString(attributes.get("group"));
         } else {
             group = "group";
         }
@@ -2014,22 +2211,19 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return pf;
     }
 
-    protected void writeAttrs(Buffer buffer, Path file, int flags, boolean followLinks) throws IOException {
-        LinkOption[]    options = IoUtils.getLinkOptions(followLinks);
-        Boolean         status = IoUtils.checkFileExists(file, options);
-        Map<String, Object> attributes;
+    protected Map<String, Object> resolveFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
+        LinkOption[] options = IoUtils.getLinkOptions(followLinks);
+        Boolean      status = IoUtils.checkFileExists(file, options);
         if (status == null) {
-            attributes = handleUnknownStatusFileAttributes(file, flags, followLinks);
+            return handleUnknownStatusFileAttributes(file, flags, followLinks);
         } else if (!status.booleanValue()) {
             throw new FileNotFoundException(file.toString());
         } else {
-            attributes = getAttributes(file, flags, followLinks);
+            return getAttributes(file, flags, followLinks);
         }
-
-        writeAttrs(buffer, attributes);
     }
 
-    protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
+    protected void writeAttrs(Buffer buffer, Map<String,?> attributes) throws IOException {
         boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
         boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
         boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
@@ -2193,7 +2387,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return Collections.emptyMap();
     }
 
-    protected void setAttributes(Path file, Map<String, Object>  attributes) throws IOException {
+    protected void setAttributes(Path file, Map<String,?>  attributes) throws IOException {
         Set<String> unsupported = new HashSet<>();
         for (String attribute : attributes.keySet()) {
             String view = null;
@@ -2559,24 +2753,54 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                 .build();
     }
 
+    /**
+     * @param handle The original handle id
+     * @param h The resolved {@link Handle} instance
+     * @param type The expected handle type
+     * @return The cast type
+     * @throws FileNotFoundException If the handle instance is {@code null}
+     * @throws InvalidHandleException If the handle instance is not of the expected type
+     */
+    protected <H extends Handle> H validateHandle(String handle, Handle h, Class<H> type) throws IOException {
+        if (h == null) {
+            throw new FileNotFoundException("No such current handle: " + handle);
+        }
+        
+        Class<?> t = h.getClass();
+        if (!type.isAssignableFrom(t)) {
+            throw new InvalidHandleException(handle, h, type);
+        }
+        
+        return type.cast(h);
+    }
+
     protected void sendStatus(int id, Exception e) throws IOException {
-        int substatus;
+        int substatus = resolveSubstatus(e);
+        sendStatus(id, substatus, e.toString());
+    }
+
+    protected int resolveSubstatus(Exception e) {
         if ((e instanceof NoSuchFileException) || (e instanceof FileNotFoundException)) {
-            substatus = SSH_FX_NO_SUCH_FILE;
+            return SSH_FX_NO_SUCH_FILE;
+        } else if (e instanceof InvalidHandleException) {
+            return SSH_FX_INVALID_HANDLE;
         } else if (e instanceof FileAlreadyExistsException) {
-            substatus = SSH_FX_FILE_ALREADY_EXISTS;
+            return SSH_FX_FILE_ALREADY_EXISTS;
         } else if (e instanceof DirectoryNotEmptyException) {
-            substatus = SSH_FX_DIR_NOT_EMPTY;
+            return SSH_FX_DIR_NOT_EMPTY;
+        } else if (e instanceof NotDirectoryException) {
+            return SSH_FX_NOT_A_DIRECTORY;
         } else if (e instanceof AccessDeniedException) {
-            substatus = SSH_FX_PERMISSION_DENIED;
+            return SSH_FX_PERMISSION_DENIED;
+        } else if (e instanceof EOFException) {
+            return SSH_FX_EOF;
         } else if (e instanceof OverlappingFileLockException) {
-            substatus = SSH_FX_LOCK_CONFLICT;
+            return SSH_FX_LOCK_CONFLICT;
         } else if (e instanceof UnsupportedOperationException) {
-            substatus = SSH_FX_OP_UNSUPPORTED;
+            return SSH_FX_OP_UNSUPPORTED;
         } else {
-            substatus = SSH_FX_FAILURE;
+            return SSH_FX_FAILURE;
         }
-        sendStatus(id, substatus, e.toString());
     }
 
     protected void sendStatus(int id, int substatus, String msg) throws IOException {
@@ -2638,7 +2862,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             try {
                 fileSystem.close();
             } catch (UnsupportedOperationException e) {
-                // Ignore
+                if (log.isDebugEnabled()) {
+                    log.debug("Closing the file system is not supported");
+                }
             } catch (IOException e) {
                 log.debug("Error closing FileSystem", e);
             }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f60fb919/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index 8844fff..6b41093 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -211,7 +211,7 @@ public class SftpTest extends BaseTestSupport {
                         assertTrue("Handle not marked as open for file=" + file, h.isOpen());
                     }
             
-                    byte[] d = "0123456789\n".getBytes();
+                    byte[] d = "0123456789\n".getBytes(StandardCharsets.UTF_8);
                     try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
                         sftp.write(h, 0, d, 0, d.length);
                         sftp.write(h, d.length, d, 0, d.length);
@@ -222,7 +222,9 @@ public class SftpTest extends BaseTestSupport {
                     }
 
                     try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
-                        sftp.write(h, 3, "-".getBytes(), 0, 1);
+                        byte[] overwrite = "-".getBytes(StandardCharsets.UTF_8);
+                        sftp.write(h, 3L, overwrite, 0, 1);
+                        d[3] = overwrite[0];
                     }
 
                     try(SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) {
@@ -238,8 +240,10 @@ public class SftpTest extends BaseTestSupport {
             
                     byte[] buf = new byte[3];
                     try(SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) {
-                        int l = sftp.read(h, 2l, buf, 0, 3);
-                        assertEquals("Mismatched read data", "2-4", new String(buf, 0, l));
+                        int l = sftp.read(h, 2L, buf, 0, buf.length);
+                        String expected = new String(d, 2, l, StandardCharsets.UTF_8);
+                        String actual = new String(buf, 0, l, StandardCharsets.UTF_8);
+                        assertEquals("Mismatched read data", expected, actual);
                     }
                 }
             } finally {