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/01 12:57:42 UTC

[01/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Repository: mina-sshd
Updated Branches:
  refs/heads/master 82791d41b -> a6e2bf9e4


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/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
new file mode 100644
index 0000000..053aad9
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -0,0 +1,652 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRUSR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWUSR;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Random;
+import java.util.Vector;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.JSchLogger;
+import org.apache.sshd.util.SimpleUserInfo;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.SftpException;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpTest extends BaseTestSupport {
+
+    private SshServer sshd;
+    private int port;
+    private com.jcraft.jsch.Session session;
+    private final FileSystemFactory fileSystemFactory;
+
+    public SftpTest() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+        fileSystemFactory = new FileSystemFactory() {
+            @Override
+            public FileSystem createFileSystem(Session session) throws IOException {
+                return fileSystem;
+            }
+        };
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+        sshd.setCommandFactory(new ScpCommandFactory());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd.setFileSystemFactory(fileSystemFactory);
+        sshd.start();
+        port = sshd.getPort();
+
+        JSchLogger.init();
+        JSch sch = new JSch();
+        session = sch.getSession("sshd", "localhost", port);
+        session.setUserInfo(new SimpleUserInfo("sshd"));
+        session.connect();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (session != null) {
+            session.disconnect();
+        }
+        
+        if (sshd != null) {
+            sshd.stop(true);
+        }
+    }
+
+    @Test
+    @Ignore
+    public void testExternal() throws Exception {
+        System.out.println("SFTP subsystem available on port " + port);
+        Thread.sleep(5 * 60000);
+    }
+
+    @Test
+    public void testOpen() throws Exception {
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+            
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+                Path clientFolder = lclSftp.resolve("client");
+                Path testFile = clientFolder.resolve(getCurrentTestName() + ".txt");
+                String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
+
+                File javaFile = testFile.toFile();
+                assertHierarchyTargetFolderExists(javaFile.getParentFile());
+                javaFile.createNewFile();
+                javaFile.setWritable(false, false);
+                javaFile.setReadable(false, false);
+        
+                try (SftpClient sftp = session.createSftpClient()) {
+                    boolean	isWindows = OsUtils.isWin32();
+            
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
+                        // NOTE: on Windows files are always readable
+                        // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
+                        assertTrue("Empty read should have failed on " + file, isWindows);
+                    } catch (IOException e) {
+                        if (isWindows) {
+                            throw e;
+                        }
+                    }
+            
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+                        fail("Empty write should have failed on " + file);
+                    } catch (IOException e) {
+                        // ok
+                    }
+    
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) {
+                        // NOTE: on Windows files are always readable
+                        assertTrue("Empty truncate should have failed on " + file, isWindows);
+                    } catch (IOException e) {
+                        // ok
+                    }
+    
+                    // NOTE: on Windows files are always readable
+                    int	perms=sftp.stat(file).perms;
+                    int	permsMask=S_IWUSR | (isWindows ? 0 : S_IRUSR);
+                    assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, (perms & permsMask));
+            
+                    javaFile.setWritable(true, false);
+            
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) {
+                        // OK should succeed
+                        assertTrue("Handle not marked as open for file=" + file, h.isOpen());
+                    }
+            
+                    byte[] d = "0123456789\n".getBytes();
+                    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);
+                    }
+
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+                        sftp.write(h, d.length * 2, d, 0, d.length);
+                    }
+
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+                        sftp.write(h, 3, "-".getBytes(), 0, 1);
+                    }
+
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
+                        // NOTE: on Windows files are always readable
+                        assertTrue("Data read should have failed on " + file, isWindows);
+                    } catch (IOException e) {
+                        if (isWindows) {
+                            throw e;
+                        }
+                    }
+            
+                    javaFile.setReadable(true, false);
+            
+                    byte[] buf = new byte[3];
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
+                        int l = sftp.read(h, 2l, buf, 0, 3);
+                        assertEquals("Mismatched read data", "2-4", new String(buf, 0, l));
+                    }
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testClient() throws Exception {
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                Path targetPath = detectTargetFolder().toPath();
+                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+                Utils.deleteRecursive(lclSftp);
+                Files.createDirectories(lclSftp);
+
+                Path parentPath = targetPath.getParent();
+                Path clientFolder = lclSftp.resolve("client");
+                String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
+                String file = dir + "/" + getCurrentTestName() + ".txt";
+
+                try (SftpClient sftp = session.createSftpClient()) {
+                    sftp.mkdir(dir);
+            
+                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+                        byte[] d = "0123456789\n".getBytes();
+                        sftp.write(h, 0, d, 0, d.length);
+                        sftp.write(h, d.length, d, 0, d.length);
+                
+                        SftpClient.Attributes attrs = sftp.stat(h);
+                        assertNotNull("No handle attributes", attrs);
+                    }            
+            
+                    try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
+                        SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
+                        assertNotNull("No dir entries", dirEntries);
+                        assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
+                        assertNull("Unexpected entry read", sftp.readDir(h));
+                    }
+            
+                    sftp.remove(file);
+    
+                    byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE];
+                    new Random(System.currentTimeMillis()).nextBytes(workBuf);
+                    try (OutputStream os = sftp.write(file)) {
+                        os.write(workBuf);
+                    }
+            
+                    try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) {
+                        int readLen = is.read(workBuf);
+                        assertEquals("Mismatched read data length", workBuf.length, readLen);
+        
+                        int i = is.read();
+                        assertEquals("Unexpected read past EOF", -1, i);
+                    }
+        
+                    SftpClient.Attributes attributes = sftp.stat(file);
+                    assertTrue("Test file not detected as regular", attributes.isRegularFile());
+            
+                    attributes = sftp.stat(dir);
+                    assertTrue("Test directory not reported as such", attributes.isDirectory());
+            
+                    int nb = 0;
+                    for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
+                        assertNotNull("Unexpected null entry", entry);
+                        nb++;
+                    }
+                    assertEquals("Mismatched read dir entries", 1, nb);
+            
+                    sftp.remove(file);
+            
+                    sftp.rmdir(dir);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    /**
+     * this test is meant to test out write's logic, to ensure that internal chunking (based on Buffer.MAX_LEN) is
+     * functioning properly. To do this, we write a variety of file sizes, both smaller and larger than Buffer.MAX_LEN.
+     * in addition, this test ensures that improper arguments passed in get caught with an IllegalArgumentException
+     * @throws Exception upon any uncaught exception or failure
+     */
+    @Test
+    public void testWriteChunking() throws Exception {
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+            
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+        
+                Path targetPath = detectTargetFolder().toPath();
+                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+                Utils.deleteRecursive(lclSftp);
+                Files.createDirectories(lclSftp);
+
+                Path parentPath = targetPath.getParent();
+                Path clientFolder = lclSftp.resolve("client");
+                String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
+        
+                try(SftpClient sftp = session.createSftpClient()) {
+                    sftp.mkdir(dir);
+            
+                    uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN - 1, "bufferMaxLenMinusOneFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN, "bufferMaxLenFile.txt");
+                    // were chunking not implemented, these would fail. these sizes should invoke our internal chunking mechanism
+                    uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN + 1, "bufferMaxLenPlusOneFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, (int)(1.5 * ByteArrayBuffer.MAX_LEN), "1point5BufferMaxLenFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) - 1, "2TimesBufferMaxLenMinusOneFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, 2 * ByteArrayBuffer.MAX_LEN, "2TimesBufferMaxLenFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) + 1, "2TimesBufferMaxLenPlusOneFile.txt");
+                    uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt");
+            
+                    // test erroneous calls that check for negative values
+                    Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid");
+                    testInvalidParams(sftp, invalidPath, Utils.resolveRelativeRemotePath(parentPath, invalidPath));
+            
+                    // cleanup
+                    sftp.rmdir(dir);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    private void testInvalidParams(SftpClient sftp, Path file, String filePath) throws Exception {
+        // generate random file and upload it
+        String randomData = randomString(5);
+        byte[] randomBytes = randomData.getBytes();
+        try(SftpClient.CloseableHandle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+            try {
+                sftp.write(handle, -1, randomBytes, 0, 0);
+                fail("should not have been able to write file with invalid file offset for " + filePath);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            try {
+                sftp.write(handle, 0, randomBytes, -1, 0);
+                fail("should not have been able to write file with invalid source offset for " + filePath);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            try {
+                sftp.write(handle, 0, randomBytes, 0, -1);
+                fail("should not have been able to write file with invalid length for " + filePath);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            try {
+                sftp.write(handle, 0, randomBytes, 0, randomBytes.length + 1);
+                fail("should not have been able to write file with length bigger than array itself (no offset) for " + filePath);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            try {
+                sftp.write(handle, 0, randomBytes, randomBytes.length, 1);
+                fail("should not have been able to write file with length bigger than array itself (with offset) for " + filePath);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+
+        sftp.remove(filePath);
+        assertFalse("File should not be there: " + file.toString(), Files.exists(file));
+    }
+
+    private void uploadAndVerifyFile(SftpClient sftp, Path clientFolder, String remoteDir, int size, String filename) throws Exception {
+        // generate random file and upload it
+        String remotePath = remoteDir + "/" + filename;
+        String randomData = randomString(size);
+        try(SftpClient.CloseableHandle handle = sftp.open(remotePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+            sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length());
+        }
+
+        // verify results
+        Path resultPath = clientFolder.resolve(filename);
+        assertTrue("File should exist on disk: " + resultPath, Files.exists(resultPath));
+        assertTrue("Mismatched file contents: " + resultPath, randomData.equals(readFile(remotePath)));
+
+        // cleanup
+        sftp.remove(remotePath);
+        assertFalse("File should have been removed: " + resultPath, Files.exists(resultPath));
+    }
+
+    @Test
+    public void testSftp() throws Exception {
+        String d = getCurrentTestName() + "\n";
+
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(lclSftp);
+        Files.createDirectories(lclSftp);
+
+        Path target = lclSftp.resolve(getCurrentTestName() + ".txt");
+        String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), target);
+
+        final int NUM_ITERATIONS=10;
+        StringBuilder   sb = new StringBuilder(d.length() * NUM_ITERATIONS * NUM_ITERATIONS);
+        for (int j = 1; j <= NUM_ITERATIONS; j++) {
+            if (sb.length() > 0) {
+                sb.setLength(0);
+            }
+
+            for (int i = 0; i < j; i++) {
+                sb.append(d);
+            }
+
+            sendFile(remotePath, sb.toString());
+            assertFileLength(target, sb.length(), 5000);
+            Files.delete(target);
+        }
+    }
+
+    @Test
+    public void testReadWriteWithOffset() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(lclSftp);
+        Files.createDirectories(lclSftp);
+
+        Path localPath = lclSftp.resolve(getCurrentTestName() + ".txt");
+        String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), localPath);
+        String data = getCurrentTestName();
+        String extraData = "@" + getClass().getSimpleName();
+        int appendOffset = -5;
+
+        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        c.connect();
+        try {
+            c.put(new ByteArrayInputStream(data.getBytes()), remotePath);
+    
+            assertTrue("Remote file not created after initial write: " + localPath, Files.exists(localPath));
+            assertEquals("Mismatched data read from " + remotePath, data, readFile(remotePath));
+    
+            try(OutputStream os = c.put(remotePath, null, ChannelSftp.APPEND, appendOffset)) {
+                os.write(extraData.getBytes());
+            }
+        } finally {
+            c.disconnect();
+        }
+
+        assertTrue("Remote file not created after data update: " + localPath, Files.exists(localPath));
+        
+        String expected = data.substring(0, data.length() + appendOffset) + extraData;
+        String actual = readFile(remotePath);
+        assertEquals("Mismatched final file data in " + remotePath, expected, actual);
+    }
+
+    @Test
+    public void testReadDir() throws Exception {
+        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        c.connect();
+        try {
+            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
+            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
+            String path = new File(base.relativize(url).getPath()).getParent() + "/";
+            path = path.replace('\\', '/');
+            Vector<?> res = c.ls(path);
+            for (Object f : res) {
+                System.out.println(f.toString());
+            }
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    @Test
+    public void testRealPath() throws Exception {
+        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        c.connect();
+
+        try {
+            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
+            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
+            String path = new File(base.relativize(url).getPath()).getParent() + "/";
+            path = path.replace('\\', '/');
+            String real = c.realpath(path);
+            System.out.println(real);
+            try {
+                real = c.realpath(path + "/foobar");
+                System.out.println(real);
+                fail("Expected SftpException");
+            } catch (SftpException e) {
+                // ok
+            }
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    @Test
+    public void testRename() throws Exception {
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+            
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+        
+                Path targetPath = detectTargetFolder().toPath();
+                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+                Utils.deleteRecursive(lclSftp);
+                Files.createDirectories(lclSftp);
+
+                Path parentPath = targetPath.getParent();
+                Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
+        
+                try(SftpClient sftp = session.createSftpClient()) {
+                    Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt");
+                    String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1);
+                    try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+                        os.write((getCurrentTestName() + "\n").getBytes());
+                    }
+
+                    Path file2 = clientFolder.resolve(getCurrentTestName() + "-2.txt");
+                    String file2Path = Utils.resolveRelativeRemotePath(parentPath, file2);
+                    Path file3 = clientFolder.resolve(getCurrentTestName() + "-3.txt");
+                    String file3Path = Utils.resolveRelativeRemotePath(parentPath, file3);
+                    try {
+                        sftp.rename(file2Path, file3Path);
+                        fail("Unxpected rename success of " + file2Path + " => " + file3Path);
+                    } catch (org.apache.sshd.client.SftpException e) {
+                        assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus());
+                    }
+            
+                    try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+                        os.write("H".getBytes());
+                    }
+            
+                    try {
+                        sftp.rename(file1Path, file2Path);
+                        fail("Unxpected rename success of " + file1Path + " => " + file2Path);
+                    } catch (org.apache.sshd.client.SftpException e) {
+                        assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
+                    }
+
+                    sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testCreateSymbolicLink() throws Exception {
+        // Do not execute on windows as the file system does not support symlinks
+        Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
+
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(lclSftp);
+        Files.createDirectories(lclSftp);
+
+        Path parentPath = targetPath.getParent();
+        Path sourcePath = lclSftp.resolve(getCurrentTestName() + ".txt");
+        String remSrcPath = Utils.resolveRelativeRemotePath(parentPath, sourcePath);
+        Path linkPath = lclSftp.resolve("link-" + sourcePath.getFileName());
+        String remLinkPath = Utils.resolveRelativeRemotePath(parentPath, linkPath);
+
+        String data = getCurrentTestName();
+        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        c.connect();
+        try {
+            c.put(new ByteArrayInputStream(data.getBytes()), remSrcPath);
+    
+            assertTrue("Source file not created: " + sourcePath, Files.exists(sourcePath));
+            assertEquals("Mismatched stored data in " + remSrcPath, data, readFile(remSrcPath));
+    
+            c.symlink(remSrcPath, remLinkPath);
+    
+            assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath));
+            assertEquals("Mismatche link data in " + remLinkPath, data, readFile(remLinkPath));
+    
+            String str1 = c.readlink(remLinkPath);
+            String str2 = c.realpath(remSrcPath);
+            assertEquals("Mismatched link vs. real path", str1, str2);
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected String readFile(String path) throws Exception {
+        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        c.connect();
+        
+        try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            InputStream is = c.get(path)) {
+
+            byte[] buffer = new byte[256];
+            int count;
+            while (-1 != (count = is.read(buffer))) {
+                bos.write(buffer, 0, count);
+            }
+
+            return bos.toString();
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected void sendFile(String path, String data) throws Exception {
+        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        c.connect();
+        try {
+            c.put(new ByteArrayInputStream(data.getBytes()), path);
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    private String randomString(int size) {
+        StringBuilder sb = new StringBuilder(size);
+        for (int i = 0; i < size; i++) {
+            sb.append((char) ((i % 10) + '0'));
+        }
+        return sb.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
index f9d6e17..43af998 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
@@ -54,7 +54,7 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.deprecated.ClientUserAuthServiceOld;
 import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.apache.sshd.util.BaseTestSupport;
 import org.apache.sshd.util.BogusPasswordAuthenticator;
 import org.apache.sshd.util.EchoShellFactory;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java b/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java
deleted file mode 100644
index 8807fae..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.server.sftp;
-
-import java.util.concurrent.ExecutorService;
-
-import org.apache.sshd.util.BaseTestSupport;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.mockito.Mockito;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpSubsystemFactoryTest extends BaseTestSupport {
-    public SftpSubsystemFactoryTest() {
-        super();
-    }
-
-    /**
-     * Make sure that the builder returns a factory with the default values
-     * if no {@code withXXX} method is invoked
-     */
-    @Test
-    public void testBuilderDefaultFactoryValues() {
-        SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder().build();
-        assertNull("Mismatched executor", factory.getExecutorService());
-        assertFalse("Mismatched shutdown state", factory.isShutdownOnExit());
-        assertSame("Mismatched unsupported attribute policy", SftpSubsystemFactory.DEFAULT_POLICY, factory.getUnsupportedAttributePolicy());
-    }
-
-    /**
-     * Make sure that the builder initializes correctly the built factory
-     */
-    @Test
-    public void testBuilderCorrectlyInitializesFactory() {
-        SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
-        ExecutorService service = dummyExecutor();
-        SftpSubsystemFactory factory = builder.withExecutorService(service)
-                .withShutdownOnExit(true)
-                .build();
-        assertSame("Mismatched executor", service, factory.getExecutorService());
-        assertTrue("Mismatched shutdown state", factory.isShutdownOnExit());
-
-        for (UnsupportedAttributePolicy policy : UnsupportedAttributePolicy.VALUES) {
-            SftpSubsystemFactory actual = builder.withUnsupportedAttributePolicy(policy).build();
-            assertSame("Mismatched unsupported attribute policy", policy, actual.getUnsupportedAttributePolicy());
-        }
-    }
-
-    /**
-     * <UL>
-     * <LI>
-     * Make sure the builder returns new instances on every call to
-     * {@link SftpSubsystemFactory.Builder#build()} method
-     * </LI>
-     * <p/>
-     * <LI>
-     * Make sure values are preserved between successive invocations
-     * of the {@link SftpSubsystemFactory.Builder#build()} method
-     * </LI>
-     * </UL
-     */
-    @Test
-    public void testBuilderUniqueInstance() {
-        SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
-        SftpSubsystemFactory f1 = builder.withExecutorService(dummyExecutor()).build();
-        SftpSubsystemFactory f2 = builder.build();
-        assertNotSame("No new instance built", f1, f2);
-        assertSame("Mismatched executors", f1.getExecutorService(), f2.getExecutorService());
-
-        SftpSubsystemFactory f3 = builder.withExecutorService(dummyExecutor()).build();
-        assertNotSame("Executor service not changed", f1.getExecutorService(), f3.getExecutorService());
-    }
-
-    private static ExecutorService dummyExecutor() {
-        return Mockito.mock(ExecutorService.class);
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java b/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
new file mode 100644
index 0000000..7a251ea
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.UnsupportedAttributePolicy;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpSubsystemFactoryTest extends BaseTestSupport {
+    public SftpSubsystemFactoryTest() {
+        super();
+    }
+
+    /**
+     * Make sure that the builder returns a factory with the default values
+     * if no {@code withXXX} method is invoked
+     */
+    @Test
+    public void testBuilderDefaultFactoryValues() {
+        SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder().build();
+        assertNull("Mismatched executor", factory.getExecutorService());
+        assertFalse("Mismatched shutdown state", factory.isShutdownOnExit());
+        assertSame("Mismatched unsupported attribute policy", SftpSubsystemFactory.DEFAULT_POLICY, factory.getUnsupportedAttributePolicy());
+    }
+
+    /**
+     * Make sure that the builder initializes correctly the built factory
+     */
+    @Test
+    public void testBuilderCorrectlyInitializesFactory() {
+        SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
+        ExecutorService service = dummyExecutor();
+        SftpSubsystemFactory factory = builder.withExecutorService(service)
+                .withShutdownOnExit(true)
+                .build();
+        assertSame("Mismatched executor", service, factory.getExecutorService());
+        assertTrue("Mismatched shutdown state", factory.isShutdownOnExit());
+
+        for (UnsupportedAttributePolicy policy : UnsupportedAttributePolicy.VALUES) {
+            SftpSubsystemFactory actual = builder.withUnsupportedAttributePolicy(policy).build();
+            assertSame("Mismatched unsupported attribute policy", policy, actual.getUnsupportedAttributePolicy());
+        }
+    }
+
+    /**
+     * <UL>
+     * <LI>
+     * Make sure the builder returns new instances on every call to
+     * {@link SftpSubsystemFactory.Builder#build()} method
+     * </LI>
+     * <p/>
+     * <LI>
+     * Make sure values are preserved between successive invocations
+     * of the {@link SftpSubsystemFactory.Builder#build()} method
+     * </LI>
+     * </UL
+     */
+    @Test
+    public void testBuilderUniqueInstance() {
+        SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
+        SftpSubsystemFactory f1 = builder.withExecutorService(dummyExecutor()).build();
+        SftpSubsystemFactory f2 = builder.build();
+        assertNotSame("No new instance built", f1, f2);
+        assertSame("Mismatched executors", f1.getExecutorService(), f2.getExecutorService());
+
+        SftpSubsystemFactory f3 = builder.withExecutorService(dummyExecutor()).build();
+        assertNotSame("Executor service not changed", f1.getExecutorService(), f3.getExecutorService());
+    }
+
+    private static ExecutorService dummyExecutor() {
+        return Mockito.mock(ExecutorService.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
index 210e35c..7ba2362 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
@@ -28,7 +28,7 @@ import org.apache.sshd.git.util.EchoShellFactory;
 import org.apache.sshd.git.util.Utils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.SshSessionFactory;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
index ba186fa..b1d45e9 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
@@ -32,7 +32,7 @@ import org.apache.sshd.git.util.EchoShellFactory;
 import org.apache.sshd.git.util.Utils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.eclipse.jgit.api.Git;
 import org.junit.FixMethodOrder;
 import org.junit.Test;


[05/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
deleted file mode 100644
index d696a4f..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
+++ /dev/null
@@ -1,2280 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.server.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.*;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclEntryFlag;
-import java.nio.file.attribute.AclEntryPermission;
-import java.nio.file.attribute.AclEntryType;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.file.FileSystemAware;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.SelectorUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-import org.apache.sshd.common.util.threads.ThreadUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
-import org.apache.sshd.server.session.ServerSession;
-
-/**
- * SFTP subsystem
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpSubsystem extends AbstractLoggingBean implements Command, Runnable, SessionAware, FileSystemAware {
-
-    /**
-     * Properties key for the maximum of available open handles per session.
-     */
-    public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
-
-    /**
-     * Force the use of a given sftp version
-     */
-    public static final String SFTP_VERSION = "sftp-version";
-
-    public static final int LOWER_SFTP_IMPL = SFTP_V3; // Working implementation from v3
-    public static final int HIGHER_SFTP_IMPL = SFTP_V6; //  .. up to
-    public static final String ALL_SFTP_IMPL;
-    public static final int  MAX_PACKET_LENGTH = 1024 * 16;
-
-    static {
-        StringBuilder sb = new StringBuilder(2 * (1 + (HIGHER_SFTP_IMPL - LOWER_SFTP_IMPL)));
-        for (int v = LOWER_SFTP_IMPL; v <= HIGHER_SFTP_IMPL; v++) {
-            if (sb.length() > 0) {
-                sb.append(',');
-            }
-            sb.append(v);
-        }
-        ALL_SFTP_IMPL = sb.toString();
-    }
-
-    private ExitCallback callback;
-    private InputStream in;
-    private OutputStream out;
-    private OutputStream err;
-    private Environment env;
-    private ServerSession session;
-    private boolean closed = false;
-	private ExecutorService executors;
-	private boolean shutdownExecutor;
-	private Future<?> pendingFuture;
-
-    private FileSystem fileSystem = FileSystems.getDefault();
-    private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
-
-    private int version;
-    private final Map<String, byte[]> extensions = new HashMap<>();
-    private final Map<String, Handle> handles = new HashMap<>();
-
-    private final UnsupportedAttributePolicy unsupportedAttributePolicy;
-
-    protected static abstract class Handle implements java.io.Closeable {
-        private Path file;
-
-        public Handle(Path file) {
-            this.file = file;
-        }
-
-        public Path getFile() {
-            return file;
-        }
-
-        @Override
-        public void close() throws IOException {
-            // ignored
-        }
-
-        @Override
-        public String toString() {
-            return Objects.toString(getFile());
-        }
-    }
-
-    protected static class DirectoryHandle extends Handle implements Iterator<Path> {
-        private boolean done;
-        // the directory should be read once at "open directory"
-        private DirectoryStream<Path> ds;
-        private Iterator<Path> fileList;
-
-        public DirectoryHandle(Path file) throws IOException {
-            super(file);
-            ds = Files.newDirectoryStream(file);
-            fileList = ds.iterator();
-        }
-
-        public boolean isDone() {
-            return done;
-        }
-
-        public void setDone(boolean done) {
-            this.done = done;
-        }
-
-        @Override
-        public boolean hasNext() {
-            return fileList.hasNext();
-        }
-
-        @Override
-        public Path next() {
-            return fileList.next();
-        }
-
-        @Override
-        public void remove() {
-            throw new UnsupportedOperationException("Not allowed to remove " + toString());
-        }
-
-        public void clearFileList() {
-            // allow the garbage collector to do the job
-            fileList = null;
-        }
-
-        @Override
-        public void close() throws IOException {
-            ds.close();
-        }
-    }
-
-    protected class FileHandle extends Handle {
-        private final FileChannel channel;
-        private long pos;
-        private final List<FileLock> locks = new ArrayList<>();
-
-        public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
-            super(file);
-            Set<OpenOption> options = new HashSet<>();
-            if ((access & ACE4_READ_DATA) != 0 || (access & ACE4_READ_ATTRIBUTES) != 0) {
-                options.add(StandardOpenOption.READ);
-            }
-            if ((access & ACE4_WRITE_DATA) != 0 || (access & ACE4_WRITE_ATTRIBUTES) != 0) {
-                options.add(StandardOpenOption.WRITE);
-            }
-            switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
-            case SSH_FXF_CREATE_NEW:
-                options.add(StandardOpenOption.CREATE_NEW);
-                break;
-            case SSH_FXF_CREATE_TRUNCATE:
-                options.add(StandardOpenOption.CREATE);
-                options.add(StandardOpenOption.TRUNCATE_EXISTING);
-                break;
-            case SSH_FXF_OPEN_EXISTING:
-                break;
-            case SSH_FXF_OPEN_OR_CREATE:
-                options.add(StandardOpenOption.CREATE);
-                break;
-            case SSH_FXF_TRUNCATE_EXISTING:
-                options.add(StandardOpenOption.TRUNCATE_EXISTING);
-                break;
-            default:    // ignored
-            }
-            if ((flags & SSH_FXF_APPEND_DATA) != 0) {
-                options.add(StandardOpenOption.APPEND);
-            }
-            FileAttribute<?>[] attributes = new FileAttribute<?>[attrs.size()];
-            int index = 0;
-            for (Map.Entry<String, Object> attr : attrs.entrySet()) {
-                final String key = attr.getKey();
-                final Object val = attr.getValue();
-                attributes[index++] = new FileAttribute<Object>() {
-                    @Override
-                    public String name() {
-                        return key;
-                    }
-
-                    @Override
-                    public Object value() {
-                        return val;
-                    }
-                };
-            }
-            FileChannel channel;
-            try {
-                  channel = FileChannel.open(file, options, attributes);
-            } catch (UnsupportedOperationException e) {
-                channel = FileChannel.open(file, options);
-                setAttributes(file, attrs);
-            }
-            this.channel = channel;
-            this.pos = 0;
-        }
-
-        public int read(byte[] data, long offset) throws IOException {
-            return read(data, 0, data.length, offset);
-        }
-
-        public int read(byte[] data, int doff, int length, long offset) throws IOException {
-            if (pos != offset) {
-                channel.position(offset);
-                pos = offset;
-            }
-            int read = channel.read(ByteBuffer.wrap(data, doff, length));
-            pos += read;
-            return read;
-        }
-
-        public void write(byte[] data, long offset) throws IOException {
-            write(data, 0, data.length, offset);
-        }
-
-        public void write(byte[] data, int doff, int length, long offset) throws IOException {
-            if (pos != offset) {
-                channel.position(offset);
-                pos = offset;
-            }
-            channel.write(ByteBuffer.wrap(data, doff, length));
-            pos += length;
-        }
-
-        @Override
-        public void close() throws IOException {
-            channel.close();
-        }
-
-        public void lock(long offset, long length, int mask) throws IOException {
-            long size = length == 0 ? channel.size() - offset : length;
-            FileLock lock = channel.tryLock(offset, size, false);
-            synchronized (locks) {
-                locks.add(lock);
-            }
-        }
-
-        public boolean unlock(long offset, long length) throws IOException {
-            long size = length == 0 ? channel.size() - offset : length;
-            FileLock lock = null;
-            for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) {
-                FileLock l = iterator.next();
-                if (l.position() == offset && l.size() == size) {
-                    iterator.remove();
-                    lock = l;
-                    break;
-                }
-            }
-            if (lock != null) {
-                lock.release();
-                return true;
-            }
-            return false;
-        }
-    }
-
-    /**
-     * @param executorService The {@link ExecutorService} to be used by
-     *                        the {@link SftpSubsystem} command when starting execution. If
-     *                        {@code null} then a single-threaded ad-hoc service is used.
-     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
-     *                        will be called when subsystem terminates - unless it is the ad-hoc
-     *                        service, which will be shutdown regardless
-     * @param policy The {@link UnsupportedAttributePolicy} to use if failed to access
-     * some local file attributes
-     * @see ThreadUtils#newSingleThreadExecutor(String)
-     */
-    public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy) {
-        if ((executors = executorService) == null) {
-            executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
-            shutdownExecutor = true;    // we always close the ad-hoc executor service
-        } else {
-            shutdownExecutor = shutdownOnExit;
-        }
-        
-        if ((unsupportedAttributePolicy=policy) == null) {
-            throw new IllegalArgumentException("No policy provided");
-        }
-    }
-
-    public final UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
-        return unsupportedAttributePolicy;
-    }
-
-    @Override
-    public void setSession(ServerSession session) {
-        this.session = session;
-    }
-
-    @Override
-    public void setFileSystem(FileSystem fileSystem) {
-        if (fileSystem != this.fileSystem) {
-            this.fileSystem = fileSystem;
-            this.defaultDir = fileSystem.getRootDirectories().iterator().next();
-        }
-    }
-
-    @Override
-    public void setExitCallback(ExitCallback callback) {
-        this.callback = callback;
-    }
-
-    @Override
-    public void setInputStream(InputStream in) {
-        this.in = in;
-    }
-
-    @Override
-    public void setOutputStream(OutputStream out) {
-        this.out = out;
-    }
-
-    @Override
-    public void setErrorStream(OutputStream err) {
-        this.err = err;
-    }
-
-    @Override
-    public void start(Environment env) throws IOException {
-        this.env = env;
-        try {
-            pendingFuture = executors.submit(this);
-        } catch (RuntimeException e) {    // e.g., RejectedExecutionException
-            log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.toString(), e);
-            throw new IOException(e);
-        }
-    }
-
-    @Override
-    public void run() {
-        DataInputStream dis = null;
-        try {
-            dis = new DataInputStream(in);
-            while (true) {
-                int length = dis.readInt();
-                if (length < 5) {
-                    throw new IllegalArgumentException("Bad length to read: " + length);
-                }
-                Buffer buffer = new ByteArrayBuffer(length + 4);
-                buffer.putInt(length);
-                int nb = length;
-                while (nb > 0) {
-                    int l = dis.read(buffer.array(), buffer.wpos(), nb);
-                    if (l < 0) {
-                        throw new IllegalArgumentException("Premature EOF while read length=" + length + " while remain=" + nb);
-                    }
-                    buffer.wpos(buffer.wpos() + l);
-                    nb -= l;
-                }
-                process(buffer);
-            }
-        } catch (Throwable t) {
-            if (!closed && !(t instanceof EOFException)) { // Ignore
-                log.error("Exception caught in SFTP subsystem", t);
-            }
-        } finally {
-            if (dis != null) {
-                try {
-                    dis.close();
-                } catch (IOException ioe) {
-                    log.error("Could not close DataInputStream", ioe);
-                }
-            }
-
-            if (handles != null) {
-                for (Map.Entry<String, Handle> entry : handles.entrySet()) {
-                    Handle handle = entry.getValue();
-                    try {
-                        handle.close();
-                    } catch (IOException ioe) {
-                        log.error("Could not close open handle: " + entry.getKey(), ioe);
-                    }
-                }
-            }
-            callback.onExit(0);
-        }
-    }
-
-    protected void process(Buffer buffer) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (log.isDebugEnabled()) {
-            log.debug("process(length={}, type={}, id={})",
-                      new Object[] { Integer.valueOf(length), Integer.valueOf(type), Integer.valueOf(id) });
-        }
-
-        switch (type) {
-            case SSH_FXP_INIT:
-                doInit(buffer, id);
-                break;
-            case SSH_FXP_OPEN:
-                doOpen(buffer, id);
-                break;
-            case SSH_FXP_CLOSE:
-                doClose(buffer, id);
-                break;
-            case SSH_FXP_READ:
-                doRead(buffer, id);
-                break;
-            case SSH_FXP_WRITE:
-                doWrite(buffer, id);
-                break;
-            case SSH_FXP_LSTAT:
-                doLStat(buffer, id);
-                break;
-            case SSH_FXP_FSTAT:
-                doFStat(buffer, id);
-                break;
-            case SSH_FXP_SETSTAT:
-                doSetStat(buffer, id);
-                break;
-            case SSH_FXP_FSETSTAT:
-                doFSetStat(buffer, id);
-                break;
-            case SSH_FXP_OPENDIR:
-                doOpenDir(buffer, id);
-                break;
-            case SSH_FXP_READDIR:
-                doReadDir(buffer, id);
-                break;
-            case SSH_FXP_REMOVE:
-                doRemove(buffer, id);
-                break;
-            case SSH_FXP_MKDIR:
-                doMakeDirectory(buffer, id);
-                break;
-            case SSH_FXP_RMDIR:
-                doRemoveDirectory(buffer, id);
-                break;
-            case SSH_FXP_REALPATH:
-                doRealPath(buffer, id);
-                break;
-            case SSH_FXP_STAT:
-                doStat(buffer, id);
-                break;
-            case SSH_FXP_RENAME:
-                doRename(buffer, id);
-                break;
-            case SSH_FXP_READLINK:
-                doReadLink(buffer, id);
-                break;
-            case SSH_FXP_SYMLINK:
-                doSymLink(buffer, id);
-                break;
-            case SSH_FXP_LINK:
-                doLink(buffer, id);
-                break;
-            case SSH_FXP_BLOCK:
-                doBlock(buffer, id);
-                break;
-            case SSH_FXP_UNBLOCK:
-                doUnblock(buffer, id);
-                break;
-            case SSH_FXP_EXTENDED:
-                doExtended(buffer, id);
-                break;
-            default:
-                log.warn("Unknown command type received: {}", Integer.valueOf(type));
-                sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
-        }
-    }
-
-    protected void doExtended(Buffer buffer, int id) throws IOException {
-        String extension = buffer.getString();
-        switch (extension) {
-        case "text-seek":
-            doTextSeek(buffer, id);
-            break;
-        case "version-select":
-            doVersionSelect(buffer, id);
-            break;
-        default:
-            log.info("Received unsupported SSH_FXP_EXTENDED({})", extension);
-            sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
-            break;
-        }
-    }
-
-    protected void doTextSeek(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long line = buffer.getLong();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}, line={})", handle, Long.valueOf(line));
-        }
-
-        // TODO : implement text-seek
-        sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(text-seek) is unsupported or not implemented");
-    }
-
-    protected void doVersionSelect(Buffer buffer, int id) throws IOException {
-        String ver = buffer.getString();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_EXTENDED(version-select) (version={})", Integer.valueOf(version));
-        }
-        
-        if (GenericUtils.length(ver) == 1) {
-            char digit = ver.charAt(0);
-            if ((digit >= '0') && (digit <= '9')) {
-                int value = digit - '0';
-                String all = checkVersionCompatibility(id, value, SSH_FX_FAILURE);
-                if (GenericUtils.isEmpty(all)) {    // validation failed
-                    return;
-                }
-
-                version = value;
-                sendStatus(id, SSH_FX_OK, "");
-                return;
-            }
-        }
-
-        sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + ver);
-    }
-
-    /**
-     * Checks if a proposed version is within supported range. <B>Note:</B>
-     * if the user forced a specific value via the {@link #SFTP_VERSION}
-     * property, then it is used to validate the proposed value
-     * @param id The SSH message ID to be used to send the failure message
-     * if required
-     * @param proposed The proposed version value
-     * @param failureOpcode The failure opcode to send if validation fails
-     * @return A {@link String} of comma separated values representing all
-     * the supported version - {@code null} if validation failed and an
-     * appropriate status message was sent
-     * @throws IOException If failed to send the failure status message
-     */
-    protected String checkVersionCompatibility(int id, int proposed, int failureOpcode) throws IOException {
-        int low = LOWER_SFTP_IMPL;
-        int hig = HIGHER_SFTP_IMPL;
-        String available = ALL_SFTP_IMPL;
-        // check if user wants to use a specific version
-        Integer sftpVersion = FactoryManagerUtils.getInteger(session, SFTP_VERSION);
-        if (sftpVersion != null) {
-            int forcedValue = sftpVersion.intValue();
-            if ((forcedValue < LOWER_SFTP_IMPL) || (forcedValue > HIGHER_SFTP_IMPL)) {
-                throw new IllegalStateException("Forced SFTP version (" + sftpVersion + ") not within supported values: " + available);
-            }
-            low = hig = sftpVersion.intValue();
-            available = sftpVersion.toString();
-        }
-
-        if (log.isTraceEnabled()) {
-            log.trace("checkVersionCompatibility(id={}) - proposed={}, available={}",
-                      new Object[] { Integer.valueOf(id), Integer.valueOf(proposed), available });
-        }
-
-        if ((proposed < low) || (proposed > hig)) {
-            sendStatus(id, failureOpcode, "Proposed version (" + proposed + ") not in supported range: " + available);
-            return null;
-        }
-
-        return available;
-    }
-
-    protected void doBlock(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        long length = buffer.getLong();
-        int mask = buffer.getInt();
-        
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_BLOCK (handle={}, offset={}, length={}, mask=0x{})",
-                      new Object[] { handle, Long.valueOf(offset), Long.valueOf(length), Integer.toHexString(mask) });
-        }
-
-        try {
-            Handle p = handles.get(handle);
-            if (!(p instanceof FileHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-                return;
-            }
-            FileHandle fileHandle = (FileHandle) p;
-            fileHandle.lock(offset, length, mask);
-            sendStatus(id, SSH_FX_OK, "");
-        } catch (IOException | OverlappingFileLockException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doUnblock(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        long length = buffer.getLong();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_UNBLOCK (handle={}, offset={}, length={})",
-                      new Object[] { handle, Long.valueOf(offset), Long.valueOf(length) });
-        }
-
-        try {
-            Handle p = handles.get(handle);
-            if (!(p instanceof FileHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-                return;
-            }
-            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 void doLink(Buffer buffer, int id) throws IOException {
-        String targetpath = buffer.getString();
-        String linkpath = buffer.getString();
-        boolean symLink = buffer.getBoolean();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})",
-                      new Object[] { 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) {
-            sendStatus(id, e);
-        }
-    }
-
-    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);
-        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) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doReadLink(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        log.debug("Received SSH_FXP_READLINK (path={})", path);
-        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) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doRename(Buffer buffer, int id) throws IOException {
-        String oldPath = buffer.getString();
-        String newPath = buffer.getString();
-        int flags = 0;
-        if (version >= SFTP_V5) {
-            flags = buffer.getInt();
-        }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})",
-                       new Object[] { oldPath, newPath, Integer.toHexString(flags) });
-        }
-        try {
-            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);
-            }
-            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);
-        }
-    }
-
-    protected void doStat(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        int flags = SSH_FILEXFER_ATTR_ALL;
-        if (version >= SFTP_V4) {
-            flags = buffer.getInt();
-        }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_STAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
-        }
-        try {
-            Path p = resolveFile(path);
-            sendAttrs(id, p, flags, true);
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doRealPath(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        log.debug("Received SSH_FXP_REALPATH (path={})", path);
-        path = GenericUtils.trimToEmpty(path);
-        if (GenericUtils.isEmpty(path)) {
-            path = ".";
-        }
-
-        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());
-            } else {
-                // Read control byte
-                int control = 0;
-                if (buffer.available() > 0) {
-                    control = buffer.getByte();
-                }
-                List<String> paths = new ArrayList<>();
-                while (buffer.available() > 0) {
-                    paths.add(buffer.getString());
-                }
-                // Resolve path
-                Path p = resolveFile(path);
-                for (String p2 : paths) {
-                    p = p.resolve(p2);
-                }
-                p = p.toAbsolutePath().normalize();
-
-                Map<String, Object> attrs = Collections.emptyMap();
-                if (control == SSH_FXP_REALPATH_STAT_IF) {
-                    try {
-                        attrs = getAttributes(p, false);
-                    } catch (IOException e) {
-                        // ignore
-                    }
-                } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
-                    attrs = getAttributes(p, false);
-                }
-                sendPath(id, p, attrs);
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected Path handleUnknownRealPathStatus(String path, Path absolute, Path normalized) throws IOException {
-        switch(unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case Warn:
-                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized);
-                break;
-            case ThrowException:
-                throw new AccessDeniedException("Cannot determine existence status of real path: " + normalized);
-            
-            default:
-                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized
-                       + " - unknown policy: " + unsupportedAttributePolicy);
-        }
-        
-        return absolute;
-    }
-
-    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) {
-            sendStatus(id, e);
-        }
-    }
-
-    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={})", path);
-        // 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());
-                }
-            } else {
-                Files.createDirectory(p);
-                setAttributes(p, attrs);
-                sendStatus(id, SSH_FX_OK, "");
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    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) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doReadDir(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        log.debug("Received SSH_FXP_READDIR (handle={})", handle);
-        Handle p = handles.get(handle);
-        try {
-            if (!(p instanceof DirectoryHandle)) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-                return;
-            }
-            
-            if (((DirectoryHandle) p).isDone()) {
-                sendStatus(id, SSH_FX_EOF, "", "");
-                return;
-            }
-
-            Path            file = p.getFile();
-            LinkOption[]    options = IoUtils.getLinkOptions(false);
-            Boolean         status = IoUtils.checkFileExists(file, options);
-            if (status == null) {
-                throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
-            }
-
-            if (!status.booleanValue()) {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, file.toString());
-            } else if (!Files.isDirectory(file, options)) {
-                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, file.toString());
-            } else if (!Files.isReadable(file)) {
-                sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
-            } else {
-                DirectoryHandle dh = (DirectoryHandle) p;
-                if (dh.hasNext()) {
-                    // There is at least one file in the directory.
-                    // Send only a few files at a time to not create packets of a too
-                    // large size or have a timeout to occur.
-                    sendName(id, dh);
-                    if (!dh.hasNext()) {
-                        // if no more files to send
-                        dh.setDone(true);
-                        dh.clearFileList();
-                    }
-                } else {
-                    // empty directory
-                    dh.setDone(true);
-                    dh.clearFileList();
-                    sendStatus(id, SSH_FX_EOF, "", "");
-                }
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doOpenDir(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        log.debug("Received SSH_FXP_OPENDIR (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 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 = UUID.randomUUID().toString();
-                handles.put(handle, new DirectoryHandle(p));
-                sendHandle(id, handle);
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    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) {
-            sendStatus(id, e);
-        }
-    }
-
-    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) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doFStat(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        int flags = SSH_FILEXFER_ATTR_ALL;
-        if (version >= SFTP_V4) {
-            flags = buffer.getInt();
-        }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_FSTAT (handle={}, flags={})", handle, "0x" + Integer.toHexString(flags));
-        }
-        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) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doLStat(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        int flags = SSH_FILEXFER_ATTR_ALL;
-        if (version >= SFTP_V4) {
-            flags = buffer.getInt();
-        }
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_LSTAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
-        }
-        try {
-            Path p = resolveFile(path);
-            sendAttrs(id, p, flags, false);
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doWrite(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        int length = buffer.getInt();
-        if (length < 0) {
-            throw new IllegalStateException();
-        }
-        if (buffer.available() < length) {
-            throw new BufferUnderflowException();
-        }
-        byte[] data = buffer.array();
-        int doff = buffer.rpos();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])",
-                      new Object[] { handle, Long.valueOf(offset), Integer.valueOf(length) });
-        }
-        try {
-            Handle p = handles.get(handle);
-            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);
-        }
-    }
-
-    protected void doRead(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        int len = buffer.getInt();
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})",
-                      new Object[]{handle, Long.valueOf(offset), Integer.valueOf(len) });
-        }
-        try {
-            Handle p = handles.get(handle);
-            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) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doClose(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        log.debug("Received SSH_FXP_CLOSE (handle={})", handle);
-        try {
-            Handle h = handles.get(handle);
-            if (h == null) {
-                sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
-            } else {
-                handles.remove(handle);
-                h.close();
-                sendStatus(id, SSH_FX_OK, "", "");
-            }
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doOpen(Buffer buffer, int id) throws IOException {
-        int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, Integer.MAX_VALUE);
-        if (handles.size() > maxHandleCount) {
-            sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
-            return;
-        }
-
-        String path = buffer.getString();
-        int access = 0;
-        if (version >= SFTP_V5) {
-            access = buffer.getInt();
-        }
-        int pflags = buffer.getInt();
-        if (version < SFTP_V5) {
-            int flags = pflags;
-            pflags = 0;
-            switch (flags & (SSH_FXF_READ | SSH_FXF_WRITE)) {
-            case SSH_FXF_READ:
-                access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
-                break;
-            case SSH_FXF_WRITE:
-                access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
-                break;
-            default:
-                access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
-                access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
-                break;
-            }
-            if ((flags & SSH_FXF_APPEND) != 0) {
-                access |= ACE4_APPEND_DATA;
-                pflags |= SSH_FXF_APPEND_DATA | SSH_FXF_APPEND_DATA_ATOMIC;
-            }
-            if ((flags & SSH_FXF_CREAT) != 0) {
-                if ((flags & SSH_FXF_EXCL) != 0) {
-                    pflags |= SSH_FXF_CREATE_NEW;
-                } else if ((flags & SSH_FXF_TRUNC) != 0) {
-                    pflags |= SSH_FXF_CREATE_TRUNCATE;
-                } else {
-                    pflags |= SSH_FXF_OPEN_OR_CREATE;
-                }
-            } else {
-                if ((flags & SSH_FXF_TRUNC) != 0) {
-                    pflags |= SSH_FXF_TRUNCATE_EXISTING;
-                } else {
-                    pflags |= SSH_FXF_OPEN_EXISTING;
-                }
-            }
-        }
-        Map<String, Object> attrs = readAttrs(buffer);
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_OPEN (path={}, access=0x{}, pflags=0x{}, attrs={})",
-                      new Object[]{path, Integer.toHexString(access), Integer.toHexString(pflags), attrs});
-        }
-        try {
-            Path file = resolveFile(path);
-            String handle = UUID.randomUUID().toString();
-            handles.put(handle, new FileHandle(file, pflags, access, attrs));
-            sendHandle(id, handle);
-        } catch (IOException e) {
-            sendStatus(id, e);
-        }
-    }
-
-    protected void doInit(Buffer buffer, int id) throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("Received SSH_FXP_INIT (version={})", Integer.valueOf(id));
-        }
-
-        String all = checkVersionCompatibility(id, id, SSH_FX_OP_UNSUPPORTED);
-        if (GenericUtils.isEmpty(all)) { // i.e. validation failed
-            return;
-        }
-        version = id;
-        while (buffer.available() > 0) {
-            String name = buffer.getString();
-            byte[] data = buffer.getBytes();
-            extensions.put(name, data);
-        }
-
-        buffer.clear();
-        buffer.putByte((byte) SSH_FXP_VERSION);
-        buffer.putInt(version);
-
-        // newline
-        buffer.putString("newline");
-        buffer.putString(System.getProperty("line.separator"));
-
-        // versions
-        buffer.putString("versions");
-        buffer.putString(all);
-
-        // supported
-        buffer.putString("supported");
-        buffer.putInt(5 * 4); // length of 5 integers
-        // supported-attribute-mask
-        buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
-                | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
-                | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
-                | SSH_FILEXFER_ATTR_BITS);
-        // TODO: supported-attribute-bits
-        buffer.putInt(0);
-        // supported-open-flags
-        buffer.putInt(SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
-                | SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_EXCL);
-        // TODO: supported-access-mask
-        buffer.putInt(0);
-        // max-read-size
-        buffer.putInt(0);
-
-        // supported2
-        buffer.putString("supported2");
-        buffer.putInt(8 * 4); // length of 7 integers + 2 shorts
-        // supported-attribute-mask
-        buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
-                | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
-                | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
-                | SSH_FILEXFER_ATTR_BITS);
-        // TODO: supported-attribute-bits
-        buffer.putInt(0);
-        // supported-open-flags
-        buffer.putInt(SSH_FXF_ACCESS_DISPOSITION | SSH_FXF_APPEND_DATA);
-        // TODO: supported-access-mask
-        buffer.putInt(0);
-        // max-read-size
-        buffer.putInt(0);
-        // supported-open-block-vector
-        buffer.putShort(0);
-        // supported-block-vector
-        buffer.putShort(0);
-        // attrib-extension-count
-        buffer.putInt(0);
-        // extension-count
-        buffer.putInt(0);
-
-        /*
-        buffer.putString("acl-supported");
-        buffer.putInt(4);
-        // capabilities
-        buffer.putInt(0);
-        */
-
-        send(buffer);
-    }
-
-    protected void sendHandle(int id, String handle) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        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();
-        buffer.putByte((byte) SSH_FXP_ATTRS);
-        buffer.putInt(id);
-        writeAttrs(buffer, file, flags, followLinks);
-        send(buffer);
-    }
-
-    protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putByte((byte) SSH_FXP_NAME);
-        buffer.putInt(id);
-        buffer.putInt(1);
-
-        String originalPath = f.toString();
-        //in case we are running on Windows
-        String unixPath = originalPath.replace(File.separatorChar, '/');
-        //normalize the given path, use *nix style separator
-        String normalizedPath = SelectorUtils.normalizePath(unixPath, "/");
-        if (normalizedPath.length() == 0) {
-            normalizedPath = "/";
-        }
-        buffer.putString(normalizedPath, StandardCharsets.UTF_8);
-
-        if (version == SFTP_V3) {
-            f = resolveFile(normalizedPath);
-            buffer.putString(getLongName(f, attrs), StandardCharsets.UTF_8); // Format specified in the specs
-            buffer.putInt(0);
-        } else if (version >= SFTP_V4) {
-            writeAttrs(buffer, attrs);
-        } else {
-            throw new IllegalStateException("sendPath(" + f + ") unsupported version: " + version);
-        }
-        send(buffer);
-    }
-
-    protected void sendLink(int id, String link) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putByte((byte) SSH_FXP_NAME);
-        buffer.putInt(id);
-        buffer.putInt(1);
-        //normalize the given path, use *nix style separator
-        buffer.putString(link);
-        buffer.putString(link);
-        buffer.putInt(0);
-        send(buffer);
-    }
-
-    protected void sendName(int id, Iterator<Path> 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;
-        while (files.hasNext() && (buffer.wpos() < MAX_PACKET_LENGTH)) {
-            Path    f = files.next();
-            String  shortName = getShortName(f);
-            buffer.putString(shortName, StandardCharsets.UTF_8);
-            if (version == SFTP_V3) {
-                String  longName = getLongName(f);
-                buffer.putString(longName, StandardCharsets.UTF_8); // Format specified in the specs
-                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++;
-        }
-
-        int oldpos = buffer.wpos();
-        buffer.wpos(wpos);
-        buffer.putInt(nb);
-        buffer.wpos(oldpos);
-        send(buffer);
-    }
-
-    private String getLongName(Path f) throws IOException {
-        return getLongName(f, true);
-    }
-
-    private String getLongName(Path f, boolean sendAttrs) throws IOException {
-        Map<String, Object> attributes;
-        if (sendAttrs) {
-            attributes = getAttributes(f, false);
-        } else {
-            attributes = Collections.emptyMap();
-        }
-        return getLongName(f, attributes);
-    }
-
-    private String getLongName(Path f, Map<String, Object> attributes) throws IOException {
-        String username;
-        if (attributes.containsKey("owner")) {
-            username = attributes.get("owner").toString();
-        } else {
-            username = "owner";
-        }
-        if (username.length() > 8) {
-            username = username.substring(0, 8);
-        } else {
-            for (int i = username.length(); i < 8; i++) {
-                username = username + " ";
-            }
-        }
-        String group;
-        if (attributes.containsKey("group")) {
-            group = attributes.get("group").toString();
-        } else {
-            group = "group";
-        }
-        if (group.length() > 8) {
-            group = group.substring(0, 8);
-        } else {
-            for (int i = group.length(); i < 8; i++) {
-                group = group + " ";
-            }
-        }
-
-        Number length = (Number) attributes.get("size");
-        if (length == null) {
-            length = Long.valueOf(0L);
-        }
-        String lengthString = String.format("%1$8s", length);
-
-        Boolean isDirectory = (Boolean) attributes.get("isDirectory");
-        Boolean isLink = (Boolean) attributes.get("isSymbolicLink");
-        @SuppressWarnings("unchecked")
-        Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
-        if (perms == null) {
-            perms = EnumSet.noneOf(PosixFilePermission.class);
-        }
-
-        StringBuilder sb = new StringBuilder();
-        sb.append((isDirectory != null && isDirectory.booleanValue()) ? "d" : (isLink != null && isLink.booleanValue()) ? "l" : "-");
-        sb.append(PosixFilePermissions.toString(perms));
-        sb.append("  ");
-        sb.append(attributes.containsKey("nlink") ? attributes.get("nlink") : "1");
-        sb.append(" ");
-        sb.append(username);
-        sb.append(" ");
-        sb.append(group);
-        sb.append(" ");
-        sb.append(lengthString);
-        sb.append(" ");
-        sb.append(getUnixDate((FileTime) attributes.get("lastModifiedTime")));
-        sb.append(" ");
-        sb.append(getShortName(f));
-
-        return sb.toString();
-    }
-
-    protected String getShortName(Path f) {
-        if (OsUtils.isUNIX()) {
-            Path    name=f.getFileName();
-            if (name == null) {
-                Path    p=resolveFile(".");
-                name = p.getFileName();
-            }
-            
-            return name.toString();
-        } else {    // need special handling for Windows root drives
-            Path    abs=f.toAbsolutePath().normalize();
-            int     count=abs.getNameCount();
-            /*
-             * According to the javadoc:
-             * 
-             *      The number of elements in the path, or 0 if this path only
-             *      represents a root component
-             */
-            if (count > 0) {
-                Path    name=abs.getFileName();
-                return name.toString();
-            } else {
-                return abs.toString().replace(File.separatorChar, '/');
-            }
-        }
-    }
-
-    protected int attributesToPermissions(boolean isReg, boolean isDir, boolean isLnk, Collection<PosixFilePermission> perms) {
-        int pf = 0;
-        if (perms != null) {
-            for (PosixFilePermission p : perms) {
-                switch (p) {
-                case OWNER_READ:
-                    pf |= S_IRUSR;
-                    break;
-                case OWNER_WRITE:
-                    pf |= S_IWUSR;
-                    break;
-                case OWNER_EXECUTE:
-                    pf |= S_IXUSR;
-                    break;
-                case GROUP_READ:
-                    pf |= S_IRGRP;
-                    break;
-                case GROUP_WRITE:
-                    pf |= S_IWGRP;
-                    break;
-                case GROUP_EXECUTE:
-                    pf |= S_IXGRP;
-                    break;
-                case OTHERS_READ:
-                    pf |= S_IROTH;
-                    break;
-                case OTHERS_WRITE:
-                    pf |= S_IWOTH;
-                    break;
-                case OTHERS_EXECUTE:
-                    pf |= S_IXOTH;
-                    break;
-                default: // ignored
-                }
-            }
-        }
-        pf |= isReg ? S_IFREG : 0;
-        pf |= isDir ? S_IFDIR : 0;
-        pf |= isLnk ? S_IFLNK : 0;
-        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;
-        if (status == null) {
-            attributes = handleUnknownStatusFileAttributes(file, flags, followLinks);
-        } else if (!status.booleanValue()) {
-            throw new FileNotFoundException(file.toString());
-        } else {
-            attributes = getAttributes(file, flags, followLinks);
-        }
-
-        writeAttrs(buffer, attributes);
-    }
-
-    protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
-        boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
-        boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
-        boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
-        @SuppressWarnings("unchecked")
-        Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) attributes.get("permissions");
-        Number size = (Number) attributes.get("size");
-        FileTime lastModifiedTime = (FileTime) attributes.get("lastModifiedTime");
-        FileTime lastAccessTime = (FileTime) attributes.get("lastAccessTime");
-
-        if (version == SFTP_V3) {
-            int flags =
-                    ((isReg || isLnk) && (size != null) ? SSH_FILEXFER_ATTR_SIZE : 0) |
-                    (attributes.containsKey("uid") && attributes.containsKey("gid") ? SSH_FILEXFER_ATTR_UIDGID : 0) |
-                    ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
-                    (((lastModifiedTime != null) && (lastAccessTime != null)) ? SSH_FILEXFER_ATTR_ACMODTIME : 0);
-            buffer.putInt(flags);
-            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-                buffer.putLong(size.longValue());
-            }
-            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-                buffer.putInt(((Number) attributes.get("uid")).intValue());
-                buffer.putInt(((Number) attributes.get("gid")).intValue());
-            }
-            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-                buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
-            }
-            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-                buffer.putInt(lastAccessTime.to(TimeUnit.SECONDS));
-                buffer.putInt(lastModifiedTime.to(TimeUnit.SECONDS));
-            }
-        } else if (version >= SFTP_V4) {
-            FileTime creationTime = (FileTime) attributes.get("creationTime");
-            int flags = (((isReg || isLnk) && (size != null)) ? SSH_FILEXFER_ATTR_SIZE : 0) |
-                        ((attributes.containsKey("owner") && attributes.containsKey("group")) ? SSH_FILEXFER_ATTR_OWNERGROUP : 0) |
-                        ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
-                        ((lastModifiedTime != null) ? SSH_FILEXFER_ATTR_MODIFYTIME : 0) |
-                        ((creationTime != null) ? SSH_FILEXFER_ATTR_CREATETIME : 0) |
-                        ((lastAccessTime != null) ? SSH_FILEXFER_ATTR_ACCESSTIME : 0);
-            buffer.putInt(flags);
-            buffer.putByte((byte) (isReg ? SSH_FILEXFER_TYPE_REGULAR :
-                    isDir ? SSH_FILEXFER_TYPE_DIRECTORY :
-                            isLnk ? SSH_FILEXFER_TYPE_SYMLINK :
-                                    SSH_FILEXFER_TYPE_UNKNOWN));
-            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-                buffer.putLong(size.longValue());
-            }
-            if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
-                buffer.putString(attributes.get("owner").toString(), StandardCharsets.UTF_8);
-                buffer.putString(attributes.get("group").toString(), StandardCharsets.UTF_8);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-                buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
-            }
-
-            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
-                putFileTime(buffer, flags, lastAccessTime);
-            }
-
-            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
-                putFileTime(buffer, flags, lastAccessTime);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
-                putFileTime(buffer, flags, lastModifiedTime);
-            }
-            // TODO: acls
-            // TODO: bits
-            // TODO: extended
-        }
-    }
-
-    protected void putFileTime(Buffer buffer, int flags, FileTime time) {
-        buffer.putLong(time.to(TimeUnit.SECONDS));
-        if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
-            long nanos = time.to(TimeUnit.NANOSECONDS);
-            nanos = nanos % TimeUnit.SECONDS.toNanos(1);
-            buffer.putInt((int) nanos);
-        }
-    }
-
-    protected boolean getBool(Boolean bool) {
-        return (bool != null) && bool.booleanValue();
-    }
-
-    protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
-        return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
-    }
-
-    public static final List<String>    DEFAULT_UNIX_VIEW=Collections.singletonList("unix:*");
-
-    protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
-        switch(unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case ThrowException:
-                throw new AccessDeniedException("Cannot determine existence for attributes of " + file);
-            case Warn:
-                log.warn("handleUnknownStatusFileAttributes(" + file + ") cannot determine existence");
-                break;
-            default:
-                log.warn("handleUnknownStatusFileAttributes(" + file + ") unknown policy: " + unsupportedAttributePolicy);
-        }
-        
-        return getAttributes(file, flags, followLinks);
-    }
-
-    protected Map<String, Object> getAttributes(Path file, int flags, boolean followLinks) throws IOException {
-        FileSystem          fs=file.getFileSystem();
-        Collection<String>  supportedViews=fs.supportedFileAttributeViews();
-        LinkOption[]        opts=IoUtils.getLinkOptions(followLinks);
-        Map<String,Object>  attrs=new HashMap<>();
-        Collection<String>  views;
-
-        if (GenericUtils.isEmpty(supportedViews)) {
-            views = Collections.<String>emptyList();
-        } else if (supportedViews.contains("unix")) {
-            views = DEFAULT_UNIX_VIEW;
-        } else {
-            views = new ArrayList<String>(supportedViews.size());
-            for (String v : supportedViews) {
-                views.add(v + ":*");
-            }
-        }
-
-        for (String v : views) {
-            Map<String, Object> ta=readFileAttributes(file, v, opts);
-            attrs.putAll(ta);
-        }
-
-        // if did not get permissions from the supported views return a best approximation
-        if (!attrs.containsKey("permissions")) {
-            Set<PosixFilePermission> perms=IoUtils.getPermissionsFromFile(file.toFile());
-            attrs.put("permissions", perms);
-        }
-
-        return attrs;
-    }
-
-    protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... opts) throws IOException {
-        try {
-            return Files.readAttributes(file, view, opts);
-        } catch(IOException e) {
-            return handleReadFileAttributesException(file, view, opts, e);
-        }
-    }
-
-    protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] opts, IOException e) throws IOException {
-        switch(unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case Warn:
-                log.warn("handleReadFileAttributesException(" + file + ")[" + view + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
-                break;
-            case ThrowException:
-                throw e;
-            default:
-                log.warn("handleReadFileAttributesException(" + file + ")[" + view + "]"
-                       + " Unknown policy (" + unsupportedAttributePolicy + ")"
-                       + " for " + e.getClass().getSimpleName() + ": " + e.getMessage());
-        }
-        
-        return Collections.emptyMap();
-    }
-
-    protected void setAttributes(Path file, Map<String, Object>  attributes) throws IOException {
-        Set<String> unsupported = new HashSet<>();
-        for (String attribute : attributes.keySet()) {
-            String view = null;
-            Object value = attributes.get(attribute);
-            switch (attribute) {
-            case "size": {
-                long newSize = ((Number) value).longValue();
-                try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
-                    channel.truncate(newSize);
-                }
-                continue;
-            }
-            case "uid":
-                view = "unix";
-                break;
-            case "gid":
-                view = "unix";
-                break;
-            case "owner":
-                view = "posix";
-                value = toUser(file, (UserPrincipal) value);
-                break;
-            case "group":
-                view = "posix";
-                value = toGroup(file, (GroupPrincipal) value);
-                break;
-            case "permissions":
-                if (OsUtils.isWin32()) {
-                    @SuppressWarnings("unchecked")
-                    Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) value;
-                    IoUtils.setPermissionsToFile(file.toFile(), perms);
-                    continue;
-                }
-                view = "posix";
-                break;
-
-            case "creationTime":
-                view = "basic";
-                break;
-            case "lastModifiedTime":
-                view = "basic";
-                break;
-            case "lastAccessTime":
-                view = "basic";
-                break;
-            default:    // ignored
-            }
-            if (view != null && value != null) {
-                try {
-                    Files.setAttribute(file, view + ":" + attribute, value, IoUtils.getLinkOptions(false));
-                } catch (UnsupportedOperationException e) {
-                    unsupported.add(attribute);
-                }
-            }
-        }
-        handleUnsupportedAttributes(unsupported);
-    }
-
-    protected void handleUnsupportedAttributes(Collection<String> attributes) {
-        if (!attributes.isEmpty()) {
-            StringBuilder sb = new StringBuilder();
-            for (String attr : attributes) {
-                if (sb.length() > 0) {
-                    sb.append(", ");
-                }
-                sb.append(attr);
-            }
-            switch (unsupportedAttributePolicy) {
-                case Ignore:
-                    break;
-                case Warn:
-                    log.warn("Unsupported attributes: " + sb.toString());
-                    break;
-                case ThrowException:
-                    throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
-                default:
-                    log.warn("Unknown policy for attributes=" + sb.toString() + ": " + unsupportedAttributePolicy);
-            }
-        }
-    }
-
-    private GroupPrincipal toGroup(Path file, GroupPrincipal name) throws IOException {
-        String groupName = name.toString();
-        FileSystem fileSystem = file.getFileSystem();
-        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
-        try {
-            return lookupService.lookupPrincipalByGroupName(groupName);
-        } catch (IOException e) {
-            handleUserPrincipalLookupServiceException(GroupPrincipal.class, groupName, e);
-            return null;
-        }
-    }
-
-    private UserPrincipal toUser(Path file, UserPrincipal name) throws IOException {
-        String username = name.toString();
-        FileSystem fileSystem = file.getFileSystem();
-        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
-        try {
-            return lookupService.lookupPrincipalByName(username);
-        } catch (IOException e) {
-            handleUserPrincipalLookupServiceException(UserPrincipal.class, username, e);
-            return null;
-        }
-    }
-
-    protected void handleUserPrincipalLookupServiceException(Class<? extends Principal> principalType, String name, IOException e) throws IOException {
-        /* According to Javadoc:
-         * 
-         *      "Where an implementation does not support any notion of group
-         *      or user then this method always throws UserPrincipalNotFoundException."
-         */
-        switch (unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case Warn:
-                log.warn("handleUserPrincipalLookupServiceException(" + principalType.getSimpleName() + "[" + name + "])"
-                       + " failed (" + e.getClass().getSimpleName() + "): " + e.getMessage());
-                break;
-            case ThrowException:
-                throw e;
-            default:
-                log.warn("Unknown policy for principal=" + principalType.getSimpleName() + "[" + name + "]: " + unsupportedAttributePolicy);
-        }
-    }
-
-    private Set<PosixFilePermission> permissionsToAttributes(int perms) {
-        Set<PosixFilePermission> p = new HashSet<>();
-        if ((perms & S_IRUSR) != 0) {
-            p.add(PosixFilePermission.OWNER_READ);
-        }
-        if ((perms & S_IWUSR) != 0) {
-            p.add(PosixFilePermission.OWNER_WRITE);
-        }
-        if ((perms & S_IXUSR) != 0) {
-            p.add(PosixFilePermission.OWNER_EXECUTE);
-        }
-        if ((perms & S_IRGRP) != 0) {
-            p.add(PosixFilePermission.GROUP_READ);
-        }
-        if ((perms & S_IWGRP) != 0) {
-            p.add(PosixFilePermission.GROUP_WRITE);
-        }
-        if ((perms & S_IXGRP) != 0) {
-            p.add(PosixFilePermission.GROUP_EXECUTE);
-        }
-        if ((perms & S_IROTH) != 0) {
-            p.add(PosixFilePermission.OTHERS_READ);
-        }
-        if ((perms & S_IWOTH) != 0) {
-            p.add(PosixFilePermission.OTHERS_WRITE);
-        }
-        if ((perms & S_IXOTH) != 0) {
-            p.add(PosixFilePermission.OTHERS_EXECUTE);
-        }
-        return p;
-    }
-
-    protected Map<String, Object> readAttrs(Buffer buffer) throws IOException {
-        Map<String, Object> attrs = new HashMap<>();
-        int flags = buffer.getInt();
-        if (version >= SFTP_V4) {
-            byte type = buffer.getByte();
-            switch (type) {
-            case SSH_FILEXFER_TYPE_REGULAR:
-                attrs.put("isRegular", Boolean.TRUE);
-                break;
-            case SSH_FILEXFER_TYPE_DIRECTORY:
-                attrs.put("isDirectory", Boolean.TRUE);
-                break;
-            case SSH_FILEXFER_TYPE_SYMLINK:
-                attrs.put("isSymbolicLink", Boolean.TRUE);
-                break;
-            case SSH_FILEXFER_TYPE_UNKNOWN:
-                attrs.put("isOther", Boolean.TRUE);
-                break;
-            default:    // ignored
-            }
-        }
-        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-            attrs.put("size", Long.valueOf(buffer.getLong()));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) {
-            attrs.put("allocationSize", Long.valueOf(buffer.getLong()));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-            attrs.put("uid", Integer.valueOf(buffer.getInt()));
-            attrs.put("gid", Integer.valueOf(buffer.getInt()));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
-            attrs.put("owner", new DefaultGroupPrincipal(buffer.getString()));
-            attrs.put("group", new DefaultGroupPrincipal(buffer.getString()));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-            attrs.put("permissions", permissionsToAttributes(buffer.getInt()));
-        }
-        if (version == SFTP_V3) {
-            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-                attrs.put("lastAccessTime", readTime(buffer, flags));
-                attrs.put("lastModifiedTime", readTime(buffer, flags));
-            }
-        } else if (version >= SFTP_V4) {
-            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
-                attrs.put("lastAccessTime", readTime(buffer, flags));
-            }
-            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
-                attrs.put("creationTime", readTime(buffer, flags));
-            }
-            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
-                attrs.put("lastModifiedTime", readTime(buffer, flags));
-            }
-            if ((flags & SSH_FILEXFER_ATTR_CTIME) != 0) {
-                attrs.put("ctime", readTime(buffer, flags));
-            }
-        }
-        if ((flags & SSH_FILEXFER_ATTR_ACL) != 0) {
-            int count = buffer.getInt();
-            List<AclEntry> acls = new ArrayList<>();
-            for (int i = 0; i < count; i++) {
-                int aclType = buffer.getInt();
-                int aclFlag = buffer.getInt();
-                int aclMask = buffer.getInt();
-                String aclWho = buffer.getString();
-                acls.add(buildAclEntry(aclType, aclFlag, aclMask, aclWho));
-            }
-            attrs.put("acl", acls);
-        }
-        if ((flags & SSH_FILEXFER_ATTR_BITS) != 0) {
-            int bits = buffer.getInt();
-            int valid = 0xffffffff;
-            if (version >= SFTP_V6) {
-                valid = buffer.getInt();
-            }
-            // TODO: handle attrib bits
-        }
-        if ((flags & SSH_FILEXFER_ATTR_TEXT_HINT) != 0) {
-            boolean text = buffer.getBoolean();
-            // TODO: handle text
-        }
-        if ((flags & SSH_FILEXFER_ATTR_MIME_TYPE) != 0) {
-            String mimeType = buffer.getString();
-            // TODO: handle mime-type
-        }
-        if ((flags & SSH_FILEXFER_ATTR_LINK_COUNT) != 0) {
-            int nlink = buffer.getInt();
-            // TODO: handle link-count
-        }
-        if ((flags & SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
-            String untranslated = buffer.getString();
-            // TODO: handle untranslated-name
-        }
-        if ((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0) {
-            int count = buffer.getInt();
-            Map<String, String> extended = new HashMap<>();
-            for (int i = 0; i < count; i++) {
-                String key = buffer.getString();
-                String val = buffer.getString();
-                extended.put(key, val);
-            }
-            attrs.put("extended", extended);
-        }
-
-        return attrs;
-    }
-
-    private FileTime readTime(Buffer buffer, int flags) {
-        long secs = buffer.getLong();
-        long millis = secs * 1000;
-        if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
-            millis += buffer.getInt() / 1000000l;
-        }
-        return FileTime.from(millis, TimeUnit.MILLISECONDS);
-    }
-
-    private AclEntry buildAclEntry(int aclType, int aclFlag, int aclMask, final String aclWho) {
-        AclEntryType type;
-        switch (aclType) {
-        case ACE4_ACCESS_ALLOWED_ACE_TYPE:
-            type = AclEntryType.ALLOW;
-            break;
-        case ACE4_ACCESS_DENIED_ACE_TYPE:
-            type = AclEntryType.DENY;
-            break;
-        case ACE4_SYSTEM_AUDIT_ACE_TYPE:
-            type = AclEntryType.AUDIT;
-            break;
-        case ACE4_SYSTEM_ALARM_ACE_TYPE:
-            type = AclEntryType.AUDIT;
-            break;
-        default:
-            throw new IllegalStateException("Unknown acl type: " + aclType);
-        }
-        Set<AclEntryFlag> flags = new HashSet<>();
-        if ((aclFlag & ACE4_FILE_INHERIT_ACE) != 0) {
-            flags.add(AclEntryFlag.FILE_INHERIT);
-        }
-        if ((aclFlag & ACE4_DIRECTORY_INHERIT_ACE) != 0) {
-            flags.add(AclEntryFlag.DIRECTORY_INHERIT);
-        }
-        if ((aclFlag & ACE4_NO_PROPAGATE_INHERIT_ACE) != 0) {
-            flags.add(AclEntryFlag.NO_PROPAGATE_INHERIT);
-        }
-        if ((aclFlag & ACE4_INHERIT_ONLY_ACE) != 0) {
-            flags.add(AclEntryFlag.INHERIT_ONLY);
-        }
-        Set<AclEntryPermission> mask = new HashSet<>();
-        if ((aclMask & ACE4_READ_DATA) != 0) {
-            mask.add(AclEntryPermission.READ_DATA);
-        }
-        if ((aclMask & ACE4_LIST_DIRECTORY) != 0) {
-            mask.add(AclEntryPermission.LIST_DIRECTORY);
-        }
-        if ((aclMask & ACE4_WRITE_DATA) != 0) {
-            mask.add(AclEntryPermission.WRITE_DATA);
-        }
-        if ((aclMask & ACE4_ADD_FILE) != 0) {
-            mask.add(AclEntryPermission.ADD_FILE);
-        }
-        if ((aclMask & ACE4_APPEND_DATA) != 0) {
-            mask.add(AclEntryPermission.APPEND_DATA);
-        }
-        if ((aclMask & ACE4_ADD_SUBDIRECTORY) != 0) {
-            mask.add(AclEntryPermission.ADD_SUBDIRECTORY);
-        }
-        if ((aclMask & ACE4_READ_NAMED_ATTRS) != 0) {
-            mask.add(AclEntryPermission.READ_NAMED_ATTRS);
-        }
-        if ((aclMask & ACE4_WRITE_NAMED_ATTRS) != 0) {
-            mask.add(AclEntryPermission.WRITE_NAMED_ATTRS);
-        }
-        if ((aclMask & ACE4_EXECUTE) != 0) {
-            mask.add(AclEntryPermission.EXECUTE);
-        }
-        if ((aclMask & ACE4_DELETE_CHILD) != 0) {
-            mask.add(AclEntryPermission.DELETE_CHILD);
-        }
-        if ((aclMask & ACE4_READ_ATTRIBUTES) != 0) {
-            mask.add(AclEntryPermission.READ_ATTRIBUTES);
-        }
-        if ((aclMask & ACE4_WRITE_ATTRIBUTES) != 0) {
-            mask.add(AclEntryPermission.WRITE_ATTRIBUTES);
-        }
-        if ((aclMask & ACE4_DELETE) != 0) {
-            mask.add(AclEntryPermission.DELETE);
-        }
-        if ((aclMask & ACE4_READ_ACL) != 0) {
-            mask.add(AclEntryPermission.READ_ACL);
-        }
-        if ((aclMask & ACE4_WRITE_ACL) != 0) {
-            mask.add(AclEntryPermission.WRITE_ACL);
-        }
-        if ((aclMask & ACE4_WRITE_OWNER) != 0) {
-            mask.add(AclEntryPermission.WRITE_OWNER);
-        }
-        if ((aclMask & ACE4_SYNCHRONIZE) != 0) {
-            mask.add(AclEntryPermission.SYNCHRONIZE);
-        }
-        UserPrincipal who = new DefaultGroupPrincipal(aclWho);
-        return AclEntry.newBuilder()
-                .setType(type)
-                .setFlags(flags)
-                .setPermissions(mask)
-                .setPrincipal(who)
-                .build();
-    }
-
-    protected void sendStatus(int id, Exception e) throws IOException {
-        int substatus;
-        if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
-            substatus = SSH_FX_NO_SUCH_FILE;
-        } else if (e instanceof FileAlreadyExistsException) {
-            substatus = SSH_FX_FILE_ALREADY_EXISTS;
-        } else if (e instanceof DirectoryNotEmptyException) {
-            substatus = SSH_FX_DIR_NOT_EMPTY;
-        } else if (e instanceof AccessDeniedException) {
-            substatus = SSH_FX_PERMISSION_DENIED;
-        } else if (e instanceof OverlappingFileLockException) {
-            substatus = SSH_FX_LOCK_CONFLICT;
-        } else {
-            substatus = SSH_FX_FAILURE;
-        }
-        sendStatus(id, substatus, e.toString());
-    }
-
-    protected void sendStatus(int id, int substatus, String msg) throws IOException {
-        sendStatus(id, substatus, msg != null ? msg : "", "");
-    }
-
-    protected void sendStatus(int id, int substatus, String msg, String lang) throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("Send SSH_FXP_STATUS (substatus={}, lang={}, msg={})",
-                      new Object[] { Integer.valueOf(substatus), lang, msg });
-        }
-
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putByte((byte) SSH_FXP_STATUS);
-        buffer.putInt(id);
-        buffer.putInt(substatus);
-        buffer.putString(msg);
-        buffer.putString(lang);
-        send(buffer);
-    }
-
-    protected void send(Buffer buffer) throws IOException {
-        DataOutputStream dos = new DataOutputStream(out);
-        dos.writeInt(buffer.available());
-        dos.write(buffer.array(), buffer.rpos(), buffer.available());
-        dos.flush();
-    }
-
-    @Override
-    public void destroy() {
-        if (!closed) {
-            if (log.isDebugEnabled()) {
-                log.debug("destroy() - mark as closed");
-            }
-
-            closed = true;
-
-            // if thread has not completed, cancel it
-            if ((pendingFuture != null) && (!pendingFuture.isDone())) {
-                boolean result = pendingFuture.cancel(true);
-                // TODO consider waiting some reasonable (?) amount of time for cancellation
-                if (log.isDebugEnabled()) {
-                    log.debug("destroy() - cancel pending future=" + result);
-                }
-            }
-
-            pendingFuture = null;
-
-            if ((executors != null) && (!executors.isShutdown()) && shutdownExecutor) {
-                Collection<Runnable> runners = executors.shutdownNow();
-                if (log.isDebugEnabled()) {
-                    log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
-                }
-            }
-
-            executors = null;
-
-            try {
-                fileSystem.close();
-            } catch (UnsupportedOperationException e) {
-                // Ignore
-            } catch (IOException e) {
-                log.debug("Error closing FileSystem", e);
-            }
-        }
-    }
-
-    private Path resolveFile(String path) {
-        //in case we are running on Windows
-        String localPath = SelectorUtils.translateToLocalPath(path);
-        return defaultDir.resolve(localPath);
-    }
-
-    private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
-            "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-
-    /**
-     * Get unix style date string.
-     */
-    private static String getUnixDate(FileTime time) {
-        return getUnixDate(time != null ? time.toMillis() : -1);
-    }
-
-    private static String getUnixDate(long millis) {
-        if (millis < 0) {
-            return "------------";
-        }
-
-        StringBuilder sb = new StringBuilder(16);
-        Calendar cal = new GregorianCalendar();
-        cal.setTimeInMillis(millis);
-
-        // month
-        sb.append(MONTHS[cal.get(Calendar.MONTH)]);
-        sb.append(' ');
-
-        // day
-        int day = cal.get(Calendar.DATE);
-        if (day < 10) {
-            sb.append(' ');
-        }
-        sb.append(day);
-        sb.append(' ');
-
-        long sixMonth = 15811200000L; // 183L * 24L * 60L * 60L * 1000L;
-        long nowTime = System.currentTimeMillis();
-        if (Math.abs(nowTime - millis) > sixMonth) {
-
-            // year
-            int year = cal.get(Calendar.YEAR);
-            sb.append(' ');
-            sb.append(year);
-        } else {
-
-            // hour
-            int hh = cal.get(Calendar.HOUR_OF_DAY);
-            if (hh < 10) {
-                sb.append('0');
-            }
-            sb.append(hh);
-            sb.append(':');
-
-            // minute
-            int mm = cal.get(Calendar.MINUTE);
-            if (mm < 10) {
-                sb.append('0');
-            }
-            sb.append(mm);
-        }
-        return sb.toString();
-    }
-
-    protected static class PrincipalBase implements Principal {
-        private final String name;
-
-        public PrincipalBase(String name) {
-            if (name == null) {
-                throw new IllegalArgumentException("name is null");
-            }
-            this.name = name;
-        }
-
-        @Override
-        public final String getName() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if ((o == null) || (getClass() != o.getClass())) {
-                return false;
-            }
-
-            Principal that = (Principal) o;
-            if (Objects.equals(getName(),that.getName())) {
-                return true;
-            } else {
-                return false;    // debug breakpoint
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(getName());
-        }
-
-        @Override
-        public String toString() {
-            return getName();
-        }
-    }
-
-    protected static class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
-        public DefaultUserPrincipal(String name) {
-            super(name);
-        }
-    }
-
-    protected static class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
-        public DefaultGroupPrincipal(String name) {
-            super(name);
-        }
-    }
-}


[03/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/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
new file mode 100644
index 0000000..c6baa5c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -0,0 +1,2280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.*;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryFlag;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.file.FileSystemAware;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * SFTP subsystem
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpSubsystem extends AbstractLoggingBean implements Command, Runnable, SessionAware, FileSystemAware {
+
+    /**
+     * Properties key for the maximum of available open handles per session.
+     */
+    public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
+
+    /**
+     * Force the use of a given sftp version
+     */
+    public static final String SFTP_VERSION = "sftp-version";
+
+    public static final int LOWER_SFTP_IMPL = SFTP_V3; // Working implementation from v3
+    public static final int HIGHER_SFTP_IMPL = SFTP_V6; //  .. up to
+    public static final String ALL_SFTP_IMPL;
+    public static final int  MAX_PACKET_LENGTH = 1024 * 16;
+
+    static {
+        StringBuilder sb = new StringBuilder(2 * (1 + (HIGHER_SFTP_IMPL - LOWER_SFTP_IMPL)));
+        for (int v = LOWER_SFTP_IMPL; v <= HIGHER_SFTP_IMPL; v++) {
+            if (sb.length() > 0) {
+                sb.append(',');
+            }
+            sb.append(v);
+        }
+        ALL_SFTP_IMPL = sb.toString();
+    }
+
+    private ExitCallback callback;
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private Environment env;
+    private ServerSession session;
+    private boolean closed = false;
+	private ExecutorService executors;
+	private boolean shutdownExecutor;
+	private Future<?> pendingFuture;
+
+    private FileSystem fileSystem = FileSystems.getDefault();
+    private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
+
+    private int version;
+    private final Map<String, byte[]> extensions = new HashMap<>();
+    private final Map<String, Handle> handles = new HashMap<>();
+
+    private final UnsupportedAttributePolicy unsupportedAttributePolicy;
+
+    protected static abstract class Handle implements java.io.Closeable {
+        private Path file;
+
+        public Handle(Path file) {
+            this.file = file;
+        }
+
+        public Path getFile() {
+            return file;
+        }
+
+        @Override
+        public void close() throws IOException {
+            // ignored
+        }
+
+        @Override
+        public String toString() {
+            return Objects.toString(getFile());
+        }
+    }
+
+    protected static class DirectoryHandle extends Handle implements Iterator<Path> {
+        private boolean done;
+        // the directory should be read once at "open directory"
+        private DirectoryStream<Path> ds;
+        private Iterator<Path> fileList;
+
+        public DirectoryHandle(Path file) throws IOException {
+            super(file);
+            ds = Files.newDirectoryStream(file);
+            fileList = ds.iterator();
+        }
+
+        public boolean isDone() {
+            return done;
+        }
+
+        public void setDone(boolean done) {
+            this.done = done;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return fileList.hasNext();
+        }
+
+        @Override
+        public Path next() {
+            return fileList.next();
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("Not allowed to remove " + toString());
+        }
+
+        public void clearFileList() {
+            // allow the garbage collector to do the job
+            fileList = null;
+        }
+
+        @Override
+        public void close() throws IOException {
+            ds.close();
+        }
+    }
+
+    protected class FileHandle extends Handle {
+        private final FileChannel channel;
+        private long pos;
+        private final List<FileLock> locks = new ArrayList<>();
+
+        public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
+            super(file);
+            Set<OpenOption> options = new HashSet<>();
+            if ((access & ACE4_READ_DATA) != 0 || (access & ACE4_READ_ATTRIBUTES) != 0) {
+                options.add(StandardOpenOption.READ);
+            }
+            if ((access & ACE4_WRITE_DATA) != 0 || (access & ACE4_WRITE_ATTRIBUTES) != 0) {
+                options.add(StandardOpenOption.WRITE);
+            }
+            switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
+            case SSH_FXF_CREATE_NEW:
+                options.add(StandardOpenOption.CREATE_NEW);
+                break;
+            case SSH_FXF_CREATE_TRUNCATE:
+                options.add(StandardOpenOption.CREATE);
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+                break;
+            case SSH_FXF_OPEN_EXISTING:
+                break;
+            case SSH_FXF_OPEN_OR_CREATE:
+                options.add(StandardOpenOption.CREATE);
+                break;
+            case SSH_FXF_TRUNCATE_EXISTING:
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+                break;
+            default:    // ignored
+            }
+            if ((flags & SSH_FXF_APPEND_DATA) != 0) {
+                options.add(StandardOpenOption.APPEND);
+            }
+            FileAttribute<?>[] attributes = new FileAttribute<?>[attrs.size()];
+            int index = 0;
+            for (Map.Entry<String, Object> attr : attrs.entrySet()) {
+                final String key = attr.getKey();
+                final Object val = attr.getValue();
+                attributes[index++] = new FileAttribute<Object>() {
+                    @Override
+                    public String name() {
+                        return key;
+                    }
+
+                    @Override
+                    public Object value() {
+                        return val;
+                    }
+                };
+            }
+            FileChannel channel;
+            try {
+                  channel = FileChannel.open(file, options, attributes);
+            } catch (UnsupportedOperationException e) {
+                channel = FileChannel.open(file, options);
+                setAttributes(file, attrs);
+            }
+            this.channel = channel;
+            this.pos = 0;
+        }
+
+        public int read(byte[] data, long offset) throws IOException {
+            return read(data, 0, data.length, offset);
+        }
+
+        public int read(byte[] data, int doff, int length, long offset) throws IOException {
+            if (pos != offset) {
+                channel.position(offset);
+                pos = offset;
+            }
+            int read = channel.read(ByteBuffer.wrap(data, doff, length));
+            pos += read;
+            return read;
+        }
+
+        public void write(byte[] data, long offset) throws IOException {
+            write(data, 0, data.length, offset);
+        }
+
+        public void write(byte[] data, int doff, int length, long offset) throws IOException {
+            if (pos != offset) {
+                channel.position(offset);
+                pos = offset;
+            }
+            channel.write(ByteBuffer.wrap(data, doff, length));
+            pos += length;
+        }
+
+        @Override
+        public void close() throws IOException {
+            channel.close();
+        }
+
+        public void lock(long offset, long length, int mask) throws IOException {
+            long size = length == 0 ? channel.size() - offset : length;
+            FileLock lock = channel.tryLock(offset, size, false);
+            synchronized (locks) {
+                locks.add(lock);
+            }
+        }
+
+        public boolean unlock(long offset, long length) throws IOException {
+            long size = length == 0 ? channel.size() - offset : length;
+            FileLock lock = null;
+            for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) {
+                FileLock l = iterator.next();
+                if (l.position() == offset && l.size() == size) {
+                    iterator.remove();
+                    lock = l;
+                    break;
+                }
+            }
+            if (lock != null) {
+                lock.release();
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * @param executorService The {@link ExecutorService} to be used by
+     *                        the {@link SftpSubsystem} command when starting execution. If
+     *                        {@code null} then a single-threaded ad-hoc service is used.
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when subsystem terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @param policy The {@link UnsupportedAttributePolicy} to use if failed to access
+     * some local file attributes
+     * @see ThreadUtils#newSingleThreadExecutor(String)
+     */
+    public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy) {
+        if ((executors = executorService) == null) {
+            executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
+            shutdownExecutor = true;    // we always close the ad-hoc executor service
+        } else {
+            shutdownExecutor = shutdownOnExit;
+        }
+        
+        if ((unsupportedAttributePolicy=policy) == null) {
+            throw new IllegalArgumentException("No policy provided");
+        }
+    }
+
+    public final UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
+        return unsupportedAttributePolicy;
+    }
+
+    @Override
+    public void setSession(ServerSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public void setFileSystem(FileSystem fileSystem) {
+        if (fileSystem != this.fileSystem) {
+            this.fileSystem = fileSystem;
+            this.defaultDir = fileSystem.getRootDirectories().iterator().next();
+        }
+    }
+
+    @Override
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    @Override
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    @Override
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    @Override
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+    }
+
+    @Override
+    public void start(Environment env) throws IOException {
+        this.env = env;
+        try {
+            pendingFuture = executors.submit(this);
+        } catch (RuntimeException e) {    // e.g., RejectedExecutionException
+            log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.toString(), e);
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public void run() {
+        DataInputStream dis = null;
+        try {
+            dis = new DataInputStream(in);
+            while (true) {
+                int length = dis.readInt();
+                if (length < 5) {
+                    throw new IllegalArgumentException("Bad length to read: " + length);
+                }
+                Buffer buffer = new ByteArrayBuffer(length + 4);
+                buffer.putInt(length);
+                int nb = length;
+                while (nb > 0) {
+                    int l = dis.read(buffer.array(), buffer.wpos(), nb);
+                    if (l < 0) {
+                        throw new IllegalArgumentException("Premature EOF while read length=" + length + " while remain=" + nb);
+                    }
+                    buffer.wpos(buffer.wpos() + l);
+                    nb -= l;
+                }
+                process(buffer);
+            }
+        } catch (Throwable t) {
+            if (!closed && !(t instanceof EOFException)) { // Ignore
+                log.error("Exception caught in SFTP subsystem", t);
+            }
+        } finally {
+            if (dis != null) {
+                try {
+                    dis.close();
+                } catch (IOException ioe) {
+                    log.error("Could not close DataInputStream", ioe);
+                }
+            }
+
+            if (handles != null) {
+                for (Map.Entry<String, Handle> entry : handles.entrySet()) {
+                    Handle handle = entry.getValue();
+                    try {
+                        handle.close();
+                    } catch (IOException ioe) {
+                        log.error("Could not close open handle: " + entry.getKey(), ioe);
+                    }
+                }
+            }
+            callback.onExit(0);
+        }
+    }
+
+    protected void process(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (log.isDebugEnabled()) {
+            log.debug("process(length={}, type={}, id={})",
+                      new Object[] { Integer.valueOf(length), Integer.valueOf(type), Integer.valueOf(id) });
+        }
+
+        switch (type) {
+            case SSH_FXP_INIT:
+                doInit(buffer, id);
+                break;
+            case SSH_FXP_OPEN:
+                doOpen(buffer, id);
+                break;
+            case SSH_FXP_CLOSE:
+                doClose(buffer, id);
+                break;
+            case SSH_FXP_READ:
+                doRead(buffer, id);
+                break;
+            case SSH_FXP_WRITE:
+                doWrite(buffer, id);
+                break;
+            case SSH_FXP_LSTAT:
+                doLStat(buffer, id);
+                break;
+            case SSH_FXP_FSTAT:
+                doFStat(buffer, id);
+                break;
+            case SSH_FXP_SETSTAT:
+                doSetStat(buffer, id);
+                break;
+            case SSH_FXP_FSETSTAT:
+                doFSetStat(buffer, id);
+                break;
+            case SSH_FXP_OPENDIR:
+                doOpenDir(buffer, id);
+                break;
+            case SSH_FXP_READDIR:
+                doReadDir(buffer, id);
+                break;
+            case SSH_FXP_REMOVE:
+                doRemove(buffer, id);
+                break;
+            case SSH_FXP_MKDIR:
+                doMakeDirectory(buffer, id);
+                break;
+            case SSH_FXP_RMDIR:
+                doRemoveDirectory(buffer, id);
+                break;
+            case SSH_FXP_REALPATH:
+                doRealPath(buffer, id);
+                break;
+            case SSH_FXP_STAT:
+                doStat(buffer, id);
+                break;
+            case SSH_FXP_RENAME:
+                doRename(buffer, id);
+                break;
+            case SSH_FXP_READLINK:
+                doReadLink(buffer, id);
+                break;
+            case SSH_FXP_SYMLINK:
+                doSymLink(buffer, id);
+                break;
+            case SSH_FXP_LINK:
+                doLink(buffer, id);
+                break;
+            case SSH_FXP_BLOCK:
+                doBlock(buffer, id);
+                break;
+            case SSH_FXP_UNBLOCK:
+                doUnblock(buffer, id);
+                break;
+            case SSH_FXP_EXTENDED:
+                doExtended(buffer, id);
+                break;
+            default:
+                log.warn("Unknown command type received: {}", Integer.valueOf(type));
+                sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
+        }
+    }
+
+    protected void doExtended(Buffer buffer, int id) throws IOException {
+        String extension = buffer.getString();
+        switch (extension) {
+        case "text-seek":
+            doTextSeek(buffer, id);
+            break;
+        case "version-select":
+            doVersionSelect(buffer, id);
+            break;
+        default:
+            log.info("Received unsupported SSH_FXP_EXTENDED({})", extension);
+            sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
+            break;
+        }
+    }
+
+    protected void doTextSeek(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        long line = buffer.getLong();
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}, line={})", handle, Long.valueOf(line));
+        }
+
+        // TODO : implement text-seek
+        sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(text-seek) is unsupported or not implemented");
+    }
+
+    protected void doVersionSelect(Buffer buffer, int id) throws IOException {
+        String ver = buffer.getString();
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_EXTENDED(version-select) (version={})", Integer.valueOf(version));
+        }
+        
+        if (GenericUtils.length(ver) == 1) {
+            char digit = ver.charAt(0);
+            if ((digit >= '0') && (digit <= '9')) {
+                int value = digit - '0';
+                String all = checkVersionCompatibility(id, value, SSH_FX_FAILURE);
+                if (GenericUtils.isEmpty(all)) {    // validation failed
+                    return;
+                }
+
+                version = value;
+                sendStatus(id, SSH_FX_OK, "");
+                return;
+            }
+        }
+
+        sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + ver);
+    }
+
+    /**
+     * Checks if a proposed version is within supported range. <B>Note:</B>
+     * if the user forced a specific value via the {@link #SFTP_VERSION}
+     * property, then it is used to validate the proposed value
+     * @param id The SSH message ID to be used to send the failure message
+     * if required
+     * @param proposed The proposed version value
+     * @param failureOpcode The failure opcode to send if validation fails
+     * @return A {@link String} of comma separated values representing all
+     * the supported version - {@code null} if validation failed and an
+     * appropriate status message was sent
+     * @throws IOException If failed to send the failure status message
+     */
+    protected String checkVersionCompatibility(int id, int proposed, int failureOpcode) throws IOException {
+        int low = LOWER_SFTP_IMPL;
+        int hig = HIGHER_SFTP_IMPL;
+        String available = ALL_SFTP_IMPL;
+        // check if user wants to use a specific version
+        Integer sftpVersion = FactoryManagerUtils.getInteger(session, SFTP_VERSION);
+        if (sftpVersion != null) {
+            int forcedValue = sftpVersion.intValue();
+            if ((forcedValue < LOWER_SFTP_IMPL) || (forcedValue > HIGHER_SFTP_IMPL)) {
+                throw new IllegalStateException("Forced SFTP version (" + sftpVersion + ") not within supported values: " + available);
+            }
+            low = hig = sftpVersion.intValue();
+            available = sftpVersion.toString();
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("checkVersionCompatibility(id={}) - proposed={}, available={}",
+                      new Object[] { Integer.valueOf(id), Integer.valueOf(proposed), available });
+        }
+
+        if ((proposed < low) || (proposed > hig)) {
+            sendStatus(id, failureOpcode, "Proposed version (" + proposed + ") not in supported range: " + available);
+            return null;
+        }
+
+        return available;
+    }
+
+    protected void doBlock(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        long offset = buffer.getLong();
+        long length = buffer.getLong();
+        int mask = buffer.getInt();
+        
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_BLOCK (handle={}, offset={}, length={}, mask=0x{})",
+                      new Object[] { handle, Long.valueOf(offset), Long.valueOf(length), Integer.toHexString(mask) });
+        }
+
+        try {
+            Handle p = handles.get(handle);
+            if (!(p instanceof FileHandle)) {
+                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                return;
+            }
+            FileHandle fileHandle = (FileHandle) p;
+            fileHandle.lock(offset, length, mask);
+            sendStatus(id, SSH_FX_OK, "");
+        } catch (IOException | OverlappingFileLockException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doUnblock(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        long offset = buffer.getLong();
+        long length = buffer.getLong();
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_UNBLOCK (handle={}, offset={}, length={})",
+                      new Object[] { handle, Long.valueOf(offset), Long.valueOf(length) });
+        }
+
+        try {
+            Handle p = handles.get(handle);
+            if (!(p instanceof FileHandle)) {
+                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                return;
+            }
+            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 void doLink(Buffer buffer, int id) throws IOException {
+        String targetpath = buffer.getString();
+        String linkpath = buffer.getString();
+        boolean symLink = buffer.getBoolean();
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})",
+                      new Object[] { 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) {
+            sendStatus(id, e);
+        }
+    }
+
+    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);
+        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) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doReadLink(Buffer buffer, int id) throws IOException {
+        String path = buffer.getString();
+        log.debug("Received SSH_FXP_READLINK (path={})", path);
+        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) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doRename(Buffer buffer, int id) throws IOException {
+        String oldPath = buffer.getString();
+        String newPath = buffer.getString();
+        int flags = 0;
+        if (version >= SFTP_V5) {
+            flags = buffer.getInt();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})",
+                       new Object[] { oldPath, newPath, Integer.toHexString(flags) });
+        }
+        try {
+            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);
+            }
+            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);
+        }
+    }
+
+    protected void doStat(Buffer buffer, int id) throws IOException {
+        String path = buffer.getString();
+        int flags = SSH_FILEXFER_ATTR_ALL;
+        if (version >= SFTP_V4) {
+            flags = buffer.getInt();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_STAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
+        }
+        try {
+            Path p = resolveFile(path);
+            sendAttrs(id, p, flags, true);
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doRealPath(Buffer buffer, int id) throws IOException {
+        String path = buffer.getString();
+        log.debug("Received SSH_FXP_REALPATH (path={})", path);
+        path = GenericUtils.trimToEmpty(path);
+        if (GenericUtils.isEmpty(path)) {
+            path = ".";
+        }
+
+        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());
+            } else {
+                // Read control byte
+                int control = 0;
+                if (buffer.available() > 0) {
+                    control = buffer.getByte();
+                }
+                List<String> paths = new ArrayList<>();
+                while (buffer.available() > 0) {
+                    paths.add(buffer.getString());
+                }
+                // Resolve path
+                Path p = resolveFile(path);
+                for (String p2 : paths) {
+                    p = p.resolve(p2);
+                }
+                p = p.toAbsolutePath().normalize();
+
+                Map<String, Object> attrs = Collections.emptyMap();
+                if (control == SSH_FXP_REALPATH_STAT_IF) {
+                    try {
+                        attrs = getAttributes(p, false);
+                    } catch (IOException e) {
+                        // ignore
+                    }
+                } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
+                    attrs = getAttributes(p, false);
+                }
+                sendPath(id, p, attrs);
+            }
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected Path handleUnknownRealPathStatus(String path, Path absolute, Path normalized) throws IOException {
+        switch(unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized);
+                break;
+            case ThrowException:
+                throw new AccessDeniedException("Cannot determine existence status of real path: " + normalized);
+            
+            default:
+                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized
+                       + " - unknown policy: " + unsupportedAttributePolicy);
+        }
+        
+        return absolute;
+    }
+
+    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) {
+            sendStatus(id, e);
+        }
+    }
+
+    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={})", path);
+        // 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());
+                }
+            } else {
+                Files.createDirectory(p);
+                setAttributes(p, attrs);
+                sendStatus(id, SSH_FX_OK, "");
+            }
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    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) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doReadDir(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        log.debug("Received SSH_FXP_READDIR (handle={})", handle);
+        Handle p = handles.get(handle);
+        try {
+            if (!(p instanceof DirectoryHandle)) {
+                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                return;
+            }
+            
+            if (((DirectoryHandle) p).isDone()) {
+                sendStatus(id, SSH_FX_EOF, "", "");
+                return;
+            }
+
+            Path            file = p.getFile();
+            LinkOption[]    options = IoUtils.getLinkOptions(false);
+            Boolean         status = IoUtils.checkFileExists(file, options);
+            if (status == null) {
+                throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
+            }
+
+            if (!status.booleanValue()) {
+                sendStatus(id, SSH_FX_NO_SUCH_FILE, file.toString());
+            } else if (!Files.isDirectory(file, options)) {
+                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, file.toString());
+            } else if (!Files.isReadable(file)) {
+                sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
+            } else {
+                DirectoryHandle dh = (DirectoryHandle) p;
+                if (dh.hasNext()) {
+                    // There is at least one file in the directory.
+                    // Send only a few files at a time to not create packets of a too
+                    // large size or have a timeout to occur.
+                    sendName(id, dh);
+                    if (!dh.hasNext()) {
+                        // if no more files to send
+                        dh.setDone(true);
+                        dh.clearFileList();
+                    }
+                } else {
+                    // empty directory
+                    dh.setDone(true);
+                    dh.clearFileList();
+                    sendStatus(id, SSH_FX_EOF, "", "");
+                }
+            }
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doOpenDir(Buffer buffer, int id) throws IOException {
+        String path = buffer.getString();
+        log.debug("Received SSH_FXP_OPENDIR (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 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 = UUID.randomUUID().toString();
+                handles.put(handle, new DirectoryHandle(p));
+                sendHandle(id, handle);
+            }
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    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) {
+            sendStatus(id, e);
+        }
+    }
+
+    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) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doFStat(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        int flags = SSH_FILEXFER_ATTR_ALL;
+        if (version >= SFTP_V4) {
+            flags = buffer.getInt();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_FSTAT (handle={}, flags={})", handle, "0x" + Integer.toHexString(flags));
+        }
+        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) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doLStat(Buffer buffer, int id) throws IOException {
+        String path = buffer.getString();
+        int flags = SSH_FILEXFER_ATTR_ALL;
+        if (version >= SFTP_V4) {
+            flags = buffer.getInt();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_LSTAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
+        }
+        try {
+            Path p = resolveFile(path);
+            sendAttrs(id, p, flags, false);
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doWrite(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        long offset = buffer.getLong();
+        int length = buffer.getInt();
+        if (length < 0) {
+            throw new IllegalStateException();
+        }
+        if (buffer.available() < length) {
+            throw new BufferUnderflowException();
+        }
+        byte[] data = buffer.array();
+        int doff = buffer.rpos();
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])",
+                      new Object[] { handle, Long.valueOf(offset), Integer.valueOf(length) });
+        }
+        try {
+            Handle p = handles.get(handle);
+            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);
+        }
+    }
+
+    protected void doRead(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        long offset = buffer.getLong();
+        int len = buffer.getInt();
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})",
+                      new Object[]{handle, Long.valueOf(offset), Integer.valueOf(len) });
+        }
+        try {
+            Handle p = handles.get(handle);
+            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) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doClose(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        log.debug("Received SSH_FXP_CLOSE (handle={})", handle);
+        try {
+            Handle h = handles.get(handle);
+            if (h == null) {
+                sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
+            } else {
+                handles.remove(handle);
+                h.close();
+                sendStatus(id, SSH_FX_OK, "", "");
+            }
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doOpen(Buffer buffer, int id) throws IOException {
+        int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, Integer.MAX_VALUE);
+        if (handles.size() > maxHandleCount) {
+            sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
+            return;
+        }
+
+        String path = buffer.getString();
+        int access = 0;
+        if (version >= SFTP_V5) {
+            access = buffer.getInt();
+        }
+        int pflags = buffer.getInt();
+        if (version < SFTP_V5) {
+            int flags = pflags;
+            pflags = 0;
+            switch (flags & (SSH_FXF_READ | SSH_FXF_WRITE)) {
+            case SSH_FXF_READ:
+                access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+                break;
+            case SSH_FXF_WRITE:
+                access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+                break;
+            default:
+                access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+                access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+                break;
+            }
+            if ((flags & SSH_FXF_APPEND) != 0) {
+                access |= ACE4_APPEND_DATA;
+                pflags |= SSH_FXF_APPEND_DATA | SSH_FXF_APPEND_DATA_ATOMIC;
+            }
+            if ((flags & SSH_FXF_CREAT) != 0) {
+                if ((flags & SSH_FXF_EXCL) != 0) {
+                    pflags |= SSH_FXF_CREATE_NEW;
+                } else if ((flags & SSH_FXF_TRUNC) != 0) {
+                    pflags |= SSH_FXF_CREATE_TRUNCATE;
+                } else {
+                    pflags |= SSH_FXF_OPEN_OR_CREATE;
+                }
+            } else {
+                if ((flags & SSH_FXF_TRUNC) != 0) {
+                    pflags |= SSH_FXF_TRUNCATE_EXISTING;
+                } else {
+                    pflags |= SSH_FXF_OPEN_EXISTING;
+                }
+            }
+        }
+        Map<String, Object> attrs = readAttrs(buffer);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_OPEN (path={}, access=0x{}, pflags=0x{}, attrs={})",
+                      new Object[]{path, Integer.toHexString(access), Integer.toHexString(pflags), attrs});
+        }
+        try {
+            Path file = resolveFile(path);
+            String handle = UUID.randomUUID().toString();
+            handles.put(handle, new FileHandle(file, pflags, access, attrs));
+            sendHandle(id, handle);
+        } catch (IOException e) {
+            sendStatus(id, e);
+        }
+    }
+
+    protected void doInit(Buffer buffer, int id) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_FXP_INIT (version={})", Integer.valueOf(id));
+        }
+
+        String all = checkVersionCompatibility(id, id, SSH_FX_OP_UNSUPPORTED);
+        if (GenericUtils.isEmpty(all)) { // i.e. validation failed
+            return;
+        }
+        version = id;
+        while (buffer.available() > 0) {
+            String name = buffer.getString();
+            byte[] data = buffer.getBytes();
+            extensions.put(name, data);
+        }
+
+        buffer.clear();
+        buffer.putByte((byte) SSH_FXP_VERSION);
+        buffer.putInt(version);
+
+        // newline
+        buffer.putString("newline");
+        buffer.putString(System.getProperty("line.separator"));
+
+        // versions
+        buffer.putString("versions");
+        buffer.putString(all);
+
+        // supported
+        buffer.putString("supported");
+        buffer.putInt(5 * 4); // length of 5 integers
+        // supported-attribute-mask
+        buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
+                | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
+                | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
+                | SSH_FILEXFER_ATTR_BITS);
+        // TODO: supported-attribute-bits
+        buffer.putInt(0);
+        // supported-open-flags
+        buffer.putInt(SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
+                | SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_EXCL);
+        // TODO: supported-access-mask
+        buffer.putInt(0);
+        // max-read-size
+        buffer.putInt(0);
+
+        // supported2
+        buffer.putString("supported2");
+        buffer.putInt(8 * 4); // length of 7 integers + 2 shorts
+        // supported-attribute-mask
+        buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
+                | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
+                | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
+                | SSH_FILEXFER_ATTR_BITS);
+        // TODO: supported-attribute-bits
+        buffer.putInt(0);
+        // supported-open-flags
+        buffer.putInt(SSH_FXF_ACCESS_DISPOSITION | SSH_FXF_APPEND_DATA);
+        // TODO: supported-access-mask
+        buffer.putInt(0);
+        // max-read-size
+        buffer.putInt(0);
+        // supported-open-block-vector
+        buffer.putShort(0);
+        // supported-block-vector
+        buffer.putShort(0);
+        // attrib-extension-count
+        buffer.putInt(0);
+        // extension-count
+        buffer.putInt(0);
+
+        /*
+        buffer.putString("acl-supported");
+        buffer.putInt(4);
+        // capabilities
+        buffer.putInt(0);
+        */
+
+        send(buffer);
+    }
+
+    protected void sendHandle(int id, String handle) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        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();
+        buffer.putByte((byte) SSH_FXP_ATTRS);
+        buffer.putInt(id);
+        writeAttrs(buffer, file, flags, followLinks);
+        send(buffer);
+    }
+
+    protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putByte((byte) SSH_FXP_NAME);
+        buffer.putInt(id);
+        buffer.putInt(1);
+
+        String originalPath = f.toString();
+        //in case we are running on Windows
+        String unixPath = originalPath.replace(File.separatorChar, '/');
+        //normalize the given path, use *nix style separator
+        String normalizedPath = SelectorUtils.normalizePath(unixPath, "/");
+        if (normalizedPath.length() == 0) {
+            normalizedPath = "/";
+        }
+        buffer.putString(normalizedPath, StandardCharsets.UTF_8);
+
+        if (version == SFTP_V3) {
+            f = resolveFile(normalizedPath);
+            buffer.putString(getLongName(f, attrs), StandardCharsets.UTF_8); // Format specified in the specs
+            buffer.putInt(0);
+        } else if (version >= SFTP_V4) {
+            writeAttrs(buffer, attrs);
+        } else {
+            throw new IllegalStateException("sendPath(" + f + ") unsupported version: " + version);
+        }
+        send(buffer);
+    }
+
+    protected void sendLink(int id, String link) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putByte((byte) SSH_FXP_NAME);
+        buffer.putInt(id);
+        buffer.putInt(1);
+        //normalize the given path, use *nix style separator
+        buffer.putString(link);
+        buffer.putString(link);
+        buffer.putInt(0);
+        send(buffer);
+    }
+
+    protected void sendName(int id, Iterator<Path> 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;
+        while (files.hasNext() && (buffer.wpos() < MAX_PACKET_LENGTH)) {
+            Path    f = files.next();
+            String  shortName = getShortName(f);
+            buffer.putString(shortName, StandardCharsets.UTF_8);
+            if (version == SFTP_V3) {
+                String  longName = getLongName(f);
+                buffer.putString(longName, StandardCharsets.UTF_8); // Format specified in the specs
+                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++;
+        }
+
+        int oldpos = buffer.wpos();
+        buffer.wpos(wpos);
+        buffer.putInt(nb);
+        buffer.wpos(oldpos);
+        send(buffer);
+    }
+
+    private String getLongName(Path f) throws IOException {
+        return getLongName(f, true);
+    }
+
+    private String getLongName(Path f, boolean sendAttrs) throws IOException {
+        Map<String, Object> attributes;
+        if (sendAttrs) {
+            attributes = getAttributes(f, false);
+        } else {
+            attributes = Collections.emptyMap();
+        }
+        return getLongName(f, attributes);
+    }
+
+    private String getLongName(Path f, Map<String, Object> attributes) throws IOException {
+        String username;
+        if (attributes.containsKey("owner")) {
+            username = attributes.get("owner").toString();
+        } else {
+            username = "owner";
+        }
+        if (username.length() > 8) {
+            username = username.substring(0, 8);
+        } else {
+            for (int i = username.length(); i < 8; i++) {
+                username = username + " ";
+            }
+        }
+        String group;
+        if (attributes.containsKey("group")) {
+            group = attributes.get("group").toString();
+        } else {
+            group = "group";
+        }
+        if (group.length() > 8) {
+            group = group.substring(0, 8);
+        } else {
+            for (int i = group.length(); i < 8; i++) {
+                group = group + " ";
+            }
+        }
+
+        Number length = (Number) attributes.get("size");
+        if (length == null) {
+            length = Long.valueOf(0L);
+        }
+        String lengthString = String.format("%1$8s", length);
+
+        Boolean isDirectory = (Boolean) attributes.get("isDirectory");
+        Boolean isLink = (Boolean) attributes.get("isSymbolicLink");
+        @SuppressWarnings("unchecked")
+        Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+        if (perms == null) {
+            perms = EnumSet.noneOf(PosixFilePermission.class);
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append((isDirectory != null && isDirectory.booleanValue()) ? "d" : (isLink != null && isLink.booleanValue()) ? "l" : "-");
+        sb.append(PosixFilePermissions.toString(perms));
+        sb.append("  ");
+        sb.append(attributes.containsKey("nlink") ? attributes.get("nlink") : "1");
+        sb.append(" ");
+        sb.append(username);
+        sb.append(" ");
+        sb.append(group);
+        sb.append(" ");
+        sb.append(lengthString);
+        sb.append(" ");
+        sb.append(getUnixDate((FileTime) attributes.get("lastModifiedTime")));
+        sb.append(" ");
+        sb.append(getShortName(f));
+
+        return sb.toString();
+    }
+
+    protected String getShortName(Path f) {
+        if (OsUtils.isUNIX()) {
+            Path    name=f.getFileName();
+            if (name == null) {
+                Path    p=resolveFile(".");
+                name = p.getFileName();
+            }
+            
+            return name.toString();
+        } else {    // need special handling for Windows root drives
+            Path    abs=f.toAbsolutePath().normalize();
+            int     count=abs.getNameCount();
+            /*
+             * According to the javadoc:
+             * 
+             *      The number of elements in the path, or 0 if this path only
+             *      represents a root component
+             */
+            if (count > 0) {
+                Path    name=abs.getFileName();
+                return name.toString();
+            } else {
+                return abs.toString().replace(File.separatorChar, '/');
+            }
+        }
+    }
+
+    protected int attributesToPermissions(boolean isReg, boolean isDir, boolean isLnk, Collection<PosixFilePermission> perms) {
+        int pf = 0;
+        if (perms != null) {
+            for (PosixFilePermission p : perms) {
+                switch (p) {
+                case OWNER_READ:
+                    pf |= S_IRUSR;
+                    break;
+                case OWNER_WRITE:
+                    pf |= S_IWUSR;
+                    break;
+                case OWNER_EXECUTE:
+                    pf |= S_IXUSR;
+                    break;
+                case GROUP_READ:
+                    pf |= S_IRGRP;
+                    break;
+                case GROUP_WRITE:
+                    pf |= S_IWGRP;
+                    break;
+                case GROUP_EXECUTE:
+                    pf |= S_IXGRP;
+                    break;
+                case OTHERS_READ:
+                    pf |= S_IROTH;
+                    break;
+                case OTHERS_WRITE:
+                    pf |= S_IWOTH;
+                    break;
+                case OTHERS_EXECUTE:
+                    pf |= S_IXOTH;
+                    break;
+                default: // ignored
+                }
+            }
+        }
+        pf |= isReg ? S_IFREG : 0;
+        pf |= isDir ? S_IFDIR : 0;
+        pf |= isLnk ? S_IFLNK : 0;
+        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;
+        if (status == null) {
+            attributes = handleUnknownStatusFileAttributes(file, flags, followLinks);
+        } else if (!status.booleanValue()) {
+            throw new FileNotFoundException(file.toString());
+        } else {
+            attributes = getAttributes(file, flags, followLinks);
+        }
+
+        writeAttrs(buffer, attributes);
+    }
+
+    protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
+        boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+        boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+        boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
+        @SuppressWarnings("unchecked")
+        Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) attributes.get("permissions");
+        Number size = (Number) attributes.get("size");
+        FileTime lastModifiedTime = (FileTime) attributes.get("lastModifiedTime");
+        FileTime lastAccessTime = (FileTime) attributes.get("lastAccessTime");
+
+        if (version == SFTP_V3) {
+            int flags =
+                    ((isReg || isLnk) && (size != null) ? SSH_FILEXFER_ATTR_SIZE : 0) |
+                    (attributes.containsKey("uid") && attributes.containsKey("gid") ? SSH_FILEXFER_ATTR_UIDGID : 0) |
+                    ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+                    (((lastModifiedTime != null) && (lastAccessTime != null)) ? SSH_FILEXFER_ATTR_ACMODTIME : 0);
+            buffer.putInt(flags);
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                buffer.putLong(size.longValue());
+            }
+            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+                buffer.putInt(((Number) attributes.get("uid")).intValue());
+                buffer.putInt(((Number) attributes.get("gid")).intValue());
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+                buffer.putInt(lastAccessTime.to(TimeUnit.SECONDS));
+                buffer.putInt(lastModifiedTime.to(TimeUnit.SECONDS));
+            }
+        } else if (version >= SFTP_V4) {
+            FileTime creationTime = (FileTime) attributes.get("creationTime");
+            int flags = (((isReg || isLnk) && (size != null)) ? SSH_FILEXFER_ATTR_SIZE : 0) |
+                        ((attributes.containsKey("owner") && attributes.containsKey("group")) ? SSH_FILEXFER_ATTR_OWNERGROUP : 0) |
+                        ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+                        ((lastModifiedTime != null) ? SSH_FILEXFER_ATTR_MODIFYTIME : 0) |
+                        ((creationTime != null) ? SSH_FILEXFER_ATTR_CREATETIME : 0) |
+                        ((lastAccessTime != null) ? SSH_FILEXFER_ATTR_ACCESSTIME : 0);
+            buffer.putInt(flags);
+            buffer.putByte((byte) (isReg ? SSH_FILEXFER_TYPE_REGULAR :
+                    isDir ? SSH_FILEXFER_TYPE_DIRECTORY :
+                            isLnk ? SSH_FILEXFER_TYPE_SYMLINK :
+                                    SSH_FILEXFER_TYPE_UNKNOWN));
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                buffer.putLong(size.longValue());
+            }
+            if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+                buffer.putString(attributes.get("owner").toString(), StandardCharsets.UTF_8);
+                buffer.putString(attributes.get("group").toString(), StandardCharsets.UTF_8);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
+            }
+
+            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+                putFileTime(buffer, flags, lastAccessTime);
+            }
+
+            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+                putFileTime(buffer, flags, lastAccessTime);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+                putFileTime(buffer, flags, lastModifiedTime);
+            }
+            // TODO: acls
+            // TODO: bits
+            // TODO: extended
+        }
+    }
+
+    protected void putFileTime(Buffer buffer, int flags, FileTime time) {
+        buffer.putLong(time.to(TimeUnit.SECONDS));
+        if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+            long nanos = time.to(TimeUnit.NANOSECONDS);
+            nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+            buffer.putInt((int) nanos);
+        }
+    }
+
+    protected boolean getBool(Boolean bool) {
+        return (bool != null) && bool.booleanValue();
+    }
+
+    protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
+        return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
+    }
+
+    public static final List<String>    DEFAULT_UNIX_VIEW=Collections.singletonList("unix:*");
+
+    protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
+        switch(unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case ThrowException:
+                throw new AccessDeniedException("Cannot determine existence for attributes of " + file);
+            case Warn:
+                log.warn("handleUnknownStatusFileAttributes(" + file + ") cannot determine existence");
+                break;
+            default:
+                log.warn("handleUnknownStatusFileAttributes(" + file + ") unknown policy: " + unsupportedAttributePolicy);
+        }
+        
+        return getAttributes(file, flags, followLinks);
+    }
+
+    protected Map<String, Object> getAttributes(Path file, int flags, boolean followLinks) throws IOException {
+        FileSystem          fs=file.getFileSystem();
+        Collection<String>  supportedViews=fs.supportedFileAttributeViews();
+        LinkOption[]        opts=IoUtils.getLinkOptions(followLinks);
+        Map<String,Object>  attrs=new HashMap<>();
+        Collection<String>  views;
+
+        if (GenericUtils.isEmpty(supportedViews)) {
+            views = Collections.<String>emptyList();
+        } else if (supportedViews.contains("unix")) {
+            views = DEFAULT_UNIX_VIEW;
+        } else {
+            views = new ArrayList<String>(supportedViews.size());
+            for (String v : supportedViews) {
+                views.add(v + ":*");
+            }
+        }
+
+        for (String v : views) {
+            Map<String, Object> ta=readFileAttributes(file, v, opts);
+            attrs.putAll(ta);
+        }
+
+        // if did not get permissions from the supported views return a best approximation
+        if (!attrs.containsKey("permissions")) {
+            Set<PosixFilePermission> perms=IoUtils.getPermissionsFromFile(file.toFile());
+            attrs.put("permissions", perms);
+        }
+
+        return attrs;
+    }
+
+    protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... opts) throws IOException {
+        try {
+            return Files.readAttributes(file, view, opts);
+        } catch(IOException e) {
+            return handleReadFileAttributesException(file, view, opts, e);
+        }
+    }
+
+    protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] opts, IOException e) throws IOException {
+        switch(unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("handleReadFileAttributesException(" + file + ")[" + view + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
+                break;
+            case ThrowException:
+                throw e;
+            default:
+                log.warn("handleReadFileAttributesException(" + file + ")[" + view + "]"
+                       + " Unknown policy (" + unsupportedAttributePolicy + ")"
+                       + " for " + e.getClass().getSimpleName() + ": " + e.getMessage());
+        }
+        
+        return Collections.emptyMap();
+    }
+
+    protected void setAttributes(Path file, Map<String, Object>  attributes) throws IOException {
+        Set<String> unsupported = new HashSet<>();
+        for (String attribute : attributes.keySet()) {
+            String view = null;
+            Object value = attributes.get(attribute);
+            switch (attribute) {
+            case "size": {
+                long newSize = ((Number) value).longValue();
+                try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
+                    channel.truncate(newSize);
+                }
+                continue;
+            }
+            case "uid":
+                view = "unix";
+                break;
+            case "gid":
+                view = "unix";
+                break;
+            case "owner":
+                view = "posix";
+                value = toUser(file, (UserPrincipal) value);
+                break;
+            case "group":
+                view = "posix";
+                value = toGroup(file, (GroupPrincipal) value);
+                break;
+            case "permissions":
+                if (OsUtils.isWin32()) {
+                    @SuppressWarnings("unchecked")
+                    Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) value;
+                    IoUtils.setPermissionsToFile(file.toFile(), perms);
+                    continue;
+                }
+                view = "posix";
+                break;
+
+            case "creationTime":
+                view = "basic";
+                break;
+            case "lastModifiedTime":
+                view = "basic";
+                break;
+            case "lastAccessTime":
+                view = "basic";
+                break;
+            default:    // ignored
+            }
+            if (view != null && value != null) {
+                try {
+                    Files.setAttribute(file, view + ":" + attribute, value, IoUtils.getLinkOptions(false));
+                } catch (UnsupportedOperationException e) {
+                    unsupported.add(attribute);
+                }
+            }
+        }
+        handleUnsupportedAttributes(unsupported);
+    }
+
+    protected void handleUnsupportedAttributes(Collection<String> attributes) {
+        if (!attributes.isEmpty()) {
+            StringBuilder sb = new StringBuilder();
+            for (String attr : attributes) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(attr);
+            }
+            switch (unsupportedAttributePolicy) {
+                case Ignore:
+                    break;
+                case Warn:
+                    log.warn("Unsupported attributes: " + sb.toString());
+                    break;
+                case ThrowException:
+                    throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
+                default:
+                    log.warn("Unknown policy for attributes=" + sb.toString() + ": " + unsupportedAttributePolicy);
+            }
+        }
+    }
+
+    private GroupPrincipal toGroup(Path file, GroupPrincipal name) throws IOException {
+        String groupName = name.toString();
+        FileSystem fileSystem = file.getFileSystem();
+        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
+        try {
+            return lookupService.lookupPrincipalByGroupName(groupName);
+        } catch (IOException e) {
+            handleUserPrincipalLookupServiceException(GroupPrincipal.class, groupName, e);
+            return null;
+        }
+    }
+
+    private UserPrincipal toUser(Path file, UserPrincipal name) throws IOException {
+        String username = name.toString();
+        FileSystem fileSystem = file.getFileSystem();
+        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
+        try {
+            return lookupService.lookupPrincipalByName(username);
+        } catch (IOException e) {
+            handleUserPrincipalLookupServiceException(UserPrincipal.class, username, e);
+            return null;
+        }
+    }
+
+    protected void handleUserPrincipalLookupServiceException(Class<? extends Principal> principalType, String name, IOException e) throws IOException {
+        /* According to Javadoc:
+         * 
+         *      "Where an implementation does not support any notion of group
+         *      or user then this method always throws UserPrincipalNotFoundException."
+         */
+        switch (unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("handleUserPrincipalLookupServiceException(" + principalType.getSimpleName() + "[" + name + "])"
+                       + " failed (" + e.getClass().getSimpleName() + "): " + e.getMessage());
+                break;
+            case ThrowException:
+                throw e;
+            default:
+                log.warn("Unknown policy for principal=" + principalType.getSimpleName() + "[" + name + "]: " + unsupportedAttributePolicy);
+        }
+    }
+
+    private Set<PosixFilePermission> permissionsToAttributes(int perms) {
+        Set<PosixFilePermission> p = new HashSet<>();
+        if ((perms & S_IRUSR) != 0) {
+            p.add(PosixFilePermission.OWNER_READ);
+        }
+        if ((perms & S_IWUSR) != 0) {
+            p.add(PosixFilePermission.OWNER_WRITE);
+        }
+        if ((perms & S_IXUSR) != 0) {
+            p.add(PosixFilePermission.OWNER_EXECUTE);
+        }
+        if ((perms & S_IRGRP) != 0) {
+            p.add(PosixFilePermission.GROUP_READ);
+        }
+        if ((perms & S_IWGRP) != 0) {
+            p.add(PosixFilePermission.GROUP_WRITE);
+        }
+        if ((perms & S_IXGRP) != 0) {
+            p.add(PosixFilePermission.GROUP_EXECUTE);
+        }
+        if ((perms & S_IROTH) != 0) {
+            p.add(PosixFilePermission.OTHERS_READ);
+        }
+        if ((perms & S_IWOTH) != 0) {
+            p.add(PosixFilePermission.OTHERS_WRITE);
+        }
+        if ((perms & S_IXOTH) != 0) {
+            p.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+        return p;
+    }
+
+    protected Map<String, Object> readAttrs(Buffer buffer) throws IOException {
+        Map<String, Object> attrs = new HashMap<>();
+        int flags = buffer.getInt();
+        if (version >= SFTP_V4) {
+            byte type = buffer.getByte();
+            switch (type) {
+            case SSH_FILEXFER_TYPE_REGULAR:
+                attrs.put("isRegular", Boolean.TRUE);
+                break;
+            case SSH_FILEXFER_TYPE_DIRECTORY:
+                attrs.put("isDirectory", Boolean.TRUE);
+                break;
+            case SSH_FILEXFER_TYPE_SYMLINK:
+                attrs.put("isSymbolicLink", Boolean.TRUE);
+                break;
+            case SSH_FILEXFER_TYPE_UNKNOWN:
+                attrs.put("isOther", Boolean.TRUE);
+                break;
+            default:    // ignored
+            }
+        }
+        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+            attrs.put("size", Long.valueOf(buffer.getLong()));
+        }
+        if ((flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) {
+            attrs.put("allocationSize", Long.valueOf(buffer.getLong()));
+        }
+        if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+            attrs.put("uid", Integer.valueOf(buffer.getInt()));
+            attrs.put("gid", Integer.valueOf(buffer.getInt()));
+        }
+        if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+            attrs.put("owner", new DefaultGroupPrincipal(buffer.getString()));
+            attrs.put("group", new DefaultGroupPrincipal(buffer.getString()));
+        }
+        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+            attrs.put("permissions", permissionsToAttributes(buffer.getInt()));
+        }
+        if (version == SFTP_V3) {
+            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+                attrs.put("lastAccessTime", readTime(buffer, flags));
+                attrs.put("lastModifiedTime", readTime(buffer, flags));
+            }
+        } else if (version >= SFTP_V4) {
+            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+                attrs.put("lastAccessTime", readTime(buffer, flags));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+                attrs.put("creationTime", readTime(buffer, flags));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+                attrs.put("lastModifiedTime", readTime(buffer, flags));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_CTIME) != 0) {
+                attrs.put("ctime", readTime(buffer, flags));
+            }
+        }
+        if ((flags & SSH_FILEXFER_ATTR_ACL) != 0) {
+            int count = buffer.getInt();
+            List<AclEntry> acls = new ArrayList<>();
+            for (int i = 0; i < count; i++) {
+                int aclType = buffer.getInt();
+                int aclFlag = buffer.getInt();
+                int aclMask = buffer.getInt();
+                String aclWho = buffer.getString();
+                acls.add(buildAclEntry(aclType, aclFlag, aclMask, aclWho));
+            }
+            attrs.put("acl", acls);
+        }
+        if ((flags & SSH_FILEXFER_ATTR_BITS) != 0) {
+            int bits = buffer.getInt();
+            int valid = 0xffffffff;
+            if (version >= SFTP_V6) {
+                valid = buffer.getInt();
+            }
+            // TODO: handle attrib bits
+        }
+        if ((flags & SSH_FILEXFER_ATTR_TEXT_HINT) != 0) {
+            boolean text = buffer.getBoolean();
+            // TODO: handle text
+        }
+        if ((flags & SSH_FILEXFER_ATTR_MIME_TYPE) != 0) {
+            String mimeType = buffer.getString();
+            // TODO: handle mime-type
+        }
+        if ((flags & SSH_FILEXFER_ATTR_LINK_COUNT) != 0) {
+            int nlink = buffer.getInt();
+            // TODO: handle link-count
+        }
+        if ((flags & SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
+            String untranslated = buffer.getString();
+            // TODO: handle untranslated-name
+        }
+        if ((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0) {
+            int count = buffer.getInt();
+            Map<String, String> extended = new HashMap<>();
+            for (int i = 0; i < count; i++) {
+                String key = buffer.getString();
+                String val = buffer.getString();
+                extended.put(key, val);
+            }
+            attrs.put("extended", extended);
+        }
+
+        return attrs;
+    }
+
+    private FileTime readTime(Buffer buffer, int flags) {
+        long secs = buffer.getLong();
+        long millis = secs * 1000;
+        if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+            millis += buffer.getInt() / 1000000l;
+        }
+        return FileTime.from(millis, TimeUnit.MILLISECONDS);
+    }
+
+    private AclEntry buildAclEntry(int aclType, int aclFlag, int aclMask, final String aclWho) {
+        AclEntryType type;
+        switch (aclType) {
+        case ACE4_ACCESS_ALLOWED_ACE_TYPE:
+            type = AclEntryType.ALLOW;
+            break;
+        case ACE4_ACCESS_DENIED_ACE_TYPE:
+            type = AclEntryType.DENY;
+            break;
+        case ACE4_SYSTEM_AUDIT_ACE_TYPE:
+            type = AclEntryType.AUDIT;
+            break;
+        case ACE4_SYSTEM_ALARM_ACE_TYPE:
+            type = AclEntryType.AUDIT;
+            break;
+        default:
+            throw new IllegalStateException("Unknown acl type: " + aclType);
+        }
+        Set<AclEntryFlag> flags = new HashSet<>();
+        if ((aclFlag & ACE4_FILE_INHERIT_ACE) != 0) {
+            flags.add(AclEntryFlag.FILE_INHERIT);
+        }
+        if ((aclFlag & ACE4_DIRECTORY_INHERIT_ACE) != 0) {
+            flags.add(AclEntryFlag.DIRECTORY_INHERIT);
+        }
+        if ((aclFlag & ACE4_NO_PROPAGATE_INHERIT_ACE) != 0) {
+            flags.add(AclEntryFlag.NO_PROPAGATE_INHERIT);
+        }
+        if ((aclFlag & ACE4_INHERIT_ONLY_ACE) != 0) {
+            flags.add(AclEntryFlag.INHERIT_ONLY);
+        }
+        Set<AclEntryPermission> mask = new HashSet<>();
+        if ((aclMask & ACE4_READ_DATA) != 0) {
+            mask.add(AclEntryPermission.READ_DATA);
+        }
+        if ((aclMask & ACE4_LIST_DIRECTORY) != 0) {
+            mask.add(AclEntryPermission.LIST_DIRECTORY);
+        }
+        if ((aclMask & ACE4_WRITE_DATA) != 0) {
+            mask.add(AclEntryPermission.WRITE_DATA);
+        }
+        if ((aclMask & ACE4_ADD_FILE) != 0) {
+            mask.add(AclEntryPermission.ADD_FILE);
+        }
+        if ((aclMask & ACE4_APPEND_DATA) != 0) {
+            mask.add(AclEntryPermission.APPEND_DATA);
+        }
+        if ((aclMask & ACE4_ADD_SUBDIRECTORY) != 0) {
+            mask.add(AclEntryPermission.ADD_SUBDIRECTORY);
+        }
+        if ((aclMask & ACE4_READ_NAMED_ATTRS) != 0) {
+            mask.add(AclEntryPermission.READ_NAMED_ATTRS);
+        }
+        if ((aclMask & ACE4_WRITE_NAMED_ATTRS) != 0) {
+            mask.add(AclEntryPermission.WRITE_NAMED_ATTRS);
+        }
+        if ((aclMask & ACE4_EXECUTE) != 0) {
+            mask.add(AclEntryPermission.EXECUTE);
+        }
+        if ((aclMask & ACE4_DELETE_CHILD) != 0) {
+            mask.add(AclEntryPermission.DELETE_CHILD);
+        }
+        if ((aclMask & ACE4_READ_ATTRIBUTES) != 0) {
+            mask.add(AclEntryPermission.READ_ATTRIBUTES);
+        }
+        if ((aclMask & ACE4_WRITE_ATTRIBUTES) != 0) {
+            mask.add(AclEntryPermission.WRITE_ATTRIBUTES);
+        }
+        if ((aclMask & ACE4_DELETE) != 0) {
+            mask.add(AclEntryPermission.DELETE);
+        }
+        if ((aclMask & ACE4_READ_ACL) != 0) {
+            mask.add(AclEntryPermission.READ_ACL);
+        }
+        if ((aclMask & ACE4_WRITE_ACL) != 0) {
+            mask.add(AclEntryPermission.WRITE_ACL);
+        }
+        if ((aclMask & ACE4_WRITE_OWNER) != 0) {
+            mask.add(AclEntryPermission.WRITE_OWNER);
+        }
+        if ((aclMask & ACE4_SYNCHRONIZE) != 0) {
+            mask.add(AclEntryPermission.SYNCHRONIZE);
+        }
+        UserPrincipal who = new DefaultGroupPrincipal(aclWho);
+        return AclEntry.newBuilder()
+                .setType(type)
+                .setFlags(flags)
+                .setPermissions(mask)
+                .setPrincipal(who)
+                .build();
+    }
+
+    protected void sendStatus(int id, Exception e) throws IOException {
+        int substatus;
+        if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
+            substatus = SSH_FX_NO_SUCH_FILE;
+        } else if (e instanceof FileAlreadyExistsException) {
+            substatus = SSH_FX_FILE_ALREADY_EXISTS;
+        } else if (e instanceof DirectoryNotEmptyException) {
+            substatus = SSH_FX_DIR_NOT_EMPTY;
+        } else if (e instanceof AccessDeniedException) {
+            substatus = SSH_FX_PERMISSION_DENIED;
+        } else if (e instanceof OverlappingFileLockException) {
+            substatus = SSH_FX_LOCK_CONFLICT;
+        } else {
+            substatus = SSH_FX_FAILURE;
+        }
+        sendStatus(id, substatus, e.toString());
+    }
+
+    protected void sendStatus(int id, int substatus, String msg) throws IOException {
+        sendStatus(id, substatus, msg != null ? msg : "", "");
+    }
+
+    protected void sendStatus(int id, int substatus, String msg, String lang) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Send SSH_FXP_STATUS (substatus={}, lang={}, msg={})",
+                      new Object[] { Integer.valueOf(substatus), lang, msg });
+        }
+
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putByte((byte) SSH_FXP_STATUS);
+        buffer.putInt(id);
+        buffer.putInt(substatus);
+        buffer.putString(msg);
+        buffer.putString(lang);
+        send(buffer);
+    }
+
+    protected void send(Buffer buffer) throws IOException {
+        DataOutputStream dos = new DataOutputStream(out);
+        dos.writeInt(buffer.available());
+        dos.write(buffer.array(), buffer.rpos(), buffer.available());
+        dos.flush();
+    }
+
+    @Override
+    public void destroy() {
+        if (!closed) {
+            if (log.isDebugEnabled()) {
+                log.debug("destroy() - mark as closed");
+            }
+
+            closed = true;
+
+            // if thread has not completed, cancel it
+            if ((pendingFuture != null) && (!pendingFuture.isDone())) {
+                boolean result = pendingFuture.cancel(true);
+                // TODO consider waiting some reasonable (?) amount of time for cancellation
+                if (log.isDebugEnabled()) {
+                    log.debug("destroy() - cancel pending future=" + result);
+                }
+            }
+
+            pendingFuture = null;
+
+            if ((executors != null) && (!executors.isShutdown()) && shutdownExecutor) {
+                Collection<Runnable> runners = executors.shutdownNow();
+                if (log.isDebugEnabled()) {
+                    log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
+                }
+            }
+
+            executors = null;
+
+            try {
+                fileSystem.close();
+            } catch (UnsupportedOperationException e) {
+                // Ignore
+            } catch (IOException e) {
+                log.debug("Error closing FileSystem", e);
+            }
+        }
+    }
+
+    private Path resolveFile(String path) {
+        //in case we are running on Windows
+        String localPath = SelectorUtils.translateToLocalPath(path);
+        return defaultDir.resolve(localPath);
+    }
+
+    private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
+            "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+    /**
+     * Get unix style date string.
+     */
+    private static String getUnixDate(FileTime time) {
+        return getUnixDate(time != null ? time.toMillis() : -1);
+    }
+
+    private static String getUnixDate(long millis) {
+        if (millis < 0) {
+            return "------------";
+        }
+
+        StringBuilder sb = new StringBuilder(16);
+        Calendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(millis);
+
+        // month
+        sb.append(MONTHS[cal.get(Calendar.MONTH)]);
+        sb.append(' ');
+
+        // day
+        int day = cal.get(Calendar.DATE);
+        if (day < 10) {
+            sb.append(' ');
+        }
+        sb.append(day);
+        sb.append(' ');
+
+        long sixMonth = 15811200000L; // 183L * 24L * 60L * 60L * 1000L;
+        long nowTime = System.currentTimeMillis();
+        if (Math.abs(nowTime - millis) > sixMonth) {
+
+            // year
+            int year = cal.get(Calendar.YEAR);
+            sb.append(' ');
+            sb.append(year);
+        } else {
+
+            // hour
+            int hh = cal.get(Calendar.HOUR_OF_DAY);
+            if (hh < 10) {
+                sb.append('0');
+            }
+            sb.append(hh);
+            sb.append(':');
+
+            // minute
+            int mm = cal.get(Calendar.MINUTE);
+            if (mm < 10) {
+                sb.append('0');
+            }
+            sb.append(mm);
+        }
+        return sb.toString();
+    }
+
+    protected static class PrincipalBase implements Principal {
+        private final String name;
+
+        public PrincipalBase(String name) {
+            if (name == null) {
+                throw new IllegalArgumentException("name is null");
+            }
+            this.name = name;
+        }
+
+        @Override
+        public final String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if ((o == null) || (getClass() != o.getClass())) {
+                return false;
+            }
+
+            Principal that = (Principal) o;
+            if (Objects.equals(getName(),that.getName())) {
+                return true;
+            } else {
+                return false;    // debug breakpoint
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(getName());
+        }
+
+        @Override
+        public String toString() {
+            return getName();
+        }
+    }
+
+    protected static class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
+        public DefaultUserPrincipal(String name) {
+            super(name);
+        }
+    }
+
+    protected static class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
+        public DefaultGroupPrincipal(String name) {
+            super(name);
+        }
+    }
+}


[02/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
new file mode 100644
index 0000000..98c0205
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.ObjectBuilder;
+import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.subsystem.SubsystemFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpSubsystemFactory implements SubsystemFactory, Cloneable, ExecutorServiceConfigurer {
+    public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME;
+    public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn;
+
+    public static class Builder implements ObjectBuilder<SftpSubsystemFactory> {
+        private final SftpSubsystemFactory factory = new SftpSubsystemFactory();
+
+        public Builder() {
+            super();
+        }
+
+        public Builder withExecutorService(ExecutorService service) {
+            factory.setExecutorService(service);
+            return this;
+        }
+
+        public Builder withShutdownOnExit(boolean shutdown) {
+            factory.setShutdownOnExit(shutdown);
+            return this;
+        }
+
+        public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
+            factory.setUnsupportedAttributePolicy(p);
+            return this;
+        }
+
+        @Override
+        public SftpSubsystemFactory build() {
+            // return a clone so that each invocation returns a different instance - avoid shared instances
+            return factory.clone();
+        }
+    }
+
+    private ExecutorService executors;
+    private boolean shutdownExecutor;
+    private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
+
+    public SftpSubsystemFactory() {
+        super();
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public ExecutorService getExecutorService() {
+        return executors;
+    }
+
+    /**
+     * @param service The {@link ExecutorService} to be used by the {@link SftpSubsystem}
+     *                command when starting execution. If {@code null} then a single-threaded ad-hoc service is used.
+     */
+    @Override
+    public void setExecutorService(ExecutorService service) {
+        executors = service;
+    }
+
+    @Override
+    public boolean isShutdownOnExit() {
+        return shutdownExecutor;
+    }
+
+    /**
+     * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                       will be called when subsystem terminates - unless it is the ad-hoc service, which
+     *                       will be shutdown regardless
+     */
+    @Override
+    public void setShutdownOnExit(boolean shutdownOnExit) {
+        shutdownExecutor = shutdownOnExit;
+    }
+
+    public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
+        return policy;
+    }
+
+    /**
+     * @param p The {@link UnsupportedAttributePolicy} to use if failed to access
+     *          some local file attributes
+     */
+    public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
+        if (p == null) {
+            throw new IllegalArgumentException("No policy provided");
+        }
+
+        policy = p;
+    }
+
+    @Override
+    public Command create() {
+        return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy());
+    }
+
+    @Override
+    public SftpSubsystemFactory clone() {
+        try {
+            return getClass().cast(super.clone());  // shallow clone is good enough
+        } catch (CloneNotSupportedException e) {
+            throw new UnsupportedOperationException("Unexpected clone exception", e);   // unexpected since we implement cloneable
+        }
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java
new file mode 100644
index 0000000..87a0c01
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum UnsupportedAttributePolicy {
+    Ignore,
+    Warn,
+    ThrowException;
+
+    public static final Set<UnsupportedAttributePolicy> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(UnsupportedAttributePolicy.class));
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 96f69b8..4ec4891 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -70,7 +70,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Transformer;
 import org.apache.sshd.common.util.buffer.Buffer;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java
deleted file mode 100644
index 22f1a70..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.client.sftp;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.sftp.SftpClient.CloseableHandle;
-import org.apache.sshd.client.sftp.SftpClient.Handle;
-import org.apache.sshd.util.BaseTestSupport;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class DefaultCloseableHandleTest extends BaseTestSupport {
-    public DefaultCloseableHandleTest() {
-        super();
-    }
-
-    @Test
-    public void testChannelBehavior() throws IOException {
-        final String id = getCurrentTestName();
-        SftpClient client = Mockito.mock(SftpClient.class);
-        Mockito.doAnswer(new Answer<Void>() {
-                @Override
-                public Void answer(InvocationOnMock invocation) throws Throwable {
-                    Object[] args = invocation.getArguments();
-                    Handle handle = (Handle) args[0];
-                    assertEquals("Mismatched closing handle", id, handle.id);
-                    return null;
-                }
-            }).when(client).close(Matchers.any(Handle.class));
-
-        CloseableHandle handle = new DefaultCloseableHandle(client, id);
-        try {
-            assertTrue("Handle not initially open", handle.isOpen());
-        } finally {
-            handle.close();
-        }
-        assertFalse("Handle not marked as closed", handle.isOpen());
-        // make sure close was called
-        Mockito.verify(client).close(handle);
-    }
-
-    @Test
-    public void testCloseIdempotent() throws IOException {
-        SftpClient client = Mockito.mock(SftpClient.class);
-        final AtomicBoolean closeCalled = new AtomicBoolean(false);
-        Mockito.doAnswer(new Answer<Void>() {
-                @Override
-                public Void answer(InvocationOnMock invocation) throws Throwable {
-                    Object[] args = invocation.getArguments();
-                    assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true));
-                    return null;
-                }
-            }).when(client).close(Matchers.any(Handle.class));
-
-        CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName());
-        for (int index=0; index < Byte.SIZE; index++) {
-            handle.close();
-        }
-        
-        assertTrue("Close method not called", closeCalled.get());
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
deleted file mode 100644
index cbe27a8..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.root.RootedFileSystemProvider;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
-import org.apache.sshd.util.Utils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpFileSystemTest extends BaseTestSupport {
-
-    private SshServer sshd;
-    private int port;
-    private final FileSystemFactory fileSystemFactory;
-
-    public SftpFileSystemTest() throws IOException {
-        Path targetPath = detectTargetFolder().toPath();
-        Path parentPath = targetPath.getParent();
-        final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
-        fileSystemFactory = new FileSystemFactory() {
-            @Override
-            public FileSystem createFileSystem(Session session) throws IOException {
-                return fileSystem;
-            }
-        };
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
-        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setFileSystemFactory(fileSystemFactory);
-        sshd.start();
-        port = sshd.getPort();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (sshd != null) {
-            sshd.stop(true);
-        }
-    }
-
-    @Test
-    public void testFileSystem() throws IOException {
-        Path targetPath = detectTargetFolder().toPath();
-        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        Utils.deleteRecursive(lclSftp);
-
-        try(FileSystem fs = FileSystems.newFileSystem(
-                URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
-                new TreeMap<String,Object>() {
-                    private static final long serialVersionUID = 1L;    // we're not serializing it
-                
-                    {
-                        put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
-                        put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
-                    }
-            })) {
-
-            Iterable<Path> rootDirs = fs.getRootDirectories();
-            for (Path root : rootDirs) {
-                String  rootName = root.toString();
-                try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
-                    for (Path child : ds) {
-                        System.out.append('\t').append('[').append(rootName).append("] ").println(child);
-                    }
-                } catch(IOException | RuntimeException e) {
-                    // TODO on Windows one might get share problems for *.sys files
-                    // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
-                    // for now, Windows is less of a target so we are lenient with it
-                    if (OsUtils.isWin32()) {
-                        System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
-                    } else {
-                        throw e;
-                    }
-                }
-            }
-
-            Path current = fs.getPath(".").toRealPath().normalize();
-            System.out.append("CWD: ").println(current);
-
-            Path parentPath = targetPath.getParent();
-            Path clientFolder = lclSftp.resolve("client");
-            String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
-            Path file1 = fs.getPath(remFile1Path);
-            Files.createDirectories(file1.getParent());
-
-            String  expected="Hello world: " + getCurrentTestName();
-            {
-                Files.write(file1, expected.getBytes());
-                String buf = new String(Files.readAllBytes(file1));
-                assertEquals("Mismatched read test data", expected, buf);
-            }
-    
-            String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
-            Path file2 = fs.getPath(remFile2Path);
-            String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
-            Path file3 = fs.getPath(remFile3Path);
-            try {
-                Files.move(file2, file3);
-                fail("Unexpected success in moving " + file2 + " => " + file3);
-            } catch (NoSuchFileException e) {
-                // expected
-            }
-
-            Files.write(file2, "h".getBytes());
-            try {
-                Files.move(file1, file2);
-                fail("Unexpected success in moving " + file1 + " => " + file2);
-            } catch (FileAlreadyExistsException e) {
-                // expected
-            }
-            Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
-            Files.move(file2, file1);
-    
-            Map<String, Object> attrs = Files.readAttributes(file1, "*");
-            System.out.append(file1.toString()).append(" attributes: ").println(attrs);
-    
-            // TODO: symbolic links only work for absolute files
-    //        Path link = fs.getPath("target/sftp/client/test2.txt");
-    //        Files.createSymbolicLink(link, link.relativize(file));
-    //        assertTrue(Files.isSymbolicLink(link));
-    //        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
-    
-            // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
-            if (OsUtils.isUNIX()) {
-                Path link = fs.getPath(remFile2Path);
-                Path linkParent = link.getParent();
-                Path relPath = linkParent.relativize(file1);
-                Files.createSymbolicLink(link, relPath);
-                assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
-
-                Path symLink = Files.readSymbolicLink(link);
-                assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
-                Files.delete(link);
-            }
-    
-            attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
-            System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
-    
-            assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
-    
-            try (FileChannel channel = FileChannel.open(file1)) {
-                try (FileLock lock = channel.lock()) {
-                    System.out.println("Locked " + lock.toString());
-    
-                    try (FileChannel channel2 = FileChannel.open(file1)) {
-                        try (FileLock lock2 = channel2.lock()) {
-                            System.out.println("Locked " + lock2.toString());
-                            fail("Unexpected success in re-locking " + file1);
-                        } catch (OverlappingFileLockException e) {
-                            // expected
-                        }
-                    }
-                }
-            }
-    
-            Files.delete(file1);
-        }
-    }
-
-    @Test
-    public void testAttributes() throws IOException {
-        Path targetPath = detectTargetFolder().toPath();
-        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        Utils.deleteRecursive(lclSftp);
-
-        try(FileSystem fs = FileSystems.newFileSystem(
-                URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
-                new TreeMap<String,Object>() {
-                    private static final long serialVersionUID = 1L;    // we're not serializing it
-                
-                    {
-                        put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_READ_BUFFER_SIZE));
-                        put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_WRITE_BUFFER_SIZE));
-                    }
-            })) {
-
-            Path parentPath = targetPath.getParent();
-            Path clientFolder = lclSftp.resolve("client");
-            String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt"));
-            Path file = fs.getPath(remFilePath);
-            Files.createDirectories(file.getParent());
-            Files.write(file, (getCurrentTestName() + "\n").getBytes());
-    
-            Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
-            assertNotNull("NO attributes read for " + file, attrs);
-    
-            Files.setAttribute(file, "basic:size", Long.valueOf(2L));
-            Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
-            Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
-
-            FileSystem fileSystem = file.getFileSystem();
-            try {
-                UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
-                GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
-                Files.setAttribute(file, "posix:group", group);
-            } catch (UserPrincipalNotFoundException e) {
-                // Also, according to the Javadoc:
-                //      "Where an implementation does not support any notion of
-                //       group then this method always throws UserPrincipalNotFoundException."
-                // Therefore we are lenient with this exception for Windows
-                if (OsUtils.isWin32()) {
-                    System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
-                } else {
-                    throw e;
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testRootFileSystem() throws IOException {
-        Path targetPath = detectTargetFolder().toPath();
-        Path rootNative = targetPath.resolve("root").toAbsolutePath();
-        Utils.deleteRecursive(rootNative);
-        Files.createDirectories(rootNative);
-
-        try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
-            Path dir = Files.createDirectories(fs.getPath("test/foo"));
-            System.out.println("Created " + dir);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java
deleted file mode 100644
index ad6f603..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWUSR;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Random;
-import java.util.Vector;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.root.RootedFileSystemProvider;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
-import org.apache.sshd.util.JSchLogger;
-import org.apache.sshd.util.SimpleUserInfo;
-import org.apache.sshd.util.Utils;
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.FixMethodOrder;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-import com.jcraft.jsch.ChannelSftp;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.SftpException;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpTest extends BaseTestSupport {
-
-    private SshServer sshd;
-    private int port;
-    private com.jcraft.jsch.Session session;
-    private final FileSystemFactory fileSystemFactory;
-
-    public SftpTest() throws IOException {
-        Path targetPath = detectTargetFolder().toPath();
-        Path parentPath = targetPath.getParent();
-        final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
-        fileSystemFactory = new FileSystemFactory() {
-            @Override
-            public FileSystem createFileSystem(Session session) throws IOException {
-                return fileSystem;
-            }
-        };
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
-        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setFileSystemFactory(fileSystemFactory);
-        sshd.start();
-        port = sshd.getPort();
-
-        JSchLogger.init();
-        JSch sch = new JSch();
-        session = sch.getSession("sshd", "localhost", port);
-        session.setUserInfo(new SimpleUserInfo("sshd"));
-        session.connect();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (session != null) {
-            session.disconnect();
-        }
-        
-        if (sshd != null) {
-            sshd.stop(true);
-        }
-    }
-
-    @Test
-    @Ignore
-    public void testExternal() throws Exception {
-        System.out.println("SFTP subsystem available on port " + port);
-        Thread.sleep(5 * 60000);
-    }
-
-    @Test
-    public void testOpen() throws Exception {
-        try(SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
-            
-            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(5L, TimeUnit.SECONDS);
-
-                Path targetPath = detectTargetFolder().toPath();
-                Path parentPath = targetPath.getParent();
-                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-                Path clientFolder = lclSftp.resolve("client");
-                Path testFile = clientFolder.resolve(getCurrentTestName() + ".txt");
-                String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
-
-                File javaFile = testFile.toFile();
-                assertHierarchyTargetFolderExists(javaFile.getParentFile());
-                javaFile.createNewFile();
-                javaFile.setWritable(false, false);
-                javaFile.setReadable(false, false);
-        
-                try (SftpClient sftp = session.createSftpClient()) {
-                    boolean	isWindows = OsUtils.isWin32();
-            
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
-                        // NOTE: on Windows files are always readable
-                        // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
-                        assertTrue("Empty read should have failed on " + file, isWindows);
-                    } catch (IOException e) {
-                        if (isWindows) {
-                            throw e;
-                        }
-                    }
-            
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
-                        fail("Empty write should have failed on " + file);
-                    } catch (IOException e) {
-                        // ok
-                    }
-    
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) {
-                        // NOTE: on Windows files are always readable
-                        assertTrue("Empty truncate should have failed on " + file, isWindows);
-                    } catch (IOException e) {
-                        // ok
-                    }
-    
-                    // NOTE: on Windows files are always readable
-                    int	perms=sftp.stat(file).perms;
-                    int	permsMask=S_IWUSR | (isWindows ? 0 : S_IRUSR);
-                    assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, (perms & permsMask));
-            
-                    javaFile.setWritable(true, false);
-            
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) {
-                        // OK should succeed
-                        assertTrue("Handle not marked as open for file=" + file, h.isOpen());
-                    }
-            
-                    byte[] d = "0123456789\n".getBytes();
-                    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);
-                    }
-
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
-                        sftp.write(h, d.length * 2, d, 0, d.length);
-                    }
-
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
-                        sftp.write(h, 3, "-".getBytes(), 0, 1);
-                    }
-
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
-                        // NOTE: on Windows files are always readable
-                        assertTrue("Data read should have failed on " + file, isWindows);
-                    } catch (IOException e) {
-                        if (isWindows) {
-                            throw e;
-                        }
-                    }
-            
-                    javaFile.setReadable(true, false);
-            
-                    byte[] buf = new byte[3];
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
-                        int l = sftp.read(h, 2l, buf, 0, 3);
-                        assertEquals("Mismatched read data", "2-4", new String(buf, 0, l));
-                    }
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testClient() throws Exception {
-        try(SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(5L, TimeUnit.SECONDS);
-
-                Path targetPath = detectTargetFolder().toPath();
-                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-                Utils.deleteRecursive(lclSftp);
-                Files.createDirectories(lclSftp);
-
-                Path parentPath = targetPath.getParent();
-                Path clientFolder = lclSftp.resolve("client");
-                String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
-                String file = dir + "/" + getCurrentTestName() + ".txt";
-
-                try (SftpClient sftp = session.createSftpClient()) {
-                    sftp.mkdir(dir);
-            
-                    try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
-                        byte[] d = "0123456789\n".getBytes();
-                        sftp.write(h, 0, d, 0, d.length);
-                        sftp.write(h, d.length, d, 0, d.length);
-                
-                        SftpClient.Attributes attrs = sftp.stat(h);
-                        assertNotNull("No handle attributes", attrs);
-                    }            
-            
-                    try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
-                        SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
-                        assertNotNull("No dir entries", dirEntries);
-                        assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
-                        assertNull("Unexpected entry read", sftp.readDir(h));
-                    }
-            
-                    sftp.remove(file);
-    
-                    byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE];
-                    new Random(System.currentTimeMillis()).nextBytes(workBuf);
-                    try (OutputStream os = sftp.write(file)) {
-                        os.write(workBuf);
-                    }
-            
-                    try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) {
-                        int readLen = is.read(workBuf);
-                        assertEquals("Mismatched read data length", workBuf.length, readLen);
-        
-                        int i = is.read();
-                        assertEquals("Unexpected read past EOF", -1, i);
-                    }
-        
-                    SftpClient.Attributes attributes = sftp.stat(file);
-                    assertTrue("Test file not detected as regular", attributes.isRegularFile());
-            
-                    attributes = sftp.stat(dir);
-                    assertTrue("Test directory not reported as such", attributes.isDirectory());
-            
-                    int nb = 0;
-                    for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
-                        assertNotNull("Unexpected null entry", entry);
-                        nb++;
-                    }
-                    assertEquals("Mismatched read dir entries", 1, nb);
-            
-                    sftp.remove(file);
-            
-                    sftp.rmdir(dir);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    /**
-     * this test is meant to test out write's logic, to ensure that internal chunking (based on Buffer.MAX_LEN) is
-     * functioning properly. To do this, we write a variety of file sizes, both smaller and larger than Buffer.MAX_LEN.
-     * in addition, this test ensures that improper arguments passed in get caught with an IllegalArgumentException
-     * @throws Exception upon any uncaught exception or failure
-     */
-    @Test
-    public void testWriteChunking() throws Exception {
-        try(SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
-            
-            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(5L, TimeUnit.SECONDS);
-        
-                Path targetPath = detectTargetFolder().toPath();
-                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-                Utils.deleteRecursive(lclSftp);
-                Files.createDirectories(lclSftp);
-
-                Path parentPath = targetPath.getParent();
-                Path clientFolder = lclSftp.resolve("client");
-                String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
-        
-                try(SftpClient sftp = session.createSftpClient()) {
-                    sftp.mkdir(dir);
-            
-                    uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN - 1, "bufferMaxLenMinusOneFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN, "bufferMaxLenFile.txt");
-                    // were chunking not implemented, these would fail. these sizes should invoke our internal chunking mechanism
-                    uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN + 1, "bufferMaxLenPlusOneFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, (int)(1.5 * ByteArrayBuffer.MAX_LEN), "1point5BufferMaxLenFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) - 1, "2TimesBufferMaxLenMinusOneFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, 2 * ByteArrayBuffer.MAX_LEN, "2TimesBufferMaxLenFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) + 1, "2TimesBufferMaxLenPlusOneFile.txt");
-                    uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt");
-            
-                    // test erroneous calls that check for negative values
-                    Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid");
-                    testInvalidParams(sftp, invalidPath, Utils.resolveRelativeRemotePath(parentPath, invalidPath));
-            
-                    // cleanup
-                    sftp.rmdir(dir);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    private void testInvalidParams(SftpClient sftp, Path file, String filePath) throws Exception {
-        // generate random file and upload it
-        String randomData = randomString(5);
-        byte[] randomBytes = randomData.getBytes();
-        try(SftpClient.CloseableHandle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
-            try {
-                sftp.write(handle, -1, randomBytes, 0, 0);
-                fail("should not have been able to write file with invalid file offset for " + filePath);
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            try {
-                sftp.write(handle, 0, randomBytes, -1, 0);
-                fail("should not have been able to write file with invalid source offset for " + filePath);
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            try {
-                sftp.write(handle, 0, randomBytes, 0, -1);
-                fail("should not have been able to write file with invalid length for " + filePath);
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            try {
-                sftp.write(handle, 0, randomBytes, 0, randomBytes.length + 1);
-                fail("should not have been able to write file with length bigger than array itself (no offset) for " + filePath);
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            try {
-                sftp.write(handle, 0, randomBytes, randomBytes.length, 1);
-                fail("should not have been able to write file with length bigger than array itself (with offset) for " + filePath);
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-        }
-
-        sftp.remove(filePath);
-        assertFalse("File should not be there: " + file.toString(), Files.exists(file));
-    }
-
-    private void uploadAndVerifyFile(SftpClient sftp, Path clientFolder, String remoteDir, int size, String filename) throws Exception {
-        // generate random file and upload it
-        String remotePath = remoteDir + "/" + filename;
-        String randomData = randomString(size);
-        try(SftpClient.CloseableHandle handle = sftp.open(remotePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
-            sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length());
-        }
-
-        // verify results
-        Path resultPath = clientFolder.resolve(filename);
-        assertTrue("File should exist on disk: " + resultPath, Files.exists(resultPath));
-        assertTrue("Mismatched file contents: " + resultPath, randomData.equals(readFile(remotePath)));
-
-        // cleanup
-        sftp.remove(remotePath);
-        assertFalse("File should have been removed: " + resultPath, Files.exists(resultPath));
-    }
-
-    @Test
-    public void testSftp() throws Exception {
-        String d = getCurrentTestName() + "\n";
-
-        Path targetPath = detectTargetFolder().toPath();
-        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
-
-        Path target = lclSftp.resolve(getCurrentTestName() + ".txt");
-        String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), target);
-
-        final int NUM_ITERATIONS=10;
-        StringBuilder   sb = new StringBuilder(d.length() * NUM_ITERATIONS * NUM_ITERATIONS);
-        for (int j = 1; j <= NUM_ITERATIONS; j++) {
-            if (sb.length() > 0) {
-                sb.setLength(0);
-            }
-
-            for (int i = 0; i < j; i++) {
-                sb.append(d);
-            }
-
-            sendFile(remotePath, sb.toString());
-            assertFileLength(target, sb.length(), 5000);
-            Files.delete(target);
-        }
-    }
-
-    @Test
-    public void testReadWriteWithOffset() throws Exception {
-        Path targetPath = detectTargetFolder().toPath();
-        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
-
-        Path localPath = lclSftp.resolve(getCurrentTestName() + ".txt");
-        String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), localPath);
-        String data = getCurrentTestName();
-        String extraData = "@" + getClass().getSimpleName();
-        int appendOffset = -5;
-
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-        try {
-            c.put(new ByteArrayInputStream(data.getBytes()), remotePath);
-    
-            assertTrue("Remote file not created after initial write: " + localPath, Files.exists(localPath));
-            assertEquals("Mismatched data read from " + remotePath, data, readFile(remotePath));
-    
-            try(OutputStream os = c.put(remotePath, null, ChannelSftp.APPEND, appendOffset)) {
-                os.write(extraData.getBytes());
-            }
-        } finally {
-            c.disconnect();
-        }
-
-        assertTrue("Remote file not created after data update: " + localPath, Files.exists(localPath));
-        
-        String expected = data.substring(0, data.length() + appendOffset) + extraData;
-        String actual = readFile(remotePath);
-        assertEquals("Mismatched final file data in " + remotePath, expected, actual);
-    }
-
-    @Test
-    public void testReadDir() throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-        try {
-            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
-            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
-            String path = new File(base.relativize(url).getPath()).getParent() + "/";
-            path = path.replace('\\', '/');
-            Vector<?> res = c.ls(path);
-            for (Object f : res) {
-                System.out.println(f.toString());
-            }
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    @Test
-    public void testRealPath() throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-
-        try {
-            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
-            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
-            String path = new File(base.relativize(url).getPath()).getParent() + "/";
-            path = path.replace('\\', '/');
-            String real = c.realpath(path);
-            System.out.println(real);
-            try {
-                real = c.realpath(path + "/foobar");
-                System.out.println(real);
-                fail("Expected SftpException");
-            } catch (SftpException e) {
-                // ok
-            }
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    @Test
-    public void testRename() throws Exception {
-        try(SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
-            
-            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(5L, TimeUnit.SECONDS);
-        
-                Path targetPath = detectTargetFolder().toPath();
-                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-                Utils.deleteRecursive(lclSftp);
-                Files.createDirectories(lclSftp);
-
-                Path parentPath = targetPath.getParent();
-                Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-        
-                try(SftpClient sftp = session.createSftpClient()) {
-                    Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt");
-                    String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1);
-                    try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
-                        os.write((getCurrentTestName() + "\n").getBytes());
-                    }
-
-                    Path file2 = clientFolder.resolve(getCurrentTestName() + "-2.txt");
-                    String file2Path = Utils.resolveRelativeRemotePath(parentPath, file2);
-                    Path file3 = clientFolder.resolve(getCurrentTestName() + "-3.txt");
-                    String file3Path = Utils.resolveRelativeRemotePath(parentPath, file3);
-                    try {
-                        sftp.rename(file2Path, file3Path);
-                        fail("Unxpected rename success of " + file2Path + " => " + file3Path);
-                    } catch (org.apache.sshd.client.SftpException e) {
-                        assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus());
-                    }
-            
-                    try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
-                        os.write("H".getBytes());
-                    }
-            
-                    try {
-                        sftp.rename(file1Path, file2Path);
-                        fail("Unxpected rename success of " + file1Path + " => " + file2Path);
-                    } catch (org.apache.sshd.client.SftpException e) {
-                        assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
-                    }
-
-                    sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testCreateSymbolicLink() throws Exception {
-        // Do not execute on windows as the file system does not support symlinks
-        Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
-
-        Path targetPath = detectTargetFolder().toPath();
-        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
-
-        Path parentPath = targetPath.getParent();
-        Path sourcePath = lclSftp.resolve(getCurrentTestName() + ".txt");
-        String remSrcPath = Utils.resolveRelativeRemotePath(parentPath, sourcePath);
-        Path linkPath = lclSftp.resolve("link-" + sourcePath.getFileName());
-        String remLinkPath = Utils.resolveRelativeRemotePath(parentPath, linkPath);
-
-        String data = getCurrentTestName();
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-        try {
-            c.put(new ByteArrayInputStream(data.getBytes()), remSrcPath);
-    
-            assertTrue("Source file not created: " + sourcePath, Files.exists(sourcePath));
-            assertEquals("Mismatched stored data in " + remSrcPath, data, readFile(remSrcPath));
-    
-            c.symlink(remSrcPath, remLinkPath);
-    
-            assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath));
-            assertEquals("Mismatche link data in " + remLinkPath, data, readFile(remLinkPath));
-    
-            String str1 = c.readlink(remLinkPath);
-            String str2 = c.realpath(remSrcPath);
-            assertEquals("Mismatched link vs. real path", str1, str2);
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    protected String readFile(String path) throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-        
-        try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
-            InputStream is = c.get(path)) {
-
-            byte[] buffer = new byte[256];
-            int count;
-            while (-1 != (count = is.read(buffer))) {
-                bos.write(buffer, 0, count);
-            }
-
-            return bos.toString();
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    protected void sendFile(String path, String data) throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-        try {
-            c.put(new ByteArrayInputStream(data.getBytes()), path);
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    private String randomString(int size) {
-        StringBuilder sb = new StringBuilder(size);
-        for (int i = 0; i < size; i++) {
-            sb.append((char) ((i % 10) + '0'));
-        }
-        return sb.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
new file mode 100644
index 0000000..8dbab78
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.subsystem.sftp.DefaultCloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class DefaultCloseableHandleTest extends BaseTestSupport {
+    public DefaultCloseableHandleTest() {
+        super();
+    }
+
+    @Test
+    public void testChannelBehavior() throws IOException {
+        final String id = getCurrentTestName();
+        SftpClient client = Mockito.mock(SftpClient.class);
+        Mockito.doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    Object[] args = invocation.getArguments();
+                    Handle handle = (Handle) args[0];
+                    assertEquals("Mismatched closing handle", id, handle.id);
+                    return null;
+                }
+            }).when(client).close(Matchers.any(Handle.class));
+
+        CloseableHandle handle = new DefaultCloseableHandle(client, id);
+        try {
+            assertTrue("Handle not initially open", handle.isOpen());
+        } finally {
+            handle.close();
+        }
+        assertFalse("Handle not marked as closed", handle.isOpen());
+        // make sure close was called
+        Mockito.verify(client).close(handle);
+    }
+
+    @Test
+    public void testCloseIdempotent() throws IOException {
+        SftpClient client = Mockito.mock(SftpClient.class);
+        final AtomicBoolean closeCalled = new AtomicBoolean(false);
+        Mockito.doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    Object[] args = invocation.getArguments();
+                    assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true));
+                    return null;
+                }
+            }).when(client).close(Matchers.any(Handle.class));
+
+        CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName());
+        for (int index=0; index < Byte.SIZE; index++) {
+            handle.close();
+        }
+        
+        assertTrue("Close method not called", closeCalled.get());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
new file mode 100644
index 0000000..fe3a123
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpFileSystemTest extends BaseTestSupport {
+
+    private SshServer sshd;
+    private int port;
+    private final FileSystemFactory fileSystemFactory;
+
+    public SftpFileSystemTest() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+        fileSystemFactory = new FileSystemFactory() {
+            @Override
+            public FileSystem createFileSystem(Session session) throws IOException {
+                return fileSystem;
+            }
+        };
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+        sshd.setCommandFactory(new ScpCommandFactory());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd.setFileSystemFactory(fileSystemFactory);
+        sshd.start();
+        port = sshd.getPort();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (sshd != null) {
+            sshd.stop(true);
+        }
+    }
+
+    @Test
+    public void testFileSystem() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(lclSftp);
+
+        try(FileSystem fs = FileSystems.newFileSystem(
+                URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
+                new TreeMap<String,Object>() {
+                    private static final long serialVersionUID = 1L;    // we're not serializing it
+                
+                    {
+                        put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
+                        put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
+                    }
+            })) {
+
+            Iterable<Path> rootDirs = fs.getRootDirectories();
+            for (Path root : rootDirs) {
+                String  rootName = root.toString();
+                try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+                    for (Path child : ds) {
+                        System.out.append('\t').append('[').append(rootName).append("] ").println(child);
+                    }
+                } catch(IOException | RuntimeException e) {
+                    // TODO on Windows one might get share problems for *.sys files
+                    // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
+                    // for now, Windows is less of a target so we are lenient with it
+                    if (OsUtils.isWin32()) {
+                        System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+
+            Path current = fs.getPath(".").toRealPath().normalize();
+            System.out.append("CWD: ").println(current);
+
+            Path parentPath = targetPath.getParent();
+            Path clientFolder = lclSftp.resolve("client");
+            String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
+            Path file1 = fs.getPath(remFile1Path);
+            Files.createDirectories(file1.getParent());
+
+            String  expected="Hello world: " + getCurrentTestName();
+            {
+                Files.write(file1, expected.getBytes());
+                String buf = new String(Files.readAllBytes(file1));
+                assertEquals("Mismatched read test data", expected, buf);
+            }
+    
+            String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
+            Path file2 = fs.getPath(remFile2Path);
+            String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
+            Path file3 = fs.getPath(remFile3Path);
+            try {
+                Files.move(file2, file3);
+                fail("Unexpected success in moving " + file2 + " => " + file3);
+            } catch (NoSuchFileException e) {
+                // expected
+            }
+
+            Files.write(file2, "h".getBytes());
+            try {
+                Files.move(file1, file2);
+                fail("Unexpected success in moving " + file1 + " => " + file2);
+            } catch (FileAlreadyExistsException e) {
+                // expected
+            }
+            Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
+            Files.move(file2, file1);
+    
+            Map<String, Object> attrs = Files.readAttributes(file1, "*");
+            System.out.append(file1.toString()).append(" attributes: ").println(attrs);
+    
+            // TODO: symbolic links only work for absolute files
+    //        Path link = fs.getPath("target/sftp/client/test2.txt");
+    //        Files.createSymbolicLink(link, link.relativize(file));
+    //        assertTrue(Files.isSymbolicLink(link));
+    //        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
+    
+            // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
+            if (OsUtils.isUNIX()) {
+                Path link = fs.getPath(remFile2Path);
+                Path linkParent = link.getParent();
+                Path relPath = linkParent.relativize(file1);
+                Files.createSymbolicLink(link, relPath);
+                assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+
+                Path symLink = Files.readSymbolicLink(link);
+                assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+                Files.delete(link);
+            }
+    
+            attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+            System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
+    
+            assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
+    
+            try (FileChannel channel = FileChannel.open(file1)) {
+                try (FileLock lock = channel.lock()) {
+                    System.out.println("Locked " + lock.toString());
+    
+                    try (FileChannel channel2 = FileChannel.open(file1)) {
+                        try (FileLock lock2 = channel2.lock()) {
+                            System.out.println("Locked " + lock2.toString());
+                            fail("Unexpected success in re-locking " + file1);
+                        } catch (OverlappingFileLockException e) {
+                            // expected
+                        }
+                    }
+                }
+            }
+    
+            Files.delete(file1);
+        }
+    }
+
+    @Test
+    public void testAttributes() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(lclSftp);
+
+        try(FileSystem fs = FileSystems.newFileSystem(
+                URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
+                new TreeMap<String,Object>() {
+                    private static final long serialVersionUID = 1L;    // we're not serializing it
+                
+                    {
+                        put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_READ_BUFFER_SIZE));
+                        put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_WRITE_BUFFER_SIZE));
+                    }
+            })) {
+
+            Path parentPath = targetPath.getParent();
+            Path clientFolder = lclSftp.resolve("client");
+            String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt"));
+            Path file = fs.getPath(remFilePath);
+            Files.createDirectories(file.getParent());
+            Files.write(file, (getCurrentTestName() + "\n").getBytes());
+    
+            Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+            assertNotNull("NO attributes read for " + file, attrs);
+    
+            Files.setAttribute(file, "basic:size", Long.valueOf(2L));
+            Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
+            Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
+
+            FileSystem fileSystem = file.getFileSystem();
+            try {
+                UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
+                GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
+                Files.setAttribute(file, "posix:group", group);
+            } catch (UserPrincipalNotFoundException e) {
+                // Also, according to the Javadoc:
+                //      "Where an implementation does not support any notion of
+                //       group then this method always throws UserPrincipalNotFoundException."
+                // Therefore we are lenient with this exception for Windows
+                if (OsUtils.isWin32()) {
+                    System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRootFileSystem() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path rootNative = targetPath.resolve("root").toAbsolutePath();
+        Utils.deleteRecursive(rootNative);
+        Files.createDirectories(rootNative);
+
+        try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
+            Path dir = Files.createDirectories(fs.getPath("test/foo"));
+            System.out.println("Created " + dir);
+        }
+    }
+}


[04/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java
deleted file mode 100644
index 04d0239..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.server.sftp;
-
-import java.util.concurrent.ExecutorService;
-
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.ObjectBuilder;
-import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
-import org.apache.sshd.server.Command;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpSubsystemFactory implements NamedFactory<Command>, Cloneable, ExecutorServiceConfigurer {
-    public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME;
-    public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn;
-
-    public static class Builder implements ObjectBuilder<SftpSubsystemFactory> {
-        private final SftpSubsystemFactory factory = new SftpSubsystemFactory();
-
-        public Builder() {
-            super();
-        }
-
-        public Builder withExecutorService(ExecutorService service) {
-            factory.setExecutorService(service);
-            return this;
-        }
-
-        public Builder withShutdownOnExit(boolean shutdown) {
-            factory.setShutdownOnExit(shutdown);
-            return this;
-        }
-
-        public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
-            factory.setUnsupportedAttributePolicy(p);
-            return this;
-        }
-
-        @Override
-        public SftpSubsystemFactory build() {
-            // return a clone so that each invocation returns a different instance - avoid shared instances
-            return factory.clone();
-        }
-    }
-
-    private ExecutorService executors;
-    private boolean shutdownExecutor;
-    private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
-
-    public SftpSubsystemFactory() {
-        super();
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public ExecutorService getExecutorService() {
-        return executors;
-    }
-
-    /**
-     * @param service The {@link ExecutorService} to be used by the {@link SftpSubsystem}
-     *                command when starting execution. If {@code null} then a single-threaded ad-hoc service is used.
-     */
-    @Override
-    public void setExecutorService(ExecutorService service) {
-        executors = service;
-    }
-
-    @Override
-    public boolean isShutdownOnExit() {
-        return shutdownExecutor;
-    }
-
-    /**
-     * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
-     *                       will be called when subsystem terminates - unless it is the ad-hoc service, which
-     *                       will be shutdown regardless
-     */
-    @Override
-    public void setShutdownOnExit(boolean shutdownOnExit) {
-        shutdownExecutor = shutdownOnExit;
-    }
-
-    public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
-        return policy;
-    }
-
-    /**
-     * @param p The {@link UnsupportedAttributePolicy} to use if failed to access
-     *          some local file attributes
-     */
-    public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
-        if (p == null) {
-            throw new IllegalArgumentException("No policy provided");
-        }
-
-        policy = p;
-    }
-
-    @Override
-    public Command create() {
-        return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy());
-    }
-
-    @Override
-    public SftpSubsystemFactory clone() {
-        try {
-            return getClass().cast(super.clone());  // shallow clone is good enough
-        } catch (CloneNotSupportedException e) {
-            throw new UnsupportedOperationException("Unexpected clone exception", e);   // unexpected since we implement cloneable
-        }
-    }
-
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java
deleted file mode 100644
index f9a1472..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.server.sftp;
-
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public enum UnsupportedAttributePolicy {
-    Ignore,
-    Warn,
-    ThrowException;
-
-    public static final Set<UnsupportedAttributePolicy> VALUES =
-            Collections.unmodifiableSet(EnumSet.allOf(UnsupportedAttributePolicy.class));
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
index e46a005..98d7e40 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
@@ -19,10 +19,6 @@
 
 package org.apache.sshd.server.shell;
 
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Set;
-
 import org.apache.sshd.common.util.OsUtils;
 
 /**
@@ -34,6 +30,10 @@ public class InteractiveProcessShellFactory extends ProcessShellFactory {
     private static final String[] LINUX_COMMAND = { "/bin/sh", "-i", "-l" };
     private static final String[] WINDOWS_COMMAND = { "cmd.exe" };
 
+    public static String[] resolveDefaultInteractiveCommand() {
+        return resolveInteractiveCommand(OsUtils.isWin32());
+    }
+
     public static String[] resolveInteractiveCommand(boolean isWin32) {
         // return clone(s) to avoid inadvertent modification
         if (isWin32) {
@@ -42,23 +42,10 @@ public class InteractiveProcessShellFactory extends ProcessShellFactory {
             return LINUX_COMMAND.clone();
         }
     }
-    
-    public static final Set<TtyOptions> LINUX_OPTIONS =
-            Collections.unmodifiableSet(EnumSet.of(TtyOptions.ONlCr));
-    public static final Set<TtyOptions> WINDOWS_OPTIONS =
-            Collections.unmodifiableSet(EnumSet.of(TtyOptions.Echo, TtyOptions.ICrNl, TtyOptions.ONlCr));
-
-    public static Set<TtyOptions> resolveTtyOptions(boolean isWin32) {
-        if (isWin32) {
-            return WINDOWS_OPTIONS;
-        } else {
-            return LINUX_OPTIONS;
-        }
-    }
 
     public static final InteractiveProcessShellFactory INSTANCE = new InteractiveProcessShellFactory();
 
     public InteractiveProcessShellFactory() {
-        super(resolveInteractiveCommand(OsUtils.isWin32()), resolveTtyOptions(OsUtils.isWin32()));
+        super(resolveDefaultInteractiveCommand(), TtyOptions.resolveDefaultTtyOptions());
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
index 033f476..eb7334f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
@@ -31,6 +31,7 @@ import java.util.Set;
 
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -44,12 +45,30 @@ import org.apache.sshd.server.Command;
  */
 public class ProcessShellFactory extends AbstractLoggingBean implements Factory<Command> {
 
-    public enum TtyOptions {
+    public static enum TtyOptions {
         Echo,
         INlCr,
         ICrNl,
         ONlCr,
-        OCrNl
+        OCrNl;
+        
+        
+        public static final Set<TtyOptions> LINUX_OPTIONS =
+                Collections.unmodifiableSet(EnumSet.of(TtyOptions.ONlCr));
+        public static final Set<TtyOptions> WINDOWS_OPTIONS =
+                Collections.unmodifiableSet(EnumSet.of(TtyOptions.Echo, TtyOptions.ICrNl, TtyOptions.ONlCr));
+
+        public static Set<TtyOptions> resolveDefaultTtyOptions() {
+            return resolveTtyOptions(OsUtils.isWin32());
+        }
+
+        public static Set<TtyOptions> resolveTtyOptions(boolean isWin32) {
+            if (isWin32) {
+                return WINDOWS_OPTIONS;
+            } else {
+                return LINUX_OPTIONS;
+            }
+        }
     }
 
     private String[] command;
@@ -60,7 +79,7 @@ public class ProcessShellFactory extends AbstractLoggingBean implements Factory<
     }
 
     public ProcessShellFactory(String[] command) {
-        this(command, EnumSet.noneOf(TtyOptions.class));
+        this(command, TtyOptions.resolveDefaultTtyOptions());
     }
 
     public ProcessShellFactory(String[] command, Collection<TtyOptions> ttyOptions) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java
new file mode 100644
index 0000000..2a888e3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.server.Command;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SubsystemFactory extends NamedFactory<Command> {
+    // required because of generics issues
+    Transformer<SubsystemFactory,NamedFactory<Command>> FAC2NAMED=new Transformer<SubsystemFactory,NamedFactory<Command>>() {
+        @Override
+        public NamedFactory<Command> transform(SubsystemFactory input) {
+            return input;
+        }
+    };
+
+}


[08/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
new file mode 100644
index 0000000..6110900
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
@@ -0,0 +1,1179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_APPEND_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_ATTRIBUTES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_ATTRIBUTES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V4;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V5;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V6;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ALL;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SIZE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_REGULAR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_SYMLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_APPEND;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_NEW;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_TRUNCATE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_EXCL;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_EXISTING;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_OR_CREATE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_READ;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNC;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_WRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_ATTRS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_BLOCK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_CLOSE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSETSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_HANDLE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_INIT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_MKDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_NAME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPEN;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPENDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READ;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REALPATH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REMOVE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_ATOMIC;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_OVERWRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RMDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SETSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STATUS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SYMLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_UNBLOCK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_VERSION;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_WRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_EOF;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_OK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFLNK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFREG;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.attribute.FileTime;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SftpException;
+import org.apache.sshd.client.channel.ChannelSubsystem;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.InputStreamWithChannel;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.OutputStreamWithChannel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultSftpClient extends AbstractSftpClient {
+    private final ClientSession clientSession;
+    private final ChannelSubsystem channel;
+    private final Map<Integer, Buffer> messages;
+    private final AtomicInteger cmdId = new AtomicInteger(100);
+    private final Buffer receiveBuffer = new ByteArrayBuffer();
+    private boolean closing;
+    private int version;
+    private final Map<String, byte[]> extensions = new HashMap<>();
+
+    public DefaultSftpClient(ClientSession clientSession) throws IOException {
+        this.clientSession = clientSession;
+        this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+        this.messages = new HashMap<>();
+        this.channel.setOut(new OutputStream() {
+            @Override
+            public void write(int b) throws IOException {
+                write(new byte[] { (byte) b }, 0, 1);
+            }
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                data(b, off, len);
+            }
+        });
+        this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE));
+        this.channel.open().verify(FactoryManagerUtils.getLongProperty(clientSession, SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT));
+        this.channel.onClose(new Runnable() {
+            @SuppressWarnings("synthetic-access")
+            @Override
+            public void run() {
+                synchronized (messages) {
+                    closing = true;
+                    messages.notifyAll();
+                }
+            }
+        });
+        init();
+    }
+
+    @Override
+    public int getVersion() {
+        return version;
+    }
+
+    @Override
+    public boolean isClosing() {
+        return closing;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return this.channel.isOpen();
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (isOpen()) {
+            this.channel.close(false);
+        }
+    }
+
+    /**
+     * Receive binary data
+     */
+    protected int data(byte[] buf, int start, int len) throws IOException {
+        Buffer incoming = new ByteArrayBuffer(buf,  start, len);
+        // If we already have partial data, we need to append it to the buffer and use it
+        if (receiveBuffer.available() > 0) {
+            receiveBuffer.putBuffer(incoming);
+            incoming = receiveBuffer;
+        }
+        // Process commands
+        int rpos = incoming.rpos();
+        for (int count=0; receive(incoming); count++) {
+            if (log.isTraceEnabled()) {
+                log.trace("Processed " + count + " data messages");
+            }
+        }
+
+        int read = incoming.rpos() - rpos;
+        // Compact and add remaining data
+        receiveBuffer.compact();
+        if (receiveBuffer != incoming && incoming.available() > 0) {
+            receiveBuffer.putBuffer(incoming);
+        }
+        return read;
+    }
+
+    /**
+     * Read SFTP packets from buffer
+     */
+    protected boolean receive(Buffer incoming) throws IOException {
+        int rpos = incoming.rpos();
+        int wpos = incoming.wpos();
+        clientSession.resetIdleTimeout();
+        if ((wpos - rpos) > 4) {
+            int length = incoming.getInt();
+            if (length < 5) {
+                throw new IOException("Illegal sftp packet length: " + length);
+            }
+            if ((wpos - rpos) >= (length + 4)) {
+                incoming.rpos(rpos);
+                incoming.wpos(rpos + 4 + length);
+                process(incoming);
+                incoming.rpos(rpos + 4 + length);
+                incoming.wpos(wpos);
+                return true;
+            }
+        }
+        incoming.rpos(rpos);
+        return false;
+    }
+
+    /**
+     * Process an SFTP packet
+     */
+    protected void process(Buffer incoming) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(incoming.available());
+        buffer.putBuffer(incoming);
+        buffer.rpos(5);
+        int id = buffer.getInt();
+        buffer.rpos(0);
+        synchronized (messages) {
+            messages.put(Integer.valueOf(id), buffer);
+            messages.notifyAll();
+        }
+    }
+
+    protected int send(int cmd, Buffer buffer) throws IOException {
+        int id = cmdId.incrementAndGet();
+        
+        try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
+            dos.writeInt(5 + buffer.available());
+            dos.writeByte(cmd);
+            dos.writeInt(id);
+            dos.write(buffer.array(), buffer.rpos(), buffer.available());
+            dos.flush();
+        }
+
+        return id;
+    }
+
+    protected Buffer receive(int id) throws IOException {
+        synchronized (messages) {
+            while (true) {
+                if (closing) {
+                    throw new SshException("Channel has been closed");
+                }
+                Buffer buffer = messages.remove(Integer.valueOf(id));
+                if (buffer != null) {
+                    return buffer;
+                }
+                try {
+                    messages.wait();
+                } catch (InterruptedException e) {
+                    throw (IOException) new InterruptedIOException("Interrupted while waiting for messages").initCause(e);
+                }
+            }
+        }
+    }
+
+    protected Buffer read() throws IOException {
+        try(DataInputStream dis=new DataInputStream(new NoCloseInputStream(channel.getInvertedOut()))) {
+            int length = dis.readInt();
+            if (length < 5) {
+                throw new IllegalArgumentException("Bad length: " + length);
+            }
+            Buffer buffer = new ByteArrayBuffer(length + 4);
+            buffer.putInt(length);
+            int nb = length;
+            while (nb > 0) {
+                int readLen = dis.read(buffer.array(), buffer.wpos(), nb);
+                if (readLen < 0) {
+                    throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb);
+                }
+                buffer.wpos(buffer.wpos() + readLen);
+                nb -= readLen;
+            }
+
+            return buffer;
+        }
+    }
+
+    protected void init() throws IOException {
+        // Init packet
+        try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
+            dos.writeInt(5);
+            dos.writeByte(SSH_FXP_INIT);
+            dos.writeInt(SFTP_V6);
+            dos.flush();
+        }
+
+        Buffer buffer;
+        synchronized (messages) {
+            while (messages.isEmpty()) {
+                try {
+                    messages.wait();
+                } catch (InterruptedException e) {
+                    throw (IOException) new InterruptedIOException("Interruppted init()").initCause(e);
+                }
+            }
+            buffer = messages.remove(messages.keySet().iterator().next());
+
+        }
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_VERSION) {
+            if (id < SFTP_V3) {
+                throw new SshException("Unsupported sftp version " + id);
+            }
+            version = id;
+            while (buffer.available() > 0) {
+                String name = buffer.getString();
+                byte[] data = buffer.getBytes();
+                extensions.put(name, data);
+            }
+        } else if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("init(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
+            throw new SftpException(substatus, msg);
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    protected void checkStatus(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
+            if (substatus != SSH_FX_OK) {
+                throw new SftpException(substatus, msg);
+            }
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    protected String checkHandle(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+            throw new SftpException(substatus, msg);
+        } else if (type == SSH_FXP_HANDLE) {
+            String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
+            return handle;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    protected Attributes checkAttributes(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+            throw new SftpException(substatus, msg);
+        } else if (type == SSH_FXP_ATTRS) {
+            return readAttributes(buffer);
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    protected String checkOneName(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+            throw new SftpException(substatus, msg);
+        } else if (type == SSH_FXP_NAME) {
+            int len = buffer.getInt();
+            if (len != 1) {
+                throw new SshException("SFTP error: received " + len + " names instead of 1");
+            }
+            String name = buffer.getString(), longName = null;
+            if (version == SFTP_V3) {
+                longName = buffer.getString();
+            }
+            Attributes attrs = readAttributes(buffer);
+            if (log.isTraceEnabled()) {
+                log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
+            }
+            return name;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    protected Attributes readAttributes(Buffer buffer) throws IOException {
+        Attributes attrs = new Attributes();
+        int flags = buffer.getInt();
+        if (version == SFTP_V3) {
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                attrs.flags.add(Attribute.Size);
+                attrs.size = buffer.getLong();
+            }
+            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+                attrs.flags.add(Attribute.UidGid);
+                attrs.uid = buffer.getInt();
+                attrs.gid = buffer.getInt();
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                attrs.flags.add(Attribute.Perms);
+                attrs.perms = buffer.getInt();
+            }
+            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+                attrs.flags.add(Attribute.AcModTime);
+                attrs.atime = buffer.getInt();
+                attrs.mtime = buffer.getInt();
+            }
+        } else if (version >= SFTP_V4) {
+            attrs.type = buffer.getByte();
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                attrs.flags.add(Attribute.Size);
+                attrs.size = buffer.getLong();
+            }
+            if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+                attrs.flags.add(Attribute.OwnerGroup);
+                attrs.owner = buffer.getString();
+                attrs.group = buffer.getString();
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                attrs.flags.add(Attribute.Perms);
+                attrs.perms = buffer.getInt();
+            }
+            
+            // update the permissions according to the type
+            switch (attrs.type) {
+                case SSH_FILEXFER_TYPE_REGULAR:
+                    attrs.perms |= S_IFREG;
+                    break;
+                case SSH_FILEXFER_TYPE_DIRECTORY:
+                    attrs.perms |= S_IFDIR;
+                    break;
+                case SSH_FILEXFER_TYPE_SYMLINK:
+                    attrs.perms |= S_IFLNK;
+                    break;
+                default:    // do nothing
+            }
+
+            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+                attrs.flags.add(Attribute.AccessTime);
+                attrs.accessTime = readTime(buffer, flags);
+                attrs.atime = (int) attrs.accessTime.to(TimeUnit.SECONDS);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+                attrs.flags.add(Attribute.CreateTime);
+                attrs.createTime = readTime(buffer, flags);
+                attrs.ctime = (int) attrs.createTime.to(TimeUnit.SECONDS);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+                attrs.flags.add(Attribute.ModifyTime);
+                attrs.modifyTime = readTime(buffer, flags);
+                attrs.mtime = (int) attrs.modifyTime.to(TimeUnit.SECONDS);
+            }
+            // TODO: acl
+        } else {
+            throw new IllegalStateException("readAttributes - unsupported version: " + version);
+        }
+        return attrs;
+    }
+
+    private FileTime readTime(Buffer buffer, int flags) {
+        long secs = buffer.getLong();
+        long millis = secs * 1000;
+        if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+            millis += buffer.getInt() / 1000000l;
+        }
+        return FileTime.from(millis, TimeUnit.MILLISECONDS);
+    }
+
+    protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
+        if (version == SFTP_V3) {
+            int flags = 0;
+            for (Attribute a : attributes.flags) {
+                switch (a) {
+                    case Size:
+                        flags |= SSH_FILEXFER_ATTR_SIZE;
+                        break;
+                    case UidGid:
+                        flags |= SSH_FILEXFER_ATTR_UIDGID;
+                        break;
+                    case Perms:
+                        flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+                        break;
+                    case AcModTime:
+                        flags |= SSH_FILEXFER_ATTR_ACMODTIME;
+                        break;
+                    default:    // do nothing
+                }
+            }
+            buffer.putInt(flags);
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                buffer.putLong(attributes.size);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+                buffer.putInt(attributes.uid);
+                buffer.putInt(attributes.gid);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                buffer.putInt(attributes.perms);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+                buffer.putInt(attributes.atime);
+                buffer.putInt(attributes.mtime);
+            }
+        } else if (version >= SFTP_V4) {
+            int flags = 0;
+            for (Attribute a : attributes.flags) {
+                switch (a) {
+                    case Size:
+                        flags |= SSH_FILEXFER_ATTR_SIZE;
+                        break;
+                    case OwnerGroup:
+                        flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
+                        break;
+                    case Perms:
+                        flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+                        break;
+                    case AccessTime:
+                        flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
+                        break;
+                    case ModifyTime:
+                        flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
+                        break;
+                    case CreateTime:
+                        flags |= SSH_FILEXFER_ATTR_CREATETIME;
+                        break;
+                    default:    // do nothing
+                }
+            }
+            buffer.putInt(flags);
+            buffer.putByte(attributes.type);
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                buffer.putLong(attributes.size);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+                buffer.putString(attributes.owner != null ? attributes.owner : "OWNER@", StandardCharsets.UTF_8);
+                buffer.putString(attributes.group != null ? attributes.group : "GROUP@", StandardCharsets.UTF_8);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                buffer.putInt(attributes.perms);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+                buffer.putLong(attributes.accessTime.to(TimeUnit.SECONDS));
+                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+                    long nanos = attributes.accessTime.to(TimeUnit.NANOSECONDS);
+                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+                    buffer.putInt((int) nanos);
+                }
+                buffer.putInt(attributes.atime);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+                buffer.putLong(attributes.createTime.to(TimeUnit.SECONDS));
+                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+                    long nanos = attributes.createTime.to(TimeUnit.NANOSECONDS);
+                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+                    buffer.putInt((int) nanos);
+                }
+                buffer.putInt(attributes.atime);
+            }
+            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+                buffer.putLong(attributes.modifyTime.to(TimeUnit.SECONDS));
+                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+                    long nanos = attributes.modifyTime.to(TimeUnit.NANOSECONDS);
+                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+                    buffer.putInt((int) nanos);
+                }
+                buffer.putInt(attributes.atime);
+            }
+            // TODO: acl
+        } else {
+            throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
+        }
+    }
+
+    @Override
+    public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(path);
+        if (version == SFTP_V3) {
+            int mode = 0;
+            for (OpenMode m : options) {
+                switch (m) {
+                    case Read:
+                        mode |= SSH_FXF_READ;
+                        break;
+                    case Write:
+                        mode |= SSH_FXF_WRITE;
+                        break;
+                    case Append:
+                        mode |= SSH_FXF_APPEND;
+                        break;
+                    case Create:
+                        mode |= SSH_FXF_CREAT;
+                        break;
+                    case Truncate:
+                        mode |= SSH_FXF_TRUNC;
+                        break;
+                    case Exclusive:
+                        mode |= SSH_FXF_EXCL;
+                        break;
+                    default:    // do nothing
+                }
+            }
+            buffer.putInt(mode);
+        } else {
+            int mode = 0;
+            int access = 0;
+            if (options.contains(OpenMode.Read)) {
+                access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+            }
+            if (options.contains(OpenMode.Write)) {
+                access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+            }
+            if (options.contains(OpenMode.Append)) {
+                access |= ACE4_APPEND_DATA;
+            }
+            if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
+                mode |= SSH_FXF_CREATE_NEW;
+            } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
+                mode |= SSH_FXF_CREATE_TRUNCATE;
+            } else if (options.contains(OpenMode.Create)) {
+                mode |= SSH_FXF_OPEN_OR_CREATE;
+            } else if (options.contains(OpenMode.Truncate)) {
+                mode |= SSH_FXF_TRUNCATE_EXISTING;
+            } else {
+                mode |= SSH_FXF_OPEN_EXISTING;
+            }
+            if (version >= SFTP_V5) {
+                buffer.putInt(access);
+            }
+            buffer.putInt(mode);
+        }
+        writeAttributes(buffer, new Attributes());
+        return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
+    }
+
+    @Override
+    public void close(Handle handle) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(handle.id);
+        checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
+    }
+
+    @Override
+    public void remove(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(path);
+        checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
+    }
+
+    @Override
+    public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(oldPath);
+        buffer.putString(newPath);
+        
+        int numOptions = GenericUtils.size(options);
+        if (version >= SFTP_V5) {
+            int opts = 0;
+            if (numOptions > 0) {
+                for (CopyMode opt : options) {
+                    switch (opt) {
+                        case Atomic:
+                            opts |= SSH_FXP_RENAME_ATOMIC;
+                            break;
+                        case Overwrite:
+                            opts |= SSH_FXP_RENAME_OVERWRITE;
+                            break;
+                        default:    // do nothing
+                    }
+                }
+            }
+            buffer.putInt(opts);
+        } else if (numOptions > 0) {
+            throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
+                                                  + " - copy options can not be used with this SFTP version: " + options);
+        }
+        checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
+    }
+
+    @Override
+    public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(handle.id);
+        buffer.putLong(fileOffset);
+        buffer.putInt(len);
+        return checkData(receive(send(SSH_FXP_READ, buffer)), dstoff, dst);
+    }
+
+    protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
+            if (substatus == SSH_FX_EOF) {
+                return -1;
+            }
+
+            throw new SftpException(substatus, msg);
+        } else if (type == SSH_FXP_DATA) {
+            int len = buffer.getInt();
+            buffer.getRawBytes(dst, dstoff, len);
+            return len;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    @Override
+    public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
+        // do some bounds checking first
+        if ((fileOffset < 0) || (srcoff < 0) || (len < 0)) {
+            throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
+                                             + " are non-negative values: file-offset=" + fileOffset
+                                             + ", src-offset=" + srcoff + ", len=" + len);
+        }
+        if ((srcoff + len) > src.length) {
+            throw new IllegalArgumentException("write(" + handle + ")"
+                                             + " cannot read bytes " + srcoff + " to " + (srcoff + len)
+                                             + " when array is only of length " + src.length);
+        }
+
+        Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
+        buffer.putString(handle.id);
+        buffer.putLong(fileOffset);
+        buffer.putBytes(src, srcoff, len);
+        checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
+    }
+
+    @Override
+    public void mkdir(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(path.length() +  Long.SIZE /* some extra fields */);
+        buffer.putString(path, StandardCharsets.UTF_8);
+        buffer.putInt(0);
+        if (version != SFTP_V3) {
+            buffer.putByte((byte) 0);
+        }
+        checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
+    }
+
+    @Override
+    public void rmdir(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(path.length() +  Long.SIZE /* some extra fields */);
+        buffer.putString(path);
+        checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
+    }
+
+    @Override
+    public CloseableHandle openDir(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(path);
+        return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
+    }
+
+    @Override
+    public DirEntry[] readDir(Handle handle) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(handle.id);
+        return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
+    }
+
+    protected DirEntry[] checkDir(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+            if (substatus == SSH_FX_EOF) {
+                return null;
+            }
+            throw new SftpException(substatus, msg);
+        } else if (type == SSH_FXP_NAME) {
+            int len = buffer.getInt();
+            DirEntry[] entries = new DirEntry[len];
+            for (int i = 0; i < len; i++) {
+                String name = buffer.getString();
+                String longName = (version == SFTP_V3) ? buffer.getString() : null;
+                Attributes attrs = readAttributes(buffer);
+                if (log.isTraceEnabled()) {
+                    log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
+                }
+
+                entries[i] = new DirEntry(name, longName, attrs);
+            }
+            return entries;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+        }
+    }
+
+    @Override
+    public String canonicalPath(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(path);
+        return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
+    }
+
+    @Override
+    public Attributes stat(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(path);
+        if (version >= SFTP_V4) {
+            buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+        }
+        return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
+    }
+
+    @Override
+    public Attributes lstat(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(path);
+        if (version >= SFTP_V4) {
+            buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+        }
+        return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
+    }
+
+    @Override
+    public Attributes stat(Handle handle) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(handle.id);
+        if (version >= SFTP_V4) {
+            buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+        }
+        return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
+    }
+
+    @Override
+    public void setStat(String path, Attributes attributes) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(path);
+        writeAttributes(buffer, attributes);
+        checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
+    }
+
+    @Override
+    public void setStat(Handle handle, Attributes attributes) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(handle.id);
+        writeAttributes(buffer, attributes);
+        checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
+    }
+
+    @Override
+    public String readLink(String path) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+        buffer.putString(path);
+        return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
+    }
+
+    @Override
+    public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+        Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */);
+        if (version < SFTP_V6) {
+            if (!symbolic) {
+                throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
+            }
+            buffer.putString(targetPath);
+            buffer.putString(linkPath);
+            checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
+        } else {
+            buffer.putString(targetPath);
+            buffer.putString(linkPath);
+            buffer.putBoolean(symbolic);
+            checkStatus(receive(send(SSH_FXP_LINK, buffer)));
+        }
+    }
+
+    @Override
+    public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(handle.id);
+        buffer.putLong(offset);
+        buffer.putLong(length);
+        buffer.putInt(mask);
+        checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
+    }
+
+    @Override
+    public void unlock(Handle handle, long offset, long length) throws IOException {
+        Buffer buffer = new ByteArrayBuffer();
+        buffer.putString(handle.id);
+        buffer.putLong(offset);
+        buffer.putLong(length);
+        checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
+    }
+
+    @Override
+    public Iterable<DirEntry> readDir(final String path) throws IOException {
+        return new Iterable<DirEntry>() {
+            @Override
+            public Iterator<DirEntry> iterator() {
+                return new Iterator<DirEntry>() {
+                    private CloseableHandle handle;
+                    private DirEntry[] entries;
+                    private int index;
+
+                    {
+                        open();
+                        load();
+                    }
+
+                    @Override
+                    public boolean hasNext() {
+                        return (entries != null) && (index < entries.length);
+                    }
+
+                    @Override
+                    public DirEntry next() {
+                        DirEntry entry = entries[index++];
+                        if (index >= entries.length) {
+                            load();
+                        }
+                        return entry;
+                    }
+
+                    @SuppressWarnings("synthetic-access")
+                    private void open() {
+                        try {
+                            handle = openDir(path);
+                            if (log.isDebugEnabled()) {
+                                log.debug("readDir(" + path + ") handle=" + handle);
+                            }
+                        } catch (IOException e) {
+                            if (log.isDebugEnabled()) {
+                                log.debug("readDir(" + path + ") failed (" + e.getClass().getSimpleName() + ") to open dir: " + e.getMessage());
+                            }
+                            throw new RuntimeException(e);
+                        }
+                    }
+
+                    @SuppressWarnings("synthetic-access")
+                    private void load() {
+                        try {
+                            entries = readDir(handle);
+                            index = 0;
+                            if (entries == null) {
+                                handle.close();
+                            }
+                        } catch (IOException e) {
+                            entries = null;
+                            try {
+                                handle.close();
+                            } catch (IOException t) {
+                                if (log.isTraceEnabled()) {
+                                    log.trace(t.getClass().getSimpleName() + " while close handle=" + handle
+                                            + " due to " + e.getClass().getSimpleName() + " [" + e.getMessage() + "]"
+                                            + ": " + t.getMessage());
+                                }
+                            }
+                            throw new RuntimeException(e);
+                        }
+                    }
+
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException("readDir(" + path + ") Iterator#remove() N/A");
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
+        if (bufferSize < MIN_READ_BUFFER_SIZE) {
+            throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE);
+        }
+
+        return new InputStreamWithChannel() {
+            private byte[] bb = new byte[1];
+            private byte[] buffer = new byte[bufferSize];
+            private int index;
+            private int available;
+            private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
+            private long offset;
+
+            @Override
+            public boolean isOpen() {
+                return (handle != null) && handle.isOpen();
+            }
+
+            @Override
+            public int read() throws IOException {
+                int read = read(bb, 0, 1);
+                if (read > 0) {
+                    return bb[0];
+                }
+
+                return read;
+            }
+
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException {
+                if (!isOpen()) {
+                    throw new IOException("read(" + path + ") stream closed");
+                }
+
+                int idx = off;
+                while (len > 0) {
+                    if (index >= available) {
+                        available = DefaultSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
+                        if (available < 0) {
+                            if (idx == off) {
+                                return -1;
+                            } else {
+                                break;
+                            }
+                        }
+                        offset += available;
+                        index = 0;
+                    }
+                    if (index >= available) {
+                        break;
+                    }
+                    int nb = Math.min(len, available - index);
+                    System.arraycopy(buffer, index, b, idx, nb);
+                    index += nb;
+                    idx += nb;
+                    len -= nb;
+                }
+                return idx - off;
+            }
+
+            @Override
+            public void close() throws IOException {
+                if (isOpen()) {
+                    try {
+                        handle.close();
+                    } finally {
+                        handle = null;
+                    }
+                }
+            }
+        };
+    }
+
+    @Override
+    public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
+        if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
+            throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE);
+        }
+
+        return new OutputStreamWithChannel() {
+            private byte[] bb = new byte[1];
+            private byte[] buffer = new byte[bufferSize];
+            private int index;
+            private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
+            private long offset;
+
+            @Override
+            public boolean isOpen() {
+                return (handle != null) && handle.isOpen();
+            }
+
+            @Override
+            public void write(int b) throws IOException {
+                bb[0] = (byte) b;
+                write(bb, 0, 1);
+            }
+
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                if (!isOpen()) {
+                    throw new IOException("write(" + path + ")[len=" + len + "] stream is closed");
+                }
+
+                do {
+                    int nb = Math.min(len, buffer.length - index);
+                    System.arraycopy(b, off, buffer, index, nb);
+                    index += nb;
+                    if (index == buffer.length) {
+                        flush();
+                    }
+                    off += nb;
+                    len -= nb;
+                } while (len > 0);
+            }
+
+            @Override
+            public void flush() throws IOException {
+                if (!isOpen()) {
+                    throw new IOException("flush(" + path + ") stream is closed");
+                }
+
+                DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
+                offset += index;
+                index = 0;
+            }
+
+            @Override
+            public void close() throws IOException {
+                if (isOpen()) {
+                    try {
+                        try {
+                            if (index > 0) {
+                                flush();
+                            }
+                        } finally {
+                            handle.close();
+                        }
+                    } finally {
+                        handle = null;
+                    }
+                }
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
new file mode 100644
index 0000000..2af762c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFLNK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFMT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFREG;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.Channel;
+import java.nio.file.attribute.FileTime;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.subsystem.SubsystemClient;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+public interface SftpClient extends SubsystemClient {
+
+    enum OpenMode {
+        Read,
+        Write,
+        Append,
+        Create,
+        Truncate,
+        Exclusive
+    }
+
+    enum CopyMode {
+        Atomic,
+        Overwrite
+    }
+
+    enum Attribute {
+        Size,
+        UidGid,
+        Perms,
+        AcModTime,
+        OwnerGroup,
+        AccessTime,
+        ModifyTime,
+        CreateTime,
+    }
+
+    public static class Handle {
+        public final String id;
+        public Handle(String id) {
+            this.id = ValidateUtils.checkNotNullAndNotEmpty(id, "No handle ID", GenericUtils.EMPTY_OBJECT_ARRAY);
+        }
+        
+        @Override
+        public String toString() {
+            return id;
+        }
+    }
+
+    public static abstract class CloseableHandle extends Handle implements Channel, Closeable {
+        protected CloseableHandle(String id) {
+            super(id);
+        }
+    }
+
+    public static class Attributes {
+        public final Set<Attribute> flags = EnumSet.noneOf(Attribute.class);
+        public long size;
+        public byte type;
+        public int uid;
+        public int gid;
+        public int perms;
+        public int atime;
+        public int ctime;
+        public int mtime;
+        public String owner;
+        public String group;
+        public FileTime accessTime;
+        public FileTime createTime;
+        public FileTime modifyTime;
+
+        @Override
+        public String toString() {
+            return "type=" + type
+                 + ";size=" + size
+                 + ";uid=" + uid
+                 + ";gid=" + gid
+                 + ";perms=0x" + Integer.toHexString(perms)
+                 + ";flags=" + flags
+                 + ";owner=" + owner
+                 + ";group=" + group
+                 + ";aTime=(" + atime + ")[" + accessTime + "]"
+                 + ";cTime=(" + ctime + ")[" + createTime + "]"
+                 + ";mTime=(" + mtime + ")[" + modifyTime + "]"
+                 ;
+        }
+
+        public Attributes size(long size) {
+            flags.add(Attribute.Size);
+            this.size = size;
+            return this;
+        }
+        public Attributes owner(String owner) {
+            flags.add(Attribute.OwnerGroup);
+            this.owner = owner;
+            if (GenericUtils.isEmpty(group)) {
+                group = "GROUP@";
+            }
+            return this;
+        }
+        public Attributes group(String group) {
+            flags.add(Attribute.OwnerGroup);
+            this.group = group;
+            if (GenericUtils.isEmpty(owner)) {
+                owner = "OWNER@";
+            }
+            return this;
+        }
+        public Attributes owner(int uid, int gid) {
+            flags.add(Attribute.UidGid);
+            this.uid = uid;
+            this.gid = gid;
+            return this;
+        }
+        public Attributes perms(int perms) {
+            flags.add(Attribute.Perms);
+            this.perms = perms;
+            return this;
+        }
+        public Attributes atime(int atime) {
+            flags.add(Attribute.AccessTime);
+            this.atime = atime;
+            this.accessTime = FileTime.from(atime, TimeUnit.SECONDS);
+            return this;
+        }
+        public Attributes ctime(int ctime) {
+            flags.add(Attribute.CreateTime);
+            this.ctime = ctime;
+            this.createTime = FileTime.from(atime, TimeUnit.SECONDS);
+            return this;
+        }
+        public Attributes mtime(int mtime) {
+            flags.add(Attribute.ModifyTime);
+            this.mtime = mtime;
+            this.modifyTime = FileTime.from(atime, TimeUnit.SECONDS);
+            return this;
+        }
+        public Attributes time(int atime, int mtime) {
+            flags.add(Attribute.AcModTime);
+            this.atime = atime;
+            this.mtime = mtime;
+            return this;
+        }
+        public Attributes accessTime(FileTime atime) {
+            flags.add(Attribute.AccessTime);
+            this.atime = (int) atime.to(TimeUnit.SECONDS);
+            this.accessTime = atime;
+            return this;
+        }
+        public Attributes createTime(FileTime ctime) {
+            flags.add(Attribute.CreateTime);
+            this.ctime = (int) ctime.to(TimeUnit.SECONDS);
+            this.createTime = ctime;
+            return this;
+        }
+        public Attributes modifyTime(FileTime mtime) {
+            flags.add(Attribute.ModifyTime);
+            this.mtime = (int) mtime.to(TimeUnit.SECONDS);
+            this.modifyTime = mtime;
+            return this;
+        }
+        public boolean isRegularFile() {
+            return (perms & S_IFMT) == S_IFREG;
+        }
+        public boolean isDirectory() {
+            return (perms & S_IFMT) == S_IFDIR;
+        }
+        public boolean isSymbolicLink() {
+            return (perms & S_IFMT) == S_IFLNK;
+        }
+        public boolean isOther() {
+            return !isRegularFile() && !isDirectory() && !isSymbolicLink();
+        }
+    }
+
+    public static class DirEntry {
+        public String filename;
+        public String longFilename;
+        public Attributes attributes;
+        public DirEntry(String filename, String longFilename, Attributes attributes) {
+            this.filename = filename;
+            this.longFilename = longFilename;
+            this.attributes = attributes;
+        }
+    }
+
+    int getVersion();
+
+    boolean isClosing();
+
+    //
+    // Low level API
+    //
+
+    CloseableHandle open(String path) throws IOException;
+    CloseableHandle open(String path, OpenMode ... options) throws IOException;
+    CloseableHandle open(String path, Collection<OpenMode> options) throws IOException;
+
+    void close(Handle handle) throws IOException;
+
+    void remove(String path) throws IOException;
+
+    void rename(String oldPath, String newPath) throws IOException;
+    void rename(String oldPath, String newPath, CopyMode... options) throws IOException;
+    void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException;
+
+    int read(Handle handle, long fileOffset, byte[] dst) throws IOException;
+    int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException;
+
+    void write(Handle handle, long fileOffset, byte[] src) throws IOException;
+    void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException;
+
+    void mkdir(String path) throws IOException;
+
+    void rmdir(String path) throws IOException;
+
+    CloseableHandle openDir(String path) throws IOException;
+
+    DirEntry[] readDir(Handle handle) throws IOException;
+
+    String canonicalPath(String path) throws IOException;
+
+    Attributes stat(String path) throws IOException;
+
+    Attributes lstat(String path) throws IOException;
+
+    Attributes stat(Handle handle) throws IOException;
+
+    void setStat(String path, Attributes attributes) throws IOException;
+
+    void setStat(Handle handle, Attributes attributes) throws IOException;
+
+    String readLink(String path) throws IOException;
+
+    void symLink(String linkPath, String targetPath) throws IOException;
+
+    void link(String linkPath, String targetPath, boolean symbolic) throws IOException;
+
+    void lock(Handle handle, long offset, long length, int mask) throws IOException;
+
+    void unlock(Handle handle, long offset, long length) throws IOException;
+
+    //
+    // High level API
+    //
+
+    Iterable<DirEntry> readDir(String path) throws IOException;
+
+    // default values used if none specified
+    int MIN_BUFFER_SIZE=Byte.MAX_VALUE, MIN_READ_BUFFER_SIZE=MIN_BUFFER_SIZE, MIN_WRITE_BUFFER_SIZE=MIN_BUFFER_SIZE;
+    int IO_BUFFER_SIZE=32 * 1024, DEFAULT_READ_BUFFER_SIZE=IO_BUFFER_SIZE, DEFAULT_WRITE_BUFFER_SIZE=IO_BUFFER_SIZE;
+    long DEFAULT_WAIT_TIMEOUT=TimeUnit.SECONDS.toMillis(30L);
+
+    /**
+     * Property that can be used on the {@link org.apache.sshd.common.FactoryManager}
+     * to control the internal timeout used by the client to open a channel.
+     * If not specified then {@link #DEFAULT_CHANNEL_OPEN_TIMEOUT} value
+     * is used
+     */
+    String SFTP_CHANNEL_OPEN_TIMEOUT = "sftp-channel-open-timeout";
+        long DEFAULT_CHANNEL_OPEN_TIMEOUT = DEFAULT_WAIT_TIMEOUT;
+
+    InputStream read(String path) throws IOException;
+    InputStream read(String path, int bufferSize) throws IOException;
+    InputStream read(String path, OpenMode ... mode) throws IOException;
+    InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException;
+    InputStream read(String path, Collection<OpenMode> mode) throws IOException;
+    InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
+
+    OutputStream write(String path) throws IOException;
+    OutputStream write(String path, int bufferSize) throws IOException;
+    OutputStream write(String path, OpenMode ... mode) throws IOException;
+    OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException;
+    OutputStream write(String path, Collection<OpenMode> mode) throws IOException;
+    OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
new file mode 100644
index 0000000..a5ba947
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_LOCK_CONFLICT;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.SftpException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+public class SftpFileChannel extends FileChannel {
+
+    private final SftpPath p;
+    private final Collection<SftpClient.OpenMode> modes;
+    private final SftpClient sftp;
+    private final SftpClient.CloseableHandle handle;
+    private final Object lock = new Object();
+    private volatile long pos;
+    private volatile Thread blockingThread;
+
+    public SftpFileChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
+        this.p = ValidateUtils.checkNotNull(p, "No target path", GenericUtils.EMPTY_OBJECT_ARRAY);
+        this.modes = ValidateUtils.checkNotNull(modes, "No channel modes specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+        
+        SftpFileSystem  fs=p.getFileSystem();
+        sftp = fs.getClient();
+        handle = sftp.open(p.toString(), modes);
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+        return (int) doRead(Collections.singletonList(dst), -1);
+    }
+
+    @Override
+    public int read(ByteBuffer dst, long position) throws IOException {
+        if (position < 0) {
+            throw new IllegalArgumentException("read(" + p + ") illegal position to read from: " + position);
+        }
+        return (int) doRead(Collections.singletonList(dst), position);
+    }
+
+    @Override
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+        List<ByteBuffer> buffers = Arrays.asList(dsts).subList(offset, offset + length);
+        return doRead(buffers, -1);
+    }
+
+    public static final Set<SftpClient.OpenMode> READ_MODES=
+            Collections.unmodifiableSet(EnumSet.of(SftpClient.OpenMode.Read));
+
+    protected long doRead(List<ByteBuffer> buffers, long position) throws IOException {
+        ensureOpen(READ_MODES);
+        synchronized (lock) {
+            boolean completed = false;
+            boolean eof = false;
+            long curPos = position >= 0 ? position : pos;
+            try {
+                long totalRead = 0;
+                beginBlocking();
+                loop:
+                for (ByteBuffer buffer : buffers) {
+                    while (buffer.remaining() > 0) {
+                        ByteBuffer wrap = buffer;
+                        if (!buffer.hasArray()) {
+                            wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
+                        }
+                        int read = sftp.read(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), wrap.remaining());
+                        if (read > 0) {
+                            if (wrap == buffer) {
+                                wrap.position(wrap.position() + read);
+                            } else {
+                                buffer.put(wrap.array(), wrap.arrayOffset(), read);
+                            }
+                            curPos += read;
+                            totalRead += read;
+                        } else {
+                            eof = read == -1;
+                            break loop;
+                        }
+                    }
+                }
+                completed = true;
+                return totalRead > 0 ? totalRead : eof ? -1 : 0;
+            } finally {
+                if (position < 0) {
+                    pos = curPos;
+                }
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+        return (int) doWrite(Collections.singletonList(src), -1);
+    }
+
+    @Override
+    public int write(ByteBuffer src, long position) throws IOException {
+        if (position < 0) {
+            throw new IllegalArgumentException("write(" + p + ") illegal position to write to: " + position);
+        }
+        return (int) doWrite(Collections.singletonList(src), position);
+    }
+
+    @Override
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+        List<ByteBuffer> buffers = Arrays.asList(srcs).subList(offset, offset + length);
+        return doWrite(buffers, -1);
+    }
+
+    public static final Set<SftpClient.OpenMode> WRITE_MODES=
+            Collections.unmodifiableSet(
+                EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Append, SftpClient.OpenMode.Create, SftpClient.OpenMode.Truncate));
+
+    protected long doWrite(List<ByteBuffer> buffers, long position) throws IOException {
+        ensureOpen(WRITE_MODES);
+        synchronized (lock) {
+            boolean completed = false;
+            long curPos = position >= 0 ? position : pos;
+            try {
+                long totalWritten = 0;
+                beginBlocking();
+                for (ByteBuffer buffer : buffers) {
+                    while (buffer.remaining() > 0) {
+                        ByteBuffer wrap = buffer;
+                        if (!buffer.hasArray()) {
+                            wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
+                            buffer.get(wrap.array(), wrap.arrayOffset(), wrap.remaining());
+                        }
+                        int written = wrap.remaining();
+                        sftp.write(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), written);
+                        if (wrap == buffer) {
+                            wrap.position(wrap.position() + written);
+                        }
+                        curPos += written;
+                        totalWritten += written;
+                    }
+                }
+                completed = true;
+                return totalWritten;
+            } finally {
+                if (position < 0) {
+                    pos = curPos;
+                }
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public long position() throws IOException {
+        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+        return pos;
+    }
+
+    @Override
+    public FileChannel position(long newPosition) throws IOException {
+        if (newPosition < 0) {
+            throw new IllegalArgumentException("position(" + p + ") illegal file channel position: " + newPosition);
+        }
+
+        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+        synchronized (lock) {
+            pos = newPosition;
+            return this;
+        }
+    }
+
+    @Override
+    public long size() throws IOException {
+        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+        return sftp.stat(handle).size;
+    }
+
+    @Override
+    public FileChannel truncate(long size) throws IOException {
+        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+        sftp.setStat(handle, new SftpClient.Attributes().size(size));
+        return this;
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+    }
+
+    @Override
+    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
+        if ((position < 0) || (count < 0)) {
+            throw new IllegalArgumentException("transferTo(" + p + ") illegal position (" + position + ") or count (" + count + ")");
+        }
+        ensureOpen(READ_MODES);
+        synchronized (lock) {
+            boolean completed = false;
+            boolean eof = false;
+            long curPos = position;
+            try {
+                beginBlocking();
+
+                int bufSize = (int) Math.min(count, 32768);
+                byte[] buffer = new byte[bufSize];
+                long totalRead = 0L;
+                while (totalRead < count) {
+                    int read = sftp.read(handle, curPos, buffer, 0, buffer.length);
+                    if (read > 0) {
+                        ByteBuffer wrap = ByteBuffer.wrap(buffer);
+                        while (wrap.remaining() > 0) {
+                            target.write(wrap);
+                        }
+                        curPos += read;
+                        totalRead += read;
+                    } else {
+                        eof = read == -1;
+                    }
+                }
+                completed = true;
+                return totalRead > 0 ? totalRead : eof ? -1 : 0;
+            } finally {
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+        if ((position < 0) || (count < 0)) {
+            throw new IllegalArgumentException("transferFrom(" + p + ") illegal position (" + position + ") or count (" + count + ")");
+        }
+        ensureOpen(WRITE_MODES);
+
+        synchronized(lock) {
+            boolean completed = false;
+            long curPos = position >= 0 ? position : pos;
+            try {
+                long totalRead = 0;
+                beginBlocking();
+
+                byte[] buffer = new byte[32768];
+                while (totalRead < count) {
+                    ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, (int) Math.min(buffer.length, count - totalRead));
+                    int read = src.read(wrap);
+                    if (read > 0) {
+                        sftp.write(handle, curPos, buffer, 0, read);
+                        curPos += read;
+                        totalRead += read;
+                    } else {
+                        break;
+                    }
+                }
+                completed = true;
+                return totalRead;
+            } finally {
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+        throw new UnsupportedOperationException("map(" + p + ")[" + mode + "," + position + "," + size + "] N/A");
+    }
+
+    @Override
+    public FileLock lock(long position, long size, boolean shared) throws IOException {
+        return tryLock(position, size, shared);
+    }
+
+    @Override
+    public FileLock tryLock(final long position, final long size, boolean shared) throws IOException {
+        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+
+        try {
+            sftp.lock(handle, position, size, 0);
+        } catch (SftpException e) {
+            if (e.getStatus() == SSH_FX_LOCK_CONFLICT) {
+                throw new OverlappingFileLockException();
+            }
+            throw e;
+        }
+
+        return new FileLock(this, position, size, shared) {
+            private final AtomicBoolean valid = new AtomicBoolean(true);
+
+            @Override
+            public boolean isValid() {
+                return acquiredBy().isOpen() && valid.get();
+            }
+
+            @SuppressWarnings("synthetic-access")
+            @Override
+            public void release() throws IOException {
+                if (valid.compareAndSet(true, false)) {
+                    sftp.unlock(handle, position, size);
+                }
+            }
+        };
+    }
+
+    @Override
+    protected void implCloseChannel() throws IOException {
+        try {
+            final Thread thread = blockingThread;
+            if (thread != null) {
+                thread.interrupt();
+            }
+        } finally {
+            try {
+                handle.close();
+            } finally {
+                sftp.close();
+            }
+        }
+    }
+
+    private void beginBlocking() {
+        begin();
+        blockingThread = Thread.currentThread();
+    }
+
+    private void endBlocking(boolean completed) throws AsynchronousCloseException {
+        blockingThread = null;
+        end(completed);
+    }
+
+    /**
+     * Checks that the channel is open and that its current mode contains
+     * at least one of the required ones
+     * @param reqModes The required modes - ignored if {@code null}/empty
+     * @throws IOException If channel not open or the required modes are not
+     * satisfied
+     */
+    private void ensureOpen(Collection<SftpClient.OpenMode> reqModes) throws IOException {
+        if (!isOpen()) {
+            throw new ClosedChannelException();
+        }
+        
+        if (GenericUtils.size(reqModes) > 0) {
+            for (SftpClient.OpenMode m : reqModes) {
+                if (this.modes.contains(m)) {
+                    return;
+                }
+            }
+            
+            throw new IOException("ensureOpen(" + p + ") current channel modes (" + this.modes + ") do contain any of the required: " + reqModes);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toString(p);
+    }
+}


[07/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
new file mode 100644
index 0000000..edbf82b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
@@ -0,0 +1,465 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.file.util.BaseFileSystem;
+import org.apache.sshd.common.file.util.ImmutableList;
+
+public class SftpFileSystem extends BaseFileSystem<SftpPath> {
+
+    private final ClientSession session;
+    private final Queue<SftpClient> pool;
+    private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
+    private SftpPath defaultDir;
+    private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+    private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+
+    public SftpFileSystem(SftpFileSystemProvider provider, ClientSession session) throws IOException {
+        super(provider);
+        this.session = session;
+        this.pool = new LinkedBlockingQueue<>(8);
+        try (SftpClient client = getClient()) {
+            defaultDir = getPath(client.canonicalPath("."));
+        }
+    }
+
+    public int getReadBufferSize() {
+        return readBufferSize;
+    }
+
+    public void setReadBufferSize(int size) {
+        if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
+            throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
+        }
+
+        readBufferSize = size;
+    }
+
+    public int getWriteBufferSize() {
+        return writeBufferSize;
+    }
+
+    public void setWriteBufferSize(int size) {
+        if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
+            throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
+        }
+
+        writeBufferSize = size;
+    }
+
+    @Override
+    protected SftpPath create(String root, ImmutableList<String> names) {
+        return new SftpPath(this, root, names);
+    }
+
+    public ClientSession getSession() {
+        return session;
+    }
+
+    @SuppressWarnings("synthetic-access")
+    public SftpClient getClient() throws IOException {
+        Wrapper wrapper = wrappers.get();
+        if (wrapper == null) {
+            while (wrapper == null) {
+                SftpClient client = pool.poll();
+                if (client == null) {
+                    client = session.createSftpClient();
+                }
+                if (!client.isClosing()) {
+                    wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
+                }
+            }
+            wrappers.set(wrapper);
+        } else {
+            wrapper.increment();
+        }
+        return wrapper;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (isOpen()) {
+            session.close(true);
+        }
+    }
+
+    @Override
+    public boolean isOpen() {
+        return !session.isClosing();
+    }
+
+    @Override
+    public Set<String> supportedFileAttributeViews() {
+        Set<String> set = new HashSet<>();
+        set.addAll(Arrays.asList("basic", "posix", "owner"));
+        return Collections.unmodifiableSet(set);
+    }
+
+    @Override
+    public UserPrincipalLookupService getUserPrincipalLookupService() {
+        return new DefaultUserPrincipalLookupService();
+    }
+
+    @Override
+    public SftpPath getDefaultDir() {
+        return defaultDir;
+    }
+
+    private class Wrapper extends AbstractSftpClient {
+
+        private final SftpClient delegate;
+        private final AtomicInteger count = new AtomicInteger(1);
+        private final int readSize, writeSize;
+
+        private Wrapper(SftpClient delegate, int readSize, int writeSize) {
+            this.delegate = delegate;
+            this.readSize = readSize;
+            this.writeSize = writeSize;
+        }
+
+        @Override
+        public int getVersion() {
+            return delegate.getVersion();
+        }
+
+        @Override
+        public boolean isClosing() {
+            return false;
+        }
+
+        @Override
+        public boolean isOpen() {
+            if (count.get() > 0) {
+                return true;
+            } else {
+                return false;   // debug breakpoint
+            }
+        }
+
+        @SuppressWarnings("synthetic-access")
+        @Override
+        public void close() throws IOException {
+            if (count.decrementAndGet() <= 0) {
+                if (!pool.offer(delegate)) {
+                    delegate.close();
+                }
+                wrappers.set(null);
+            }
+        }
+
+        public void increment() {
+            count.incrementAndGet();
+        }
+
+        @Override
+        public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("open(" + path + ")[" + options + "] client is closed");
+            }
+            return delegate.open(path, options);
+        }
+
+        @Override
+        public void close(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("close(" + handle + ") client is closed");
+            }
+            delegate.close(handle);
+        }
+
+        @Override
+        public void remove(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("remove(" + path + ") client is closed");
+            }
+            delegate.remove(path);
+        }
+
+        @Override
+        public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
+            }
+            delegate.rename(oldPath, newPath, options);
+        }
+
+        @Override
+        public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
+            }
+            return delegate.read(handle, fileOffset, dst, dstOffset, len);
+        }
+
+        @Override
+        public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
+            }
+            delegate.write(handle, fileOffset, src, srcOffset, len);
+        }
+
+        @Override
+        public void mkdir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("mkdir(" + path + ") client is closed");
+            }
+            delegate.mkdir(path);
+        }
+
+        @Override
+        public void rmdir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("rmdir(" + path + ") client is closed");
+            }
+            delegate.rmdir(path);
+        }
+
+        @Override
+        public CloseableHandle openDir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("openDir(" + path + ") client is closed");
+            }
+            return delegate.openDir(path);
+        }
+
+        @Override
+        public DirEntry[] readDir(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readDir(" + handle + ") client is closed");
+            }
+            return delegate.readDir(handle);
+        }
+
+        @Override
+        public String canonicalPath(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("canonicalPath(" + path + ") client is closed");
+            }
+            return delegate.canonicalPath(path);
+        }
+
+        @Override
+        public Attributes stat(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("stat(" + path + ") client is closed");
+            }
+            return delegate.stat(path);
+        }
+
+        @Override
+        public Attributes lstat(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("lstat(" + path + ") client is closed");
+            }
+            return delegate.lstat(path);
+        }
+
+        @Override
+        public Attributes stat(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("stat(" + handle + ") client is closed");
+            }
+            return delegate.stat(handle);
+        }
+
+        @Override
+        public void setStat(String path, Attributes attributes) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
+            }
+            delegate.setStat(path, attributes);
+        }
+
+        @Override
+        public void setStat(Handle handle, Attributes attributes) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
+            }
+            delegate.setStat(handle, attributes);
+        }
+
+        @Override
+        public String readLink(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readLink(" + path + ") client is closed");
+            }
+            return delegate.readLink(path);
+        }
+
+        @Override
+        public void symLink(String linkPath, String targetPath) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("symLink(" + linkPath + " => " + targetPath + ") client is closed");
+            }
+            delegate.symLink(linkPath, targetPath);
+        }
+
+        @Override
+        public Iterable<DirEntry> readDir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readDir(" + path + ") client is closed");
+            }
+            return delegate.readDir(path);
+        }
+
+        @Override
+        public InputStream read(String path) throws IOException {
+            return read(path, readSize);
+        }
+
+        @Override
+        public InputStream read(String path, OpenMode... mode) throws IOException {
+            return read(path, readSize, mode);
+        }
+
+        @Override
+        public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
+            return read(path, readSize, mode);
+        }
+
+        @Override
+        public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+            }
+            return delegate.read(path, bufferSize, mode);
+        }
+
+        @Override
+        public OutputStream write(String path) throws IOException {
+            return write(path, writeSize);
+        }
+
+        @Override
+        public OutputStream write(String path, OpenMode... mode) throws IOException {
+            return write(path, writeSize, mode);
+        }
+
+        @Override
+        public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
+            return write(path, writeSize, mode);
+        }
+
+        @Override
+        public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+            }
+            return delegate.write(path, bufferSize, mode);
+        }
+
+        @Override
+        public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("link(" + linkPath + " => " + targetPath + "] symbolic=" + symbolic + ": client is closed");
+            }
+            delegate.link(linkPath, targetPath, symbolic);
+        }
+
+        @Override
+        public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
+            }
+            delegate.lock(handle, offset, length, mask);
+        }
+
+        @Override
+        public void unlock(Handle handle, long offset, long length) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
+            }
+            delegate.unlock(handle, offset, length);
+        }
+    }
+
+    protected static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
+
+        @Override
+        public UserPrincipal lookupPrincipalByName(String name) throws IOException {
+            return new DefaultUserPrincipal(name);
+        }
+
+        @Override
+        public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
+            return new DefaultGroupPrincipal(group);
+        }
+    }
+
+    protected static class DefaultUserPrincipal implements UserPrincipal {
+
+        private final String name;
+
+        public DefaultUserPrincipal(String name) {
+            if (name == null) {
+                throw new IllegalArgumentException("name is null");
+            }
+            this.name = name;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DefaultUserPrincipal that = (DefaultUserPrincipal) o;
+            return name.equals(that.name);
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    protected static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
+
+        public DefaultGroupPrincipal(String name) {
+            super(name);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/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
new file mode 100644
index 0000000..0e5169c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -0,0 +1,892 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRGRP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IROTH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRUSR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWGRP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWOTH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWUSR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXGRP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXOTH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXUSR;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.ClientBuilder;
+import org.apache.sshd.client.SftpException;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SftpFileSystemProvider extends FileSystemProvider {
+    public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
+        public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+    public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
+        public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+    public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
+        public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
+
+    private final SshClient client;
+    private final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
+    protected final Logger log;
+
+    public SftpFileSystemProvider() {
+        this(null);
+    }
+
+    public SftpFileSystemProvider(SshClient client) {
+        this.log = LoggerFactory.getLogger(getClass());
+        if (client == null) {
+            // TODO: make this configurable using system properties
+            client = ClientBuilder.builder().build();
+        }
+        this.client = client;
+        this.client.start();
+    }
+
+    @Override
+    public String getScheme() {
+        return SftpConstants.SFTP_SUBSYSTEM_NAME;
+    }
+
+    @Override
+    public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+        synchronized (fileSystems) {
+            String authority = uri.getAuthority();
+            SftpFileSystem fileSystem = fileSystems.get(authority);
+            if (fileSystem != null) {
+                throw new FileSystemAlreadyExistsException(authority);
+            }
+            String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+            String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+            String[] ui = GenericUtils.split(userInfo, ':');
+            int port = uri.getPort();
+            if (port <= 0) {
+                port = SshConfigFileReader.DEFAULT_PORT;
+            }
+
+            ClientSession session=null;
+            try {
+                session = client.connect(ui[0], host, port)
+                                .verify(FactoryManagerUtils.getLongProperty(env, CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME))
+                                .getSession()
+                                ;
+                session.addPasswordIdentity(ui[1]);
+                session.auth().verify();
+                fileSystem = new SftpFileSystem(this, session);
+                fileSystem.setReadBufferSize(FactoryManagerUtils.getIntProperty(env, READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+                fileSystem.setWriteBufferSize(FactoryManagerUtils.getIntProperty(env, WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+                fileSystems.put(authority, fileSystem);
+                return fileSystem;
+            } catch(Exception e) {
+                if (session != null) {
+                    try {
+                        session.close();
+                    } catch(IOException t) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Failed (" + t.getClass().getSimpleName() + ")"
+                                    + " to close session for new file system on " + host + ":" + port
+                                    + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
+                                    + ": " + t.getMessage());
+                        }
+                    }
+                }
+                
+                if (e instanceof IOException) {
+                    throw (IOException) e;
+                } else if (e instanceof RuntimeException) {
+                    throw (RuntimeException) e;
+                } else {
+                    throw new IOException(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public FileSystem getFileSystem(URI uri) {
+        synchronized (fileSystems) {
+            String authority = uri.getAuthority();
+            SftpFileSystem fileSystem = fileSystems.get(authority);
+            if (fileSystem == null) {
+                throw new FileSystemNotFoundException(authority);
+            }
+            return fileSystem;
+        }
+    }
+
+    @Override
+    public Path getPath(URI uri) {
+        FileSystem fs = getFileSystem(uri);
+        return fs.getPath(uri.getPath());
+    }
+
+    @Override
+    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        return newFileChannel(path, options, attrs);
+    }
+
+    @Override
+    public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        Collection<SftpClient.OpenMode> modes = EnumSet.noneOf(SftpClient.OpenMode.class);
+        for (OpenOption option : options) {
+            if (option == StandardOpenOption.READ) {
+                modes.add(SftpClient.OpenMode.Read);
+            } else if (option == StandardOpenOption.APPEND) {
+                modes.add(SftpClient.OpenMode.Append);
+            } else if (option == StandardOpenOption.CREATE) {
+                modes.add(SftpClient.OpenMode.Create);
+            } else if (option == StandardOpenOption.TRUNCATE_EXISTING) {
+                modes.add(SftpClient.OpenMode.Truncate);
+            } else if (option == StandardOpenOption.WRITE) {
+                modes.add(SftpClient.OpenMode.Write);
+            } else if (option == StandardOpenOption.CREATE_NEW) {
+                modes.add(SftpClient.OpenMode.Create);
+                modes.add(SftpClient.OpenMode.Exclusive);
+            } else if (option == StandardOpenOption.SPARSE) {
+                /*
+                 * As per the Javadoc:
+                 * 
+                 *      The option is ignored when the file system does not
+                 *  support the creation of sparse files
+                 */
+                continue;
+            } else {
+                throw new IllegalArgumentException("newFileChannel(" + path + ") unsupported open option: " + option);
+            }
+        }
+        if (modes.isEmpty()) {
+            modes.add(SftpClient.OpenMode.Read);
+            modes.add(SftpClient.OpenMode.Write);
+        }
+        // TODO: attrs
+        return new SftpFileChannel(toSftpPath(path), modes);
+    }
+
+    @Override
+    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+        final SftpPath p = toSftpPath(dir);
+        return new DirectoryStream<Path>() {
+            private final SftpFileSystem fs = p.getFileSystem();
+            private final SftpClient sftp = fs.getClient();
+            private final Iterable<SftpClient.DirEntry> iter = sftp.readDir(p.toString());
+
+            @Override
+            public Iterator<Path> iterator() {
+                return new Iterator<Path>() {
+                    @SuppressWarnings("synthetic-access")
+                    private final Iterator<SftpClient.DirEntry> it = iter.iterator();
+
+                    @Override
+                    public boolean hasNext() {
+                        return it.hasNext();
+                    }
+
+                    @Override
+                    public Path next() {
+                        SftpClient.DirEntry entry = it.next();
+                        return p.resolve(entry.filename);
+                    }
+
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
+                    }
+                };
+            }
+
+            @Override
+            public void close() throws IOException {
+                sftp.close();
+            }
+        };
+    }
+
+    @Override
+    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+        SftpPath p = toSftpPath(dir);
+        SftpFileSystem fs = p.getFileSystem();
+        try (SftpClient sftp = fs.getClient()) {
+            try {
+                sftp.mkdir(dir.toString());
+            } catch (SftpException e) {
+                int sftpStatus=e.getStatus();
+                if ((sftp.getVersion() == SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
+                    try {
+                        Attributes attributes = sftp.stat(dir.toString());
+                        if (attributes != null) {
+                            throw new FileAlreadyExistsException(p.toString());
+                        }
+                    } catch (SshException e2) {
+                        e.addSuppressed(e2);
+                    }
+                }
+                if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
+                    throw new FileAlreadyExistsException(p.toString());
+                }
+                throw e;
+            }
+            for (FileAttribute<?> attr : attrs) {
+                setAttribute(p, attr.name(), attr.value());
+            }
+        }
+    }
+
+    @Override
+    public void delete(Path path) throws IOException {
+        SftpPath p = toSftpPath(path);
+        checkAccess(p, AccessMode.WRITE);
+        
+        SftpFileSystem fs = p.getFileSystem();
+        try (SftpClient sftp = fs.getClient()) {
+            BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
+            if (attributes.isDirectory()) {
+                sftp.rmdir(path.toString());
+            } else {
+                sftp.remove(path.toString());
+            }
+        }
+    }
+
+    @Override
+    public void copy(Path source, Path target, CopyOption... options) throws IOException {
+        SftpPath src = toSftpPath(source);
+        SftpPath dst = toSftpPath(target);
+        if (src.getFileSystem() != dst.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+        }
+        checkAccess(src);
+
+        boolean replaceExisting = false;
+        boolean copyAttributes = false;
+        boolean noFollowLinks = false;
+        for (CopyOption opt : options) {
+            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+        }
+        LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
+
+        // attributes of source file
+        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+        if (attrs.isSymbolicLink())
+            throw new IOException("Copying of symbolic links not supported");
+
+        // delete target if it exists and REPLACE_EXISTING is specified
+        Boolean status=IoUtils.checkFileExists(target, linkOptions);
+        if (status == null) {
+            throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
+        }
+
+        if (replaceExisting) {
+            deleteIfExists(target);
+        } else {
+            if (status.booleanValue()) {
+                throw new FileAlreadyExistsException(target.toString());
+            }
+        }
+
+        // create directory or copy file
+        if (attrs.isDirectory()) {
+            createDirectory(target);
+        } else {
+            try (InputStream in = newInputStream(source);
+                 OutputStream os = newOutputStream(target)) {
+                IoUtils.copy(in, os);
+            }
+        }
+
+        // copy basic attributes to target
+        if (copyAttributes) {
+            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+            try {
+                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+            } catch (Throwable x) {
+                // rollback
+                try {
+                    delete(target);
+                } catch (Throwable suppressed) {
+                    x.addSuppressed(suppressed);
+                }
+                throw x;
+            }
+        }
+    }
+
+    @Override
+    public void move(Path source, Path target, CopyOption... options) throws IOException {
+        SftpPath src = toSftpPath(source);
+        SftpFileSystem fsSrc = src.getFileSystem(); 
+        SftpPath dst = toSftpPath(target);
+        
+        if (src.getFileSystem() != dst.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+        }
+        checkAccess(src);
+
+        boolean replaceExisting = false;
+        boolean copyAttributes = false;
+        boolean noFollowLinks = false;
+        for (CopyOption opt : options) {
+            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+        }
+        LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
+
+        // attributes of source file
+        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+        if (attrs.isSymbolicLink()) {
+            throw new IOException("Copying of symbolic links not supported");
+        }
+
+        // delete target if it exists and REPLACE_EXISTING is specified
+        Boolean status=IoUtils.checkFileExists(target, linkOptions);
+        if (status == null) {
+            throw new AccessDeniedException("Existence cannot be determined for move target " + target);
+        }
+
+        if (replaceExisting) {
+            deleteIfExists(target);
+        } else if (status.booleanValue()) {
+            throw new FileAlreadyExistsException(target.toString());
+        }
+
+        try (SftpClient sftp = fsSrc.getClient()) {
+            sftp.rename(src.toString(), dst.toString());
+        }
+
+        // copy basic attributes to target
+        if (copyAttributes) {
+            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+            try {
+                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+            } catch (Throwable x) {
+                // rollback
+                try {
+                    delete(target);
+                } catch (Throwable suppressed) {
+                    x.addSuppressed(suppressed);
+                }
+                throw x;
+            }
+        }
+    }
+
+    @Override
+    public boolean isSameFile(Path path1, Path path2) throws IOException {
+        SftpPath p1 = toSftpPath(path1);
+        SftpPath p2 = toSftpPath(path2);
+        if (p1.getFileSystem() != p2.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
+        }
+        checkAccess(p1);
+        checkAccess(p2);
+        return p1.equals(p2);
+    }
+
+    @Override
+    public boolean isHidden(Path path) throws IOException {
+        return false;
+    }
+
+    @Override
+    public FileStore getFileStore(Path path) throws IOException {
+        throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") N/A");
+    }
+
+    @Override
+    public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
+        SftpPath l = toSftpPath(link);
+        SftpFileSystem fsLink = l.getFileSystem();
+        SftpPath t = toSftpPath(target);
+        if (fsLink != t.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
+        }
+        try (SftpClient client = fsLink.getClient()) {
+            client.symLink(l.toString(), t.toString());
+        }
+    }
+
+    @Override
+    public Path readSymbolicLink(Path link) throws IOException {
+        SftpPath l = toSftpPath(link);
+        SftpFileSystem fsLink = l.getFileSystem();
+        try (SftpClient client = fsLink.getClient()) {
+            return fsLink.getPath(client.readLink(l.toString()));
+        }
+    }
+
+    @Override
+    public void checkAccess(Path path, AccessMode... modes) throws IOException {
+        SftpPath p = toSftpPath(path);
+        boolean w = false;
+        boolean x = false;
+        if (GenericUtils.length(modes) > 0) {
+            for (AccessMode mode : modes) {
+                switch (mode) {
+                    case READ:
+                        break;
+                    case WRITE:
+                        w = true;
+                        break;
+                    case EXECUTE:
+                        x = true;
+                        break;
+                    default:
+                        throw new UnsupportedOperationException("Unsupported mode: " + mode);
+                }
+            }
+        }
+
+        BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
+        if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
+            throw new NoSuchFileException(path.toString());
+        }
+        
+        SftpFileSystem fs = p.getFileSystem();
+        if (x || (w && fs.isReadOnly())) {
+            throw new AccessDeniedException(path.toString());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V extends FileAttributeView> V getFileAttributeView(final Path path, Class<V> type, final LinkOption... options) {
+        if (type.isAssignableFrom(PosixFileAttributeView.class)) {
+            return (V) new PosixFileAttributeView() {
+                @Override
+                public String name() {
+                    return "view";
+                }
+
+                @SuppressWarnings("synthetic-access")
+                @Override
+                public PosixFileAttributes readAttributes() throws IOException {
+                    SftpPath p = toSftpPath(path);
+                    SftpFileSystem fs = p.getFileSystem();
+                    final SftpClient.Attributes attributes;
+                    try (SftpClient client =fs.getClient()) {
+                        try {
+                            if (followLinks(options)) {
+                                attributes = client.stat(p.toString());
+                            } else {
+                                attributes = client.lstat(p.toString());
+                            }
+                        } catch (SftpException e) {
+                            if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
+                                throw new NoSuchFileException(p.toString());
+                            }
+                            throw e;
+                        }
+                    }
+                    return new PosixFileAttributes() {
+                        @Override
+                        public UserPrincipal owner() {
+                            return attributes.owner != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.owner) : null;
+                        }
+
+                        @Override
+                        public GroupPrincipal group() {
+                            return attributes.group != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.group) : null;
+                        }
+
+                        @Override
+                        public Set<PosixFilePermission> permissions() {
+                            return permissionsToAttributes(attributes.perms);
+                        }
+
+                        @Override
+                        public FileTime lastModifiedTime() {
+                            return FileTime.from(attributes.mtime, TimeUnit.SECONDS);
+                        }
+
+                        @Override
+                        public FileTime lastAccessTime() {
+                            return FileTime.from(attributes.atime, TimeUnit.SECONDS);
+                        }
+
+                        @Override
+                        public FileTime creationTime() {
+                            return FileTime.from(attributes.ctime, TimeUnit.SECONDS);
+                        }
+
+                        @Override
+                        public boolean isRegularFile() {
+                            return attributes.isRegularFile();
+                        }
+
+                        @Override
+                        public boolean isDirectory() {
+                            return attributes.isDirectory();
+                        }
+
+                        @Override
+                        public boolean isSymbolicLink() {
+                            return attributes.isSymbolicLink();
+                        }
+
+                        @Override
+                        public boolean isOther() {
+                            return attributes.isOther();
+                        }
+
+                        @Override
+                        public long size() {
+                            return attributes.size;
+                        }
+
+                        @Override
+                        public Object fileKey() {
+                            // TODO
+                            return null;
+                        }
+                    };
+                }
+
+                @Override
+                public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+                    if (lastModifiedTime != null) {
+                        setAttribute(path, "lastModifiedTime", lastModifiedTime, options);
+                    }
+                    if (lastAccessTime != null) {
+                        setAttribute(path, "lastAccessTime", lastAccessTime, options);
+                    }
+                    if (createTime != null) {
+                        setAttribute(path, "createTime", createTime, options);
+                    }
+                }
+
+                @Override
+                public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
+                    setAttribute(path, "permissions", perms, options);
+                }
+
+                @Override
+                public void setGroup(GroupPrincipal group) throws IOException {
+                    setAttribute(path, "group", group, options);
+                }
+
+                @Override
+                public UserPrincipal getOwner() throws IOException {
+                    return readAttributes().owner();
+                }
+
+                @Override
+                public void setOwner(UserPrincipal owner) throws IOException {
+                    setAttribute(path, "owner", owner, options);
+                }
+            };
+        } else {
+            throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
+        }
+    }
+
+    @Override
+    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+        if (type.isAssignableFrom(PosixFileAttributes.class)) {
+            return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
+        }
+
+        throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
+    }
+
+    @Override
+    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+        String view;
+        String attrs;
+        int i = attributes.indexOf(':');
+        if (i == -1) {
+            view = "basic";
+            attrs = attributes;
+        } else {
+            view = attributes.substring(0, i++);
+            attrs = attributes.substring(i);
+        }
+        SftpPath p = toSftpPath(path);
+        SftpFileSystem fs = p.getFileSystem();
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
+            throw new UnsupportedOperationException("readAttributes(" + path + ")[" + attributes + "] view " + view + " not supported: " + views);
+        }
+
+        PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
+        if ("*".equals(attrs)) {
+            attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
+        }
+        Map<String, Object> map = new HashMap<>();
+        for (String attr : attrs.split(",")) {
+            switch (attr) {
+                case "lastModifiedTime":
+                    map.put(attr, v.lastModifiedTime());
+                    break;
+                case "lastAccessTime":
+                    map.put(attr, v.lastAccessTime());
+                    break;
+                case "creationTime":
+                    map.put(attr, v.creationTime());
+                    break;
+                case "size":
+                    map.put(attr, Long.valueOf(v.size()));
+                    break;
+                case "isRegularFile":
+                    map.put(attr, Boolean.valueOf(v.isRegularFile()));
+                    break;
+                case "isDirectory":
+                    map.put(attr, Boolean.valueOf(v.isDirectory()));
+                    break;
+                case "isSymbolicLink":
+                    map.put(attr, Boolean.valueOf(v.isSymbolicLink()));
+                    break;
+                case "isOther":
+                    map.put(attr, Boolean.valueOf(v.isOther()));
+                    break;
+                case "fileKey":
+                    map.put(attr, v.fileKey());
+                    break;
+                case "owner":
+                    map.put(attr, v.owner());
+                    break;
+                case "permissions":
+                    map.put(attr, v.permissions());
+                    break;
+                case "group":
+                    map.put(attr, v.group());
+                    break;
+                default:
+                    if (log.isTraceEnabled()) {
+                        log.trace("readAttributes({})[{}] ignored {}={}", path, attributes, attr, v);
+                    }
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+        String view;
+        String attr;
+        int i = attribute.indexOf(':');
+        if (i == -1) {
+            view = "basic";
+            attr = attribute;
+        } else {
+            view = attribute.substring(0, i++);
+            attr = attribute.substring(i);
+        }
+        SftpPath p = toSftpPath(path);
+        SftpFileSystem fs = p.getFileSystem();
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if (GenericUtils.isEmpty(views) || (!view.contains(view))) {
+            throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "] view " + view + " not supported: " + views);
+        }
+
+        SftpClient.Attributes attributes = new SftpClient.Attributes();
+        switch (attr) {
+            case "lastModifiedTime":
+                attributes.mtime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+                break;
+            case "lastAccessTime":
+                attributes.atime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+                break;
+            case "creationTime":
+                attributes.ctime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+                break;
+            case "size":
+                attributes.size(((Number) value).longValue());
+                break;
+            case "permissions": {
+                @SuppressWarnings("unchecked")
+                Set<PosixFilePermission>    attrSet = (Set<PosixFilePermission>) value;
+                attributes.perms(attributesToPermissions(path, attrSet));
+                }
+                break;
+            case "owner":
+                attributes.owner(((UserPrincipal) value).getName());
+                break;
+            case "group":
+                attributes.group(((GroupPrincipal) value).getName());
+                break;
+            case "isRegularFile":
+            case "isDirectory":
+            case "isSymbolicLink":
+            case "isOther":
+            case "fileKey":
+                throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "]"
+                                                       + " unknown view=" + view + " attribute: " + attr);
+            default:
+                if (log.isTraceEnabled()) {
+                    log.trace("setAttribute({})[{}] ignore {}={}", path, attribute, attr, value);
+                }
+        }
+
+        try (SftpClient client = fs.getClient()) {
+            client.setStat(p.toString(), attributes);
+        }
+    }
+
+    private SftpPath toSftpPath(Path path) {
+        ValidateUtils.checkNotNull(path, "No path provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+        if (!(path instanceof SftpPath)) {
+            throw new ProviderMismatchException("Path is not SFTP: " + path);
+        }
+        return (SftpPath) path;
+    }
+
+    static boolean followLinks(LinkOption... paramVarArgs) {
+        boolean bool = true;
+        for (LinkOption localLinkOption : paramVarArgs) {
+            if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
+                bool = false;
+            }
+        }
+        return bool;
+    }
+
+    private Set<PosixFilePermission> permissionsToAttributes(int perms) {
+        Set<PosixFilePermission> p = new HashSet<>();
+        if ((perms & S_IRUSR) != 0) {
+            p.add(PosixFilePermission.OWNER_READ);
+        }
+        if ((perms & S_IWUSR) != 0) {
+            p.add(PosixFilePermission.OWNER_WRITE);
+        }
+        if ((perms & S_IXUSR) != 0) {
+            p.add(PosixFilePermission.OWNER_EXECUTE);
+        }
+        if ((perms & S_IRGRP) != 0) {
+            p.add(PosixFilePermission.GROUP_READ);
+        }
+        if ((perms & S_IWGRP) != 0) {
+            p.add(PosixFilePermission.GROUP_WRITE);
+        }
+        if ((perms & S_IXGRP) != 0) {
+            p.add(PosixFilePermission.GROUP_EXECUTE);
+        }
+        if ((perms & S_IROTH) != 0) {
+            p.add(PosixFilePermission.OTHERS_READ);
+        }
+        if ((perms & S_IWOTH) != 0) {
+            p.add(PosixFilePermission.OTHERS_WRITE);
+        }
+        if ((perms & S_IXOTH) != 0) {
+            p.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+        return p;
+    }
+
+    protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
+        if (GenericUtils.isEmpty(perms)) {
+            return 0;
+        }
+
+        int pf = 0;
+        for (PosixFilePermission p : perms) {
+            switch (p) {
+                case OWNER_READ:
+                    pf |= S_IRUSR;
+                    break;
+                case OWNER_WRITE:
+                    pf |= S_IWUSR;
+                    break;
+                case OWNER_EXECUTE:
+                    pf |= S_IXUSR;
+                    break;
+                case GROUP_READ:
+                    pf |= S_IRGRP;
+                    break;
+                case GROUP_WRITE:
+                    pf |= S_IWGRP;
+                    break;
+                case GROUP_EXECUTE:
+                    pf |= S_IXGRP;
+                    break;
+                case OTHERS_READ:
+                    pf |= S_IROTH;
+                    break;
+                case OTHERS_WRITE:
+                    pf |= S_IWOTH;
+                    break;
+                case OTHERS_EXECUTE:
+                    pf |= S_IXOTH;
+                    break;
+                default:
+                    if (log.isTraceEnabled()) {
+                        log.trace("attributesToPermissions(" + path + ") ignored " + p);
+                    }
+            }
+        }
+
+        return pf;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
new file mode 100644
index 0000000..f130d1a
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.spi.FileSystemProvider;
+
+import org.apache.sshd.common.file.util.BasePath;
+import org.apache.sshd.common.file.util.ImmutableList;
+
+public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
+    public SftpPath(SftpFileSystem fileSystem, String root, ImmutableList<String> names) {
+        super(fileSystem, root, names);
+    }
+
+    @Override
+    public SftpPath toRealPath(LinkOption... options) throws IOException {
+//        try (SftpClient client = fileSystem.getClient()) {
+//            client.realP
+//        }
+        // TODO: handle links
+        SftpPath absolute = toAbsolutePath();
+        FileSystem fs = getFileSystem();
+        FileSystemProvider provider = fs.provider();
+        provider.checkAccess(absolute);
+        return absolute;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
index bad235f..ef67e34 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
@@ -33,7 +33,6 @@ import java.nio.file.FileStore;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystemAlreadyExistsException;
 import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
@@ -73,42 +72,52 @@ public class RootedFileSystemProvider extends FileSystemProvider {
     @Override
     public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
         Path path = uriToPath(uri);
-        synchronized (fileSystems)
-        {
-            Path localPath2 = null;
-            if (ensureDirectory(path))
-            {
-                localPath2 = path.toRealPath();
-                if (this.fileSystems.containsKey(localPath2)) {
-                    throw new FileSystemAlreadyExistsException();
-                }
+        Path localPath2 = ensureDirectory(path).toRealPath();
+
+        RootedFileSystem rootedFs=null;
+        synchronized (fileSystems) {
+            if (!this.fileSystems.containsKey(localPath2)) {
+                rootedFs = new RootedFileSystem(this, path, env);
+                this.fileSystems.put(localPath2, rootedFs);
             }
-            RootedFileSystem rootedFs = new RootedFileSystem(this, path, env);
-            this.fileSystems.put(localPath2, rootedFs);
-            return rootedFs;
         }
+
+        // do all the throwing outside the synchronized block to minimize its lock time
+        if (rootedFs == null) {
+            throw new FileSystemAlreadyExistsException("newFileSystem(" + uri + ") already mapped " + localPath2);
+        }
+
+        return rootedFs;
     }
 
     @Override
     public FileSystem getFileSystem(URI uri) {
+        Path path = uriToPath(uri);
+        Path real;
+        try {
+            real = path.toRealPath();
+        } catch (IOException e) {
+            FileSystemNotFoundException err = new FileSystemNotFoundException(uri.toString());
+            err.initCause(e);
+            throw err;
+        }
+
+        RootedFileSystem fileSystem = null;
         synchronized (fileSystems) {
-            RootedFileSystem fileSystem = null;
-            try {
-                fileSystem = fileSystems.get(uriToPath(uri).toRealPath());
-            } catch (IOException ignore) {
-                // ignored
-            }
-            if (fileSystem == null) {
-                throw new FileSystemNotFoundException(uri.toString());
-            }
-            return fileSystem;
+            fileSystem = fileSystems.get(real);
         }
+
+        // do all the throwing outside the synchronized block to minimize its lock time
+        if (fileSystem == null) {
+            throw new FileSystemNotFoundException(uri.toString());
+        }
+
+        return fileSystem;
     }
 
     @Override
     public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
-        ensureDirectory(path);
-        return new RootedFileSystem(this, path, env);
+        return new RootedFileSystem(this, ensureDirectory(path), env);
     }
 
     protected Path uriToPath(URI uri) {
@@ -130,11 +139,8 @@ public class RootedFileSystemProvider extends FileSystemProvider {
         }
     }
 
-    private boolean ensureDirectory(Path path) {
-        if (!Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
-            throw new UnsupportedOperationException("Not a directory: " + path);
-        }
-        return true;
+    private static Path ensureDirectory(Path path) {
+        return IoUtils.ensureDirectory(path, IoUtils.getLinkOptions(false));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java b/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java
deleted file mode 100644
index dd54bf3..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.common.sftp;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpConstants {
-    public static String SFTP_SUBSYSTEM_NAME = "sftp";
-
-    public static final int SSH_FXP_INIT =             1;
-    public static final int SSH_FXP_VERSION =          2;
-    public static final int SSH_FXP_OPEN =             3;
-    public static final int SSH_FXP_CLOSE =            4;
-    public static final int SSH_FXP_READ =             5;
-    public static final int SSH_FXP_WRITE =            6;
-    public static final int SSH_FXP_LSTAT =            7;
-    public static final int SSH_FXP_FSTAT =            8;
-    public static final int SSH_FXP_SETSTAT =          9;
-    public static final int SSH_FXP_FSETSTAT =        10;
-    public static final int SSH_FXP_OPENDIR =         11;
-    public static final int SSH_FXP_READDIR =         12;
-    public static final int SSH_FXP_REMOVE =          13;
-    public static final int SSH_FXP_MKDIR =           14;
-    public static final int SSH_FXP_RMDIR =           15;
-    public static final int SSH_FXP_REALPATH =        16;
-    public static final int SSH_FXP_STAT =            17;
-    public static final int SSH_FXP_RENAME =          18;
-    public static final int SSH_FXP_READLINK =        19;
-    public static final int SSH_FXP_SYMLINK =         20; // v3 -> v5
-    public static final int SSH_FXP_LINK =            21; // v6
-    public static final int SSH_FXP_BLOCK =           22; // v6
-    public static final int SSH_FXP_UNBLOCK =         23; // v6
-    public static final int SSH_FXP_STATUS =         101;
-    public static final int SSH_FXP_HANDLE =         102;
-    public static final int SSH_FXP_DATA =           103;
-    public static final int SSH_FXP_NAME =           104;
-    public static final int SSH_FXP_ATTRS =          105;
-    public static final int SSH_FXP_EXTENDED =       200;
-    public static final int SSH_FXP_EXTENDED_REPLY = 201;
-
-    public static final int SSH_FX_OK =                           0;
-    public static final int SSH_FX_EOF =                          1;
-    public static final int SSH_FX_NO_SUCH_FILE =                 2;
-    public static final int SSH_FX_PERMISSION_DENIED =            3;
-    public static final int SSH_FX_FAILURE =                      4;
-    public static final int SSH_FX_BAD_MESSAGE =                  5;
-    public static final int SSH_FX_NO_CONNECTION =                6;
-    public static final int SSH_FX_CONNECTION_LOST =              7;
-    public static final int SSH_FX_OP_UNSUPPORTED =               8;
-    public static final int SSH_FX_INVALID_HANDLE =               9;
-    public static final int SSH_FX_NO_SUCH_PATH =                10;
-    public static final int SSH_FX_FILE_ALREADY_EXISTS =         11;
-    public static final int SSH_FX_WRITE_PROTECT =               12;
-    public static final int SSH_FX_NO_MEDIA =                    13;
-    public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM =      14;
-    public static final int SSH_FX_QUOTA_EXCEEDED =              15;
-    public static final int SSH_FX_UNKNOWN_PRINCIPLE =           16;
-    public static final int SSH_FX_LOCK_CONFLICT =               17;
-    public static final int SSH_FX_DIR_NOT_EMPTY =               18;
-    public static final int SSH_FX_NOT_A_DIRECTORY =             19;
-    public static final int SSH_FX_INVALID_FILENAME =            20;
-    public static final int SSH_FX_LINK_LOOP =                   21;
-    public static final int SSH_FX_CANNOT_DELETE =               22;
-    public static final int SSH_FX_INVALID_PARAMETER =           23;
-    public static final int SSH_FX_FILE_IS_A_DIRECTORY =         24;
-    public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT =    25;
-    public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED =     26;
-    public static final int SSH_FX_DELETE_PENDING =              27;
-    public static final int SSH_FX_FILE_CORRUPT =                28;
-    public static final int SSH_FX_OWNER_INVALID =               29;
-    public static final int SSH_FX_GROUP_INVALID =               30;
-    public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31;
-
-    public static final int SSH_FILEXFER_ATTR_SIZE =              0x00000001;
-    public static final int SSH_FILEXFER_ATTR_UIDGID =            0x00000002;
-    public static final int SSH_FILEXFER_ATTR_PERMISSIONS =       0x00000004;
-    public static final int SSH_FILEXFER_ATTR_ACMODTIME =         0x00000008; // v3 naming convention
-    public static final int SSH_FILEXFER_ATTR_ACCESSTIME =        0x00000008; // v4
-    public static final int SSH_FILEXFER_ATTR_CREATETIME =        0x00000010; // v4
-    public static final int SSH_FILEXFER_ATTR_MODIFYTIME =        0x00000020; // v4
-    public static final int SSH_FILEXFER_ATTR_ACL =               0x00000040; // v4
-    public static final int SSH_FILEXFER_ATTR_OWNERGROUP =        0x00000080; // v4
-    public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES =   0x00000100; // v5
-    public static final int SSH_FILEXFER_ATTR_BITS =              0x00000200; // v5
-    public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE =   0x00000400; // v6
-    public static final int SSH_FILEXFER_ATTR_TEXT_HINT =         0x00000800; // v6
-    public static final int SSH_FILEXFER_ATTR_MIME_TYPE =         0x00001000; // v6
-    public static final int SSH_FILEXFER_ATTR_LINK_COUNT =        0x00002000; // v6
-    public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; // v6
-    public static final int SSH_FILEXFER_ATTR_CTIME =             0x00008000; // v6
-    public static final int SSH_FILEXFER_ATTR_EXTENDED =          0x80000000;
-
-    public static final int SSH_FILEXFER_ATTR_ALL =               0x0000FFFF; // All attributes
-
-    public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY =         0x00000001;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM =           0x00000002;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN =           0x00000004;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE =          0x00000010;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED =        0x00000020;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED =       0x00000040;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE =           0x00000080;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY =      0x00000100;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE =        0x00000200;
-    public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC =             0x00000400;
-
-    public static final int SSH_FILEXFER_TYPE_REGULAR =      1;
-    public static final int SSH_FILEXFER_TYPE_DIRECTORY =    2;
-    public static final int SSH_FILEXFER_TYPE_SYMLINK =      3;
-    public static final int SSH_FILEXFER_TYPE_SPECIAL =      4;
-    public static final int SSH_FILEXFER_TYPE_UNKNOWN =      5;
-    public static final int SSH_FILEXFER_TYPE_SOCKET =       6; // v5
-    public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE =  7; // v5
-    public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; // v5
-    public static final int SSH_FILEXFER_TYPE_FIFO         = 9; // v5
-
-    public static final int SSH_FXF_READ =   0x00000001;
-    public static final int SSH_FXF_WRITE =  0x00000002;
-    public static final int SSH_FXF_APPEND = 0x00000004;
-    public static final int SSH_FXF_CREAT =  0x00000008;
-    public static final int SSH_FXF_TRUNC =  0x00000010;
-    public static final int SSH_FXF_EXCL =   0x00000020;
-    public static final int SSH_FXF_TEXT =   0x00000040;
-
-    public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007;
-    public static final int SSH_FXF_CREATE_NEW =         0x00000000;
-    public static final int SSH_FXF_CREATE_TRUNCATE =    0x00000001;
-    public static final int SSH_FXF_OPEN_EXISTING =      0x00000002;
-    public static final int SSH_FXF_OPEN_OR_CREATE =     0x00000003;
-    public static final int SSH_FXF_TRUNCATE_EXISTING =  0x00000004;
-    public static final int SSH_FXF_APPEND_DATA =        0x00000008;
-    public static final int SSH_FXF_APPEND_DATA_ATOMIC = 0x00000010;
-    public static final int SSH_FXF_TEXT_MODE =          0x00000020;
-    public static final int SSH_FXF_READ_LOCK =          0x00000040;
-    public static final int SSH_FXF_WRITE_LOCK =         0x00000080;
-    public static final int SSH_FXF_DELETE_LOCK =        0x00000100;
-
-    public static final int SSH_FXP_RENAME_OVERWRITE = 0x00000001;
-    public static final int SSH_FXP_RENAME_ATOMIC =    0x00000002;
-    public static final int SSH_FXP_RENAME_NATIVE =    0x00000004;
-
-    public static final int SSH_FXP_REALPATH_NO_CHECK    = 0x00000001;
-    public static final int SSH_FXP_REALPATH_STAT_IF     = 0x00000002;
-    public static final int SSH_FXP_REALPATH_STAT_ALWAYS = 0x00000003;
-
-    public static final int SSH_FXF_RENAME_OVERWRITE =  0x00000001;
-    public static final int SSH_FXF_RENAME_ATOMIC =     0x00000002;
-    public static final int SSH_FXF_RENAME_NATIVE =     0x00000004;
-
-    public static final int ACE4_ACCESS_ALLOWED_ACE_TYPE      = 0x00000000;
-    public static final int ACE4_ACCESS_DENIED_ACE_TYPE       = 0x00000001;
-    public static final int ACE4_SYSTEM_AUDIT_ACE_TYPE        = 0x00000002;
-    public static final int ACE4_SYSTEM_ALARM_ACE_TYPE        = 0x00000003;
-
-    public static final int ACE4_FILE_INHERIT_ACE             = 0x00000001;
-    public static final int ACE4_DIRECTORY_INHERIT_ACE        = 0x00000002;
-    public static final int ACE4_NO_PROPAGATE_INHERIT_ACE     = 0x00000004;
-    public static final int ACE4_INHERIT_ONLY_ACE             = 0x00000008;
-    public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG   = 0x00000010;
-    public static final int ACE4_FAILED_ACCESS_ACE_FLAG       = 0x00000020;
-    public static final int ACE4_IDENTIFIER_GROUP             = 0x00000040;
-
-    public static final int ACE4_READ_DATA            = 0x00000001;
-    public static final int ACE4_LIST_DIRECTORY       = 0x00000001;
-    public static final int ACE4_WRITE_DATA           = 0x00000002;
-    public static final int ACE4_ADD_FILE             = 0x00000002;
-    public static final int ACE4_APPEND_DATA          = 0x00000004;
-    public static final int ACE4_ADD_SUBDIRECTORY     = 0x00000004;
-    public static final int ACE4_READ_NAMED_ATTRS     = 0x00000008;
-    public static final int ACE4_WRITE_NAMED_ATTRS    = 0x00000010;
-    public static final int ACE4_EXECUTE              = 0x00000020;
-    public static final int ACE4_DELETE_CHILD         = 0x00000040;
-    public static final int ACE4_READ_ATTRIBUTES      = 0x00000080;
-    public static final int ACE4_WRITE_ATTRIBUTES     = 0x00000100;
-    public static final int ACE4_DELETE               = 0x00010000;
-    public static final int ACE4_READ_ACL             = 0x00020000;
-    public static final int ACE4_WRITE_ACL            = 0x00040000;
-    public static final int ACE4_WRITE_OWNER          = 0x00080000;
-    public static final int ACE4_SYNCHRONIZE          = 0x00100000;
-
-    public static final int S_IFMT =   0170000;  // bitmask for the file type bitfields
-    public static final int S_IFSOCK = 0140000;  // socket
-    public static final int S_IFLNK =  0120000;  // symbolic link
-    public static final int S_IFREG =  0100000;  // regular file
-    public static final int S_IFBLK =  0060000;  // block device
-    public static final int S_IFDIR =  0040000;  // directory
-    public static final int S_IFCHR =  0020000;  // character device
-    public static final int S_IFIFO =  0010000;  // fifo
-    public static final int S_ISUID =  0004000;  // set UID bit
-    public static final int S_ISGID =  0002000;  // set GID bit
-    public static final int S_ISVTX =  0001000;  // sticky bit
-    public static final int S_IRUSR =  0000400;
-    public static final int S_IWUSR =  0000200;
-    public static final int S_IXUSR =  0000100;
-    public static final int S_IRGRP =  0000040;
-    public static final int S_IWGRP =  0000020;
-    public static final int S_IXGRP =  0000010;
-    public static final int S_IROTH =  0000004;
-    public static final int S_IWOTH =  0000002;
-    public static final int S_IXOTH =  0000001;
-
-    public static int SFTP_V3 = 3;
-    public static int SFTP_V4 = 4;
-    public static int SFTP_V5 = 5;
-    public static int SFTP_V6 = 6;
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
new file mode 100644
index 0000000..f2f005d
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.subsystem.sftp;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpConstants {
+    public static String SFTP_SUBSYSTEM_NAME = "sftp";
+
+    public static final int SSH_FXP_INIT =             1;
+    public static final int SSH_FXP_VERSION =          2;
+    public static final int SSH_FXP_OPEN =             3;
+    public static final int SSH_FXP_CLOSE =            4;
+    public static final int SSH_FXP_READ =             5;
+    public static final int SSH_FXP_WRITE =            6;
+    public static final int SSH_FXP_LSTAT =            7;
+    public static final int SSH_FXP_FSTAT =            8;
+    public static final int SSH_FXP_SETSTAT =          9;
+    public static final int SSH_FXP_FSETSTAT =        10;
+    public static final int SSH_FXP_OPENDIR =         11;
+    public static final int SSH_FXP_READDIR =         12;
+    public static final int SSH_FXP_REMOVE =          13;
+    public static final int SSH_FXP_MKDIR =           14;
+    public static final int SSH_FXP_RMDIR =           15;
+    public static final int SSH_FXP_REALPATH =        16;
+    public static final int SSH_FXP_STAT =            17;
+    public static final int SSH_FXP_RENAME =          18;
+    public static final int SSH_FXP_READLINK =        19;
+    public static final int SSH_FXP_SYMLINK =         20; // v3 -> v5
+    public static final int SSH_FXP_LINK =            21; // v6
+    public static final int SSH_FXP_BLOCK =           22; // v6
+    public static final int SSH_FXP_UNBLOCK =         23; // v6
+    public static final int SSH_FXP_STATUS =         101;
+    public static final int SSH_FXP_HANDLE =         102;
+    public static final int SSH_FXP_DATA =           103;
+    public static final int SSH_FXP_NAME =           104;
+    public static final int SSH_FXP_ATTRS =          105;
+    public static final int SSH_FXP_EXTENDED =       200;
+    public static final int SSH_FXP_EXTENDED_REPLY = 201;
+
+    public static final int SSH_FX_OK =                           0;
+    public static final int SSH_FX_EOF =                          1;
+    public static final int SSH_FX_NO_SUCH_FILE =                 2;
+    public static final int SSH_FX_PERMISSION_DENIED =            3;
+    public static final int SSH_FX_FAILURE =                      4;
+    public static final int SSH_FX_BAD_MESSAGE =                  5;
+    public static final int SSH_FX_NO_CONNECTION =                6;
+    public static final int SSH_FX_CONNECTION_LOST =              7;
+    public static final int SSH_FX_OP_UNSUPPORTED =               8;
+    public static final int SSH_FX_INVALID_HANDLE =               9;
+    public static final int SSH_FX_NO_SUCH_PATH =                10;
+    public static final int SSH_FX_FILE_ALREADY_EXISTS =         11;
+    public static final int SSH_FX_WRITE_PROTECT =               12;
+    public static final int SSH_FX_NO_MEDIA =                    13;
+    public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM =      14;
+    public static final int SSH_FX_QUOTA_EXCEEDED =              15;
+    public static final int SSH_FX_UNKNOWN_PRINCIPLE =           16;
+    public static final int SSH_FX_LOCK_CONFLICT =               17;
+    public static final int SSH_FX_DIR_NOT_EMPTY =               18;
+    public static final int SSH_FX_NOT_A_DIRECTORY =             19;
+    public static final int SSH_FX_INVALID_FILENAME =            20;
+    public static final int SSH_FX_LINK_LOOP =                   21;
+    public static final int SSH_FX_CANNOT_DELETE =               22;
+    public static final int SSH_FX_INVALID_PARAMETER =           23;
+    public static final int SSH_FX_FILE_IS_A_DIRECTORY =         24;
+    public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT =    25;
+    public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED =     26;
+    public static final int SSH_FX_DELETE_PENDING =              27;
+    public static final int SSH_FX_FILE_CORRUPT =                28;
+    public static final int SSH_FX_OWNER_INVALID =               29;
+    public static final int SSH_FX_GROUP_INVALID =               30;
+    public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31;
+
+    public static final int SSH_FILEXFER_ATTR_SIZE =              0x00000001;
+    public static final int SSH_FILEXFER_ATTR_UIDGID =            0x00000002;
+    public static final int SSH_FILEXFER_ATTR_PERMISSIONS =       0x00000004;
+    public static final int SSH_FILEXFER_ATTR_ACMODTIME =         0x00000008; // v3 naming convention
+    public static final int SSH_FILEXFER_ATTR_ACCESSTIME =        0x00000008; // v4
+    public static final int SSH_FILEXFER_ATTR_CREATETIME =        0x00000010; // v4
+    public static final int SSH_FILEXFER_ATTR_MODIFYTIME =        0x00000020; // v4
+    public static final int SSH_FILEXFER_ATTR_ACL =               0x00000040; // v4
+    public static final int SSH_FILEXFER_ATTR_OWNERGROUP =        0x00000080; // v4
+    public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES =   0x00000100; // v5
+    public static final int SSH_FILEXFER_ATTR_BITS =              0x00000200; // v5
+    public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE =   0x00000400; // v6
+    public static final int SSH_FILEXFER_ATTR_TEXT_HINT =         0x00000800; // v6
+    public static final int SSH_FILEXFER_ATTR_MIME_TYPE =         0x00001000; // v6
+    public static final int SSH_FILEXFER_ATTR_LINK_COUNT =        0x00002000; // v6
+    public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; // v6
+    public static final int SSH_FILEXFER_ATTR_CTIME =             0x00008000; // v6
+    public static final int SSH_FILEXFER_ATTR_EXTENDED =          0x80000000;
+
+    public static final int SSH_FILEXFER_ATTR_ALL =               0x0000FFFF; // All attributes
+
+    public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY =         0x00000001;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM =           0x00000002;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN =           0x00000004;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE =          0x00000010;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED =        0x00000020;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED =       0x00000040;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE =           0x00000080;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY =      0x00000100;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE =        0x00000200;
+    public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC =             0x00000400;
+
+    public static final int SSH_FILEXFER_TYPE_REGULAR =      1;
+    public static final int SSH_FILEXFER_TYPE_DIRECTORY =    2;
+    public static final int SSH_FILEXFER_TYPE_SYMLINK =      3;
+    public static final int SSH_FILEXFER_TYPE_SPECIAL =      4;
+    public static final int SSH_FILEXFER_TYPE_UNKNOWN =      5;
+    public static final int SSH_FILEXFER_TYPE_SOCKET =       6; // v5
+    public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE =  7; // v5
+    public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; // v5
+    public static final int SSH_FILEXFER_TYPE_FIFO         = 9; // v5
+
+    public static final int SSH_FXF_READ =   0x00000001;
+    public static final int SSH_FXF_WRITE =  0x00000002;
+    public static final int SSH_FXF_APPEND = 0x00000004;
+    public static final int SSH_FXF_CREAT =  0x00000008;
+    public static final int SSH_FXF_TRUNC =  0x00000010;
+    public static final int SSH_FXF_EXCL =   0x00000020;
+    public static final int SSH_FXF_TEXT =   0x00000040;
+
+    public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007;
+    public static final int SSH_FXF_CREATE_NEW =         0x00000000;
+    public static final int SSH_FXF_CREATE_TRUNCATE =    0x00000001;
+    public static final int SSH_FXF_OPEN_EXISTING =      0x00000002;
+    public static final int SSH_FXF_OPEN_OR_CREATE =     0x00000003;
+    public static final int SSH_FXF_TRUNCATE_EXISTING =  0x00000004;
+    public static final int SSH_FXF_APPEND_DATA =        0x00000008;
+    public static final int SSH_FXF_APPEND_DATA_ATOMIC = 0x00000010;
+    public static final int SSH_FXF_TEXT_MODE =          0x00000020;
+    public static final int SSH_FXF_READ_LOCK =          0x00000040;
+    public static final int SSH_FXF_WRITE_LOCK =         0x00000080;
+    public static final int SSH_FXF_DELETE_LOCK =        0x00000100;
+
+    public static final int SSH_FXP_RENAME_OVERWRITE = 0x00000001;
+    public static final int SSH_FXP_RENAME_ATOMIC =    0x00000002;
+    public static final int SSH_FXP_RENAME_NATIVE =    0x00000004;
+
+    public static final int SSH_FXP_REALPATH_NO_CHECK    = 0x00000001;
+    public static final int SSH_FXP_REALPATH_STAT_IF     = 0x00000002;
+    public static final int SSH_FXP_REALPATH_STAT_ALWAYS = 0x00000003;
+
+    public static final int SSH_FXF_RENAME_OVERWRITE =  0x00000001;
+    public static final int SSH_FXF_RENAME_ATOMIC =     0x00000002;
+    public static final int SSH_FXF_RENAME_NATIVE =     0x00000004;
+
+    public static final int ACE4_ACCESS_ALLOWED_ACE_TYPE      = 0x00000000;
+    public static final int ACE4_ACCESS_DENIED_ACE_TYPE       = 0x00000001;
+    public static final int ACE4_SYSTEM_AUDIT_ACE_TYPE        = 0x00000002;
+    public static final int ACE4_SYSTEM_ALARM_ACE_TYPE        = 0x00000003;
+
+    public static final int ACE4_FILE_INHERIT_ACE             = 0x00000001;
+    public static final int ACE4_DIRECTORY_INHERIT_ACE        = 0x00000002;
+    public static final int ACE4_NO_PROPAGATE_INHERIT_ACE     = 0x00000004;
+    public static final int ACE4_INHERIT_ONLY_ACE             = 0x00000008;
+    public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG   = 0x00000010;
+    public static final int ACE4_FAILED_ACCESS_ACE_FLAG       = 0x00000020;
+    public static final int ACE4_IDENTIFIER_GROUP             = 0x00000040;
+
+    public static final int ACE4_READ_DATA            = 0x00000001;
+    public static final int ACE4_LIST_DIRECTORY       = 0x00000001;
+    public static final int ACE4_WRITE_DATA           = 0x00000002;
+    public static final int ACE4_ADD_FILE             = 0x00000002;
+    public static final int ACE4_APPEND_DATA          = 0x00000004;
+    public static final int ACE4_ADD_SUBDIRECTORY     = 0x00000004;
+    public static final int ACE4_READ_NAMED_ATTRS     = 0x00000008;
+    public static final int ACE4_WRITE_NAMED_ATTRS    = 0x00000010;
+    public static final int ACE4_EXECUTE              = 0x00000020;
+    public static final int ACE4_DELETE_CHILD         = 0x00000040;
+    public static final int ACE4_READ_ATTRIBUTES      = 0x00000080;
+    public static final int ACE4_WRITE_ATTRIBUTES     = 0x00000100;
+    public static final int ACE4_DELETE               = 0x00010000;
+    public static final int ACE4_READ_ACL             = 0x00020000;
+    public static final int ACE4_WRITE_ACL            = 0x00040000;
+    public static final int ACE4_WRITE_OWNER          = 0x00080000;
+    public static final int ACE4_SYNCHRONIZE          = 0x00100000;
+
+    public static final int S_IFMT =   0170000;  // bitmask for the file type bitfields
+    public static final int S_IFSOCK = 0140000;  // socket
+    public static final int S_IFLNK =  0120000;  // symbolic link
+    public static final int S_IFREG =  0100000;  // regular file
+    public static final int S_IFBLK =  0060000;  // block device
+    public static final int S_IFDIR =  0040000;  // directory
+    public static final int S_IFCHR =  0020000;  // character device
+    public static final int S_IFIFO =  0010000;  // fifo
+    public static final int S_ISUID =  0004000;  // set UID bit
+    public static final int S_ISGID =  0002000;  // set GID bit
+    public static final int S_ISVTX =  0001000;  // sticky bit
+    public static final int S_IRUSR =  0000400;
+    public static final int S_IWUSR =  0000200;
+    public static final int S_IXUSR =  0000100;
+    public static final int S_IRGRP =  0000040;
+    public static final int S_IWGRP =  0000020;
+    public static final int S_IXGRP =  0000010;
+    public static final int S_IROTH =  0000004;
+    public static final int S_IWOTH =  0000002;
+    public static final int S_IXOTH =  0000001;
+
+    public static int SFTP_V3 = 3;
+    public static int SFTP_V4 = 4;
+    public static int SFTP_V5 = 5;
+    public static int SFTP_V6 = 6;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
index 7afc506..74dd77f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -312,4 +312,17 @@ public class IoUtils {
         
         return null;
     }
+
+    /**
+     * @param path The {@link Path} to check
+     * @param options The {@link LinkOption}s to use when checking if path is a directory
+     * @return The same input path if it is a directory
+     * @throws UnsupportedOperationException if input path not a directory
+     */
+    public static Path ensureDirectory(Path path, LinkOption ... options) {
+        if (!Files.isDirectory(path, options)) {
+            throw new UnsupportedOperationException("Not a directory: " + path);
+        }
+        return path;
+    }
 }


[09/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
deleted file mode 100644
index 0d4f826..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_LOCK_CONFLICT;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.AsynchronousCloseException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.SftpException;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-
-public class SftpFileChannel extends FileChannel {
-
-    private final SftpPath p;
-    private final Collection<SftpClient.OpenMode> modes;
-    private final SftpClient sftp;
-    private final SftpClient.CloseableHandle handle;
-    private final Object lock = new Object();
-    private volatile long pos;
-    private volatile Thread blockingThread;
-
-    public SftpFileChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
-        this.p = ValidateUtils.checkNotNull(p, "No target path", GenericUtils.EMPTY_OBJECT_ARRAY);
-        this.modes = ValidateUtils.checkNotNull(modes, "No channel modes specified", GenericUtils.EMPTY_OBJECT_ARRAY);
-        
-        SftpFileSystem  fs=p.getFileSystem();
-        sftp = fs.getClient();
-        handle = sftp.open(p.toString(), modes);
-    }
-
-    @Override
-    public int read(ByteBuffer dst) throws IOException {
-        return (int) doRead(Collections.singletonList(dst), -1);
-    }
-
-    @Override
-    public int read(ByteBuffer dst, long position) throws IOException {
-        if (position < 0) {
-            throw new IllegalArgumentException("read(" + p + ") illegal position to read from: " + position);
-        }
-        return (int) doRead(Collections.singletonList(dst), position);
-    }
-
-    @Override
-    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
-        List<ByteBuffer> buffers = Arrays.asList(dsts).subList(offset, offset + length);
-        return doRead(buffers, -1);
-    }
-
-    public static final Set<SftpClient.OpenMode> READ_MODES=
-            Collections.unmodifiableSet(EnumSet.of(SftpClient.OpenMode.Read));
-
-    protected long doRead(List<ByteBuffer> buffers, long position) throws IOException {
-        ensureOpen(READ_MODES);
-        synchronized (lock) {
-            boolean completed = false;
-            boolean eof = false;
-            long curPos = position >= 0 ? position : pos;
-            try {
-                long totalRead = 0;
-                beginBlocking();
-                loop:
-                for (ByteBuffer buffer : buffers) {
-                    while (buffer.remaining() > 0) {
-                        ByteBuffer wrap = buffer;
-                        if (!buffer.hasArray()) {
-                            wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
-                        }
-                        int read = sftp.read(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), wrap.remaining());
-                        if (read > 0) {
-                            if (wrap == buffer) {
-                                wrap.position(wrap.position() + read);
-                            } else {
-                                buffer.put(wrap.array(), wrap.arrayOffset(), read);
-                            }
-                            curPos += read;
-                            totalRead += read;
-                        } else {
-                            eof = read == -1;
-                            break loop;
-                        }
-                    }
-                }
-                completed = true;
-                return totalRead > 0 ? totalRead : eof ? -1 : 0;
-            } finally {
-                if (position < 0) {
-                    pos = curPos;
-                }
-                endBlocking(completed);
-            }
-        }
-    }
-
-    @Override
-    public int write(ByteBuffer src) throws IOException {
-        return (int) doWrite(Collections.singletonList(src), -1);
-    }
-
-    @Override
-    public int write(ByteBuffer src, long position) throws IOException {
-        if (position < 0) {
-            throw new IllegalArgumentException("write(" + p + ") illegal position to write to: " + position);
-        }
-        return (int) doWrite(Collections.singletonList(src), position);
-    }
-
-    @Override
-    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
-        List<ByteBuffer> buffers = Arrays.asList(srcs).subList(offset, offset + length);
-        return doWrite(buffers, -1);
-    }
-
-    public static final Set<SftpClient.OpenMode> WRITE_MODES=
-            Collections.unmodifiableSet(
-                EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Append, SftpClient.OpenMode.Create, SftpClient.OpenMode.Truncate));
-
-    protected long doWrite(List<ByteBuffer> buffers, long position) throws IOException {
-        ensureOpen(WRITE_MODES);
-        synchronized (lock) {
-            boolean completed = false;
-            long curPos = position >= 0 ? position : pos;
-            try {
-                long totalWritten = 0;
-                beginBlocking();
-                for (ByteBuffer buffer : buffers) {
-                    while (buffer.remaining() > 0) {
-                        ByteBuffer wrap = buffer;
-                        if (!buffer.hasArray()) {
-                            wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
-                            buffer.get(wrap.array(), wrap.arrayOffset(), wrap.remaining());
-                        }
-                        int written = wrap.remaining();
-                        sftp.write(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), written);
-                        if (wrap == buffer) {
-                            wrap.position(wrap.position() + written);
-                        }
-                        curPos += written;
-                        totalWritten += written;
-                    }
-                }
-                completed = true;
-                return totalWritten;
-            } finally {
-                if (position < 0) {
-                    pos = curPos;
-                }
-                endBlocking(completed);
-            }
-        }
-    }
-
-    @Override
-    public long position() throws IOException {
-        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-        return pos;
-    }
-
-    @Override
-    public FileChannel position(long newPosition) throws IOException {
-        if (newPosition < 0) {
-            throw new IllegalArgumentException("position(" + p + ") illegal file channel position: " + newPosition);
-        }
-
-        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-        synchronized (lock) {
-            pos = newPosition;
-            return this;
-        }
-    }
-
-    @Override
-    public long size() throws IOException {
-        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-        return sftp.stat(handle).size;
-    }
-
-    @Override
-    public FileChannel truncate(long size) throws IOException {
-        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-        sftp.setStat(handle, new SftpClient.Attributes().size(size));
-        return this;
-    }
-
-    @Override
-    public void force(boolean metaData) throws IOException {
-        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-    }
-
-    @Override
-    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
-        if ((position < 0) || (count < 0)) {
-            throw new IllegalArgumentException("transferTo(" + p + ") illegal position (" + position + ") or count (" + count + ")");
-        }
-        ensureOpen(READ_MODES);
-        synchronized (lock) {
-            boolean completed = false;
-            boolean eof = false;
-            long curPos = position;
-            try {
-                beginBlocking();
-
-                int bufSize = (int) Math.min(count, 32768);
-                byte[] buffer = new byte[bufSize];
-                long totalRead = 0L;
-                while (totalRead < count) {
-                    int read = sftp.read(handle, curPos, buffer, 0, buffer.length);
-                    if (read > 0) {
-                        ByteBuffer wrap = ByteBuffer.wrap(buffer);
-                        while (wrap.remaining() > 0) {
-                            target.write(wrap);
-                        }
-                        curPos += read;
-                        totalRead += read;
-                    } else {
-                        eof = read == -1;
-                    }
-                }
-                completed = true;
-                return totalRead > 0 ? totalRead : eof ? -1 : 0;
-            } finally {
-                endBlocking(completed);
-            }
-        }
-    }
-
-    @Override
-    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
-        if ((position < 0) || (count < 0)) {
-            throw new IllegalArgumentException("transferFrom(" + p + ") illegal position (" + position + ") or count (" + count + ")");
-        }
-        ensureOpen(WRITE_MODES);
-
-        synchronized(lock) {
-            boolean completed = false;
-            long curPos = position >= 0 ? position : pos;
-            try {
-                long totalRead = 0;
-                beginBlocking();
-
-                byte[] buffer = new byte[32768];
-                while (totalRead < count) {
-                    ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, (int) Math.min(buffer.length, count - totalRead));
-                    int read = src.read(wrap);
-                    if (read > 0) {
-                        sftp.write(handle, curPos, buffer, 0, read);
-                        curPos += read;
-                        totalRead += read;
-                    } else {
-                        break;
-                    }
-                }
-                completed = true;
-                return totalRead;
-            } finally {
-                endBlocking(completed);
-            }
-        }
-    }
-
-    @Override
-    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
-        throw new UnsupportedOperationException("map(" + p + ")[" + mode + "," + position + "," + size + "] N/A");
-    }
-
-    @Override
-    public FileLock lock(long position, long size, boolean shared) throws IOException {
-        return tryLock(position, size, shared);
-    }
-
-    @Override
-    public FileLock tryLock(final long position, final long size, boolean shared) throws IOException {
-        ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-
-        try {
-            sftp.lock(handle, position, size, 0);
-        } catch (SftpException e) {
-            if (e.getStatus() == SSH_FX_LOCK_CONFLICT) {
-                throw new OverlappingFileLockException();
-            }
-            throw e;
-        }
-
-        return new FileLock(this, position, size, shared) {
-            private final AtomicBoolean valid = new AtomicBoolean(true);
-
-            @Override
-            public boolean isValid() {
-                return acquiredBy().isOpen() && valid.get();
-            }
-
-            @SuppressWarnings("synthetic-access")
-            @Override
-            public void release() throws IOException {
-                if (valid.compareAndSet(true, false)) {
-                    sftp.unlock(handle, position, size);
-                }
-            }
-        };
-    }
-
-    @Override
-    protected void implCloseChannel() throws IOException {
-        try {
-            final Thread thread = blockingThread;
-            if (thread != null) {
-                thread.interrupt();
-            }
-        } finally {
-            try {
-                handle.close();
-            } finally {
-                sftp.close();
-            }
-        }
-    }
-
-    private void beginBlocking() {
-        begin();
-        blockingThread = Thread.currentThread();
-    }
-
-    private void endBlocking(boolean completed) throws AsynchronousCloseException {
-        blockingThread = null;
-        end(completed);
-    }
-
-    /**
-     * Checks that the channel is open and that its current mode contains
-     * at least one of the required ones
-     * @param reqModes The required modes - ignored if {@code null}/empty
-     * @throws IOException If channel not open or the required modes are not
-     * satisfied
-     */
-    private void ensureOpen(Collection<SftpClient.OpenMode> reqModes) throws IOException {
-        if (!isOpen()) {
-            throw new ClosedChannelException();
-        }
-        
-        if (GenericUtils.size(reqModes) > 0) {
-            for (SftpClient.OpenMode m : reqModes) {
-                if (this.modes.contains(m)) {
-                    return;
-                }
-            }
-            
-            throw new IOException("ensureOpen(" + p + ") current channel modes (" + this.modes + ") do contain any of the required: " + reqModes);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return Objects.toString(p);
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
deleted file mode 100644
index 026d13c..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.file.util.BaseFileSystem;
-import org.apache.sshd.common.file.util.ImmutableList;
-
-public class SftpFileSystem extends BaseFileSystem<SftpPath> {
-
-    private final ClientSession session;
-    private final Queue<SftpClient> pool;
-    private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
-    private SftpPath defaultDir;
-    private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
-    private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
-
-    public SftpFileSystem(SftpFileSystemProvider provider, ClientSession session) throws IOException {
-        super(provider);
-        this.session = session;
-        this.pool = new LinkedBlockingQueue<>(8);
-        try (SftpClient client = getClient()) {
-            defaultDir = getPath(client.canonicalPath("."));
-        }
-    }
-
-    public int getReadBufferSize() {
-        return readBufferSize;
-    }
-
-    public void setReadBufferSize(int size) {
-        if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
-            throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
-        }
-
-        readBufferSize = size;
-    }
-
-    public int getWriteBufferSize() {
-        return writeBufferSize;
-    }
-
-    public void setWriteBufferSize(int size) {
-        if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
-            throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
-        }
-
-        writeBufferSize = size;
-    }
-
-    @Override
-    protected SftpPath create(String root, ImmutableList<String> names) {
-        return new SftpPath(this, root, names);
-    }
-
-    public ClientSession getSession() {
-        return session;
-    }
-
-    @SuppressWarnings("synthetic-access")
-    public SftpClient getClient() throws IOException {
-        Wrapper wrapper = wrappers.get();
-        if (wrapper == null) {
-            while (wrapper == null) {
-                SftpClient client = pool.poll();
-                if (client == null) {
-                    client = session.createSftpClient();
-                }
-                if (!client.isClosing()) {
-                    wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
-                }
-            }
-            wrappers.set(wrapper);
-        } else {
-            wrapper.increment();
-        }
-        return wrapper;
-    }
-
-    @Override
-    public void close() throws IOException {
-        if (isOpen()) {
-            session.close(true);
-        }
-    }
-
-    @Override
-    public boolean isOpen() {
-        return !session.isClosing();
-    }
-
-    @Override
-    public Set<String> supportedFileAttributeViews() {
-        Set<String> set = new HashSet<>();
-        set.addAll(Arrays.asList("basic", "posix", "owner"));
-        return Collections.unmodifiableSet(set);
-    }
-
-    @Override
-    public UserPrincipalLookupService getUserPrincipalLookupService() {
-        return new DefaultUserPrincipalLookupService();
-    }
-
-    @Override
-    public SftpPath getDefaultDir() {
-        return defaultDir;
-    }
-
-    private class Wrapper extends AbstractSftpClient {
-
-        private final SftpClient delegate;
-        private final AtomicInteger count = new AtomicInteger(1);
-        private final int readSize, writeSize;
-
-        private Wrapper(SftpClient delegate, int readSize, int writeSize) {
-            this.delegate = delegate;
-            this.readSize = readSize;
-            this.writeSize = writeSize;
-        }
-
-        @Override
-        public int getVersion() {
-            return delegate.getVersion();
-        }
-
-        @Override
-        public boolean isClosing() {
-            return false;
-        }
-
-        @SuppressWarnings("synthetic-access")
-        @Override
-        public void close() throws IOException {
-            if (count.decrementAndGet() == 0) {
-                if (!pool.offer(delegate)) {
-                    delegate.close();
-                }
-                wrappers.set(null);
-            }
-        }
-
-        public void increment() {
-            count.incrementAndGet();
-        }
-
-        @Override
-        public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
-            return delegate.open(path, options);
-        }
-
-        @Override
-        public void close(Handle handle) throws IOException {
-            delegate.close(handle);
-        }
-
-        @Override
-        public void remove(String path) throws IOException {
-            delegate.remove(path);
-        }
-
-        @Override
-        public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
-            delegate.rename(oldPath, newPath, options);
-        }
-
-        @Override
-        public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
-            return delegate.read(handle, fileOffset, dst, dstoff, len);
-        }
-
-        @Override
-        public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
-            delegate.write(handle, fileOffset, src, srcoff, len);
-        }
-
-        @Override
-        public void mkdir(String path) throws IOException {
-            delegate.mkdir(path);
-        }
-
-        @Override
-        public void rmdir(String path) throws IOException {
-            delegate.rmdir(path);
-        }
-
-        @Override
-        public CloseableHandle openDir(String path) throws IOException {
-            return delegate.openDir(path);
-        }
-
-        @Override
-        public DirEntry[] readDir(Handle handle) throws IOException {
-            return delegate.readDir(handle);
-        }
-
-        @Override
-        public String canonicalPath(String canonical) throws IOException {
-            return delegate.canonicalPath(canonical);
-        }
-
-        @Override
-        public Attributes stat(String path) throws IOException {
-            return delegate.stat(path);
-        }
-
-        @Override
-        public Attributes lstat(String path) throws IOException {
-            return delegate.lstat(path);
-        }
-
-        @Override
-        public Attributes stat(Handle handle) throws IOException {
-            return delegate.stat(handle);
-        }
-
-        @Override
-        public void setStat(String path, Attributes attributes) throws IOException {
-            delegate.setStat(path, attributes);
-        }
-
-        @Override
-        public void setStat(Handle handle, Attributes attributes) throws IOException {
-            delegate.setStat(handle, attributes);
-        }
-
-        @Override
-        public String readLink(String path) throws IOException {
-            return delegate.readLink(path);
-        }
-
-        @Override
-        public void symLink(String linkPath, String targetPath) throws IOException {
-            delegate.symLink(linkPath, targetPath);
-        }
-
-        @Override
-        public Iterable<DirEntry> readDir(String path) throws IOException {
-            return delegate.readDir(path);
-        }
-
-        @Override
-        public InputStream read(String path) throws IOException {
-            return read(path, readSize);
-        }
-
-        @Override
-        public InputStream read(String path, OpenMode... mode) throws IOException {
-            return read(path, readSize, mode);
-        }
-
-        @Override
-        public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
-            return read(path, readSize, mode);
-        }
-
-        @Override
-        public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
-            return delegate.read(path, bufferSize, mode);
-        }
-
-        @Override
-        public OutputStream write(String path) throws IOException {
-            return write(path, writeSize);
-        }
-
-        @Override
-        public OutputStream write(String path, OpenMode... mode) throws IOException {
-            return write(path, writeSize, mode);
-        }
-
-        @Override
-        public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
-            return write(path, writeSize, mode);
-        }
-
-        @Override
-        public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
-            return delegate.write(path, bufferSize, mode);
-        }
-
-        @Override
-        public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
-            delegate.link(linkPath, targetPath, symbolic);
-        }
-
-        @Override
-        public void lock(Handle handle, long offset, long length, int mask) throws IOException {
-            delegate.lock(handle, offset, length, mask);
-        }
-
-        @Override
-        public void unlock(Handle handle, long offset, long length) throws IOException {
-            delegate.unlock(handle, offset, length);
-        }
-    }
-
-    protected static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
-
-        @Override
-        public UserPrincipal lookupPrincipalByName(String name) throws IOException {
-            return new DefaultUserPrincipal(name);
-        }
-
-        @Override
-        public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
-            return new DefaultGroupPrincipal(group);
-        }
-    }
-
-    protected static class DefaultUserPrincipal implements UserPrincipal {
-
-        private final String name;
-
-        public DefaultUserPrincipal(String name) {
-            if (name == null) {
-                throw new IllegalArgumentException("name is null");
-            }
-            this.name = name;
-        }
-
-        @Override
-        public String getName() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            DefaultUserPrincipal that = (DefaultUserPrincipal) o;
-            return name.equals(that.name);
-        }
-
-        @Override
-        public int hashCode() {
-            return name.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return name;
-        }
-    }
-
-    protected static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
-
-        public DefaultGroupPrincipal(String name) {
-            super(name);
-        }
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
deleted file mode 100644
index 2ac72b1..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
+++ /dev/null
@@ -1,892 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V3;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRGRP;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IROTH;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWGRP;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWOTH;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IXGRP;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IXOTH;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IXUSR;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.channels.FileChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.FileSystemException;
-import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.ProviderMismatchException;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.ClientBuilder;
-import org.apache.sshd.client.SftpException;
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.sftp.SftpClient.Attributes;
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.config.SshConfigFileReader;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class SftpFileSystemProvider extends FileSystemProvider {
-    public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
-        public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
-    public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
-        public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
-    public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
-        public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
-
-    private final SshClient client;
-    private final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
-    protected final Logger log;
-
-    public SftpFileSystemProvider() {
-        this(null);
-    }
-
-    public SftpFileSystemProvider(SshClient client) {
-        this.log = LoggerFactory.getLogger(getClass());
-        if (client == null) {
-            // TODO: make this configurable using system properties
-            client = ClientBuilder.builder().build();
-        }
-        this.client = client;
-        this.client.start();
-    }
-
-    @Override
-    public String getScheme() {
-        return SftpConstants.SFTP_SUBSYSTEM_NAME;
-    }
-
-    @Override
-    public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-        synchronized (fileSystems) {
-            String authority = uri.getAuthority();
-            SftpFileSystem fileSystem = fileSystems.get(authority);
-            if (fileSystem != null) {
-                throw new FileSystemAlreadyExistsException(authority);
-            }
-            String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
-            String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
-            String[] ui = GenericUtils.split(userInfo, ':');
-            int port = uri.getPort();
-            if (port <= 0) {
-                port = SshConfigFileReader.DEFAULT_PORT;
-            }
-
-            ClientSession session=null;
-            try {
-                session = client.connect(ui[0], host, port)
-                                .verify(FactoryManagerUtils.getLongProperty(env, CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME))
-                                .getSession()
-                                ;
-                session.addPasswordIdentity(ui[1]);
-                session.auth().verify();
-                fileSystem = new SftpFileSystem(this, session);
-                fileSystem.setReadBufferSize(FactoryManagerUtils.getIntProperty(env, READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
-                fileSystem.setWriteBufferSize(FactoryManagerUtils.getIntProperty(env, WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
-                fileSystems.put(authority, fileSystem);
-                return fileSystem;
-            } catch(Exception e) {
-                if (session != null) {
-                    try {
-                        session.close();
-                    } catch(IOException t) {
-                        if (log.isDebugEnabled()) {
-                            log.debug("Failed (" + t.getClass().getSimpleName() + ")"
-                                    + " to close session for new file system on " + host + ":" + port
-                                    + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
-                                    + ": " + t.getMessage());
-                        }
-                    }
-                }
-                
-                if (e instanceof IOException) {
-                    throw (IOException) e;
-                } else if (e instanceof RuntimeException) {
-                    throw (RuntimeException) e;
-                } else {
-                    throw new IOException(e);
-                }
-            }
-        }
-    }
-
-    @Override
-    public FileSystem getFileSystem(URI uri) {
-        synchronized (fileSystems) {
-            String authority = uri.getAuthority();
-            SftpFileSystem fileSystem = fileSystems.get(authority);
-            if (fileSystem == null) {
-                throw new FileSystemNotFoundException(authority);
-            }
-            return fileSystem;
-        }
-    }
-
-    @Override
-    public Path getPath(URI uri) {
-        FileSystem fs = getFileSystem(uri);
-        return fs.getPath(uri.getPath());
-    }
-
-    @Override
-    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
-        return newFileChannel(path, options, attrs);
-    }
-
-    @Override
-    public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
-        Collection<SftpClient.OpenMode> modes = EnumSet.noneOf(SftpClient.OpenMode.class);
-        for (OpenOption option : options) {
-            if (option == StandardOpenOption.READ) {
-                modes.add(SftpClient.OpenMode.Read);
-            } else if (option == StandardOpenOption.APPEND) {
-                modes.add(SftpClient.OpenMode.Append);
-            } else if (option == StandardOpenOption.CREATE) {
-                modes.add(SftpClient.OpenMode.Create);
-            } else if (option == StandardOpenOption.TRUNCATE_EXISTING) {
-                modes.add(SftpClient.OpenMode.Truncate);
-            } else if (option == StandardOpenOption.WRITE) {
-                modes.add(SftpClient.OpenMode.Write);
-            } else if (option == StandardOpenOption.CREATE_NEW) {
-                modes.add(SftpClient.OpenMode.Create);
-                modes.add(SftpClient.OpenMode.Exclusive);
-            } else if (option == StandardOpenOption.SPARSE) {
-                /*
-                 * As per the Javadoc:
-                 * 
-                 *      The option is ignored when the file system does not
-                 *  support the creation of sparse files
-                 */
-                continue;
-            } else {
-                throw new IllegalArgumentException("newFileChannel(" + path + ") unsupported open option: " + option);
-            }
-        }
-        if (modes.isEmpty()) {
-            modes.add(SftpClient.OpenMode.Read);
-            modes.add(SftpClient.OpenMode.Write);
-        }
-        // TODO: attrs
-        return new SftpFileChannel(toSftpPath(path), modes);
-    }
-
-    @Override
-    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
-        final SftpPath p = toSftpPath(dir);
-        return new DirectoryStream<Path>() {
-            private final SftpFileSystem fs = p.getFileSystem();
-            private final SftpClient sftp = fs.getClient();
-            private final Iterable<SftpClient.DirEntry> iter = sftp.readDir(p.toString());
-
-            @Override
-            public Iterator<Path> iterator() {
-                return new Iterator<Path>() {
-                    @SuppressWarnings("synthetic-access")
-                    private final Iterator<SftpClient.DirEntry> it = iter.iterator();
-
-                    @Override
-                    public boolean hasNext() {
-                        return it.hasNext();
-                    }
-
-                    @Override
-                    public Path next() {
-                        SftpClient.DirEntry entry = it.next();
-                        return p.resolve(entry.filename);
-                    }
-
-                    @Override
-                    public void remove() {
-                        throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
-                    }
-                };
-            }
-
-            @Override
-            public void close() throws IOException {
-                sftp.close();
-            }
-        };
-    }
-
-    @Override
-    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
-        SftpPath p = toSftpPath(dir);
-        SftpFileSystem fs = p.getFileSystem();
-        try (SftpClient sftp = fs.getClient()) {
-            try {
-                sftp.mkdir(dir.toString());
-            } catch (SftpException e) {
-                int sftpStatus=e.getStatus();
-                if ((sftp.getVersion() == SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
-                    try {
-                        Attributes attributes = sftp.stat(dir.toString());
-                        if (attributes != null) {
-                            throw new FileAlreadyExistsException(p.toString());
-                        }
-                    } catch (SshException e2) {
-                        e.addSuppressed(e2);
-                    }
-                }
-                if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
-                    throw new FileAlreadyExistsException(p.toString());
-                }
-                throw e;
-            }
-            for (FileAttribute<?> attr : attrs) {
-                setAttribute(p, attr.name(), attr.value());
-            }
-        }
-    }
-
-    @Override
-    public void delete(Path path) throws IOException {
-        SftpPath p = toSftpPath(path);
-        checkAccess(p, AccessMode.WRITE);
-        
-        SftpFileSystem fs = p.getFileSystem();
-        try (SftpClient sftp = fs.getClient()) {
-            BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
-            if (attributes.isDirectory()) {
-                sftp.rmdir(path.toString());
-            } else {
-                sftp.remove(path.toString());
-            }
-        }
-    }
-
-    @Override
-    public void copy(Path source, Path target, CopyOption... options) throws IOException {
-        SftpPath src = toSftpPath(source);
-        SftpPath dst = toSftpPath(target);
-        if (src.getFileSystem() != dst.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
-        }
-        checkAccess(src);
-
-        boolean replaceExisting = false;
-        boolean copyAttributes = false;
-        boolean noFollowLinks = false;
-        for (CopyOption opt : options) {
-            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
-            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
-            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
-        }
-        LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
-
-        // attributes of source file
-        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
-        if (attrs.isSymbolicLink())
-            throw new IOException("Copying of symbolic links not supported");
-
-        // delete target if it exists and REPLACE_EXISTING is specified
-        Boolean status=IoUtils.checkFileExists(target, linkOptions);
-        if (status == null) {
-            throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
-        }
-
-        if (replaceExisting) {
-            deleteIfExists(target);
-        } else {
-            if (status.booleanValue()) {
-                throw new FileAlreadyExistsException(target.toString());
-            }
-        }
-
-        // create directory or copy file
-        if (attrs.isDirectory()) {
-            createDirectory(target);
-        } else {
-            try (InputStream in = newInputStream(source);
-                 OutputStream os = newOutputStream(target)) {
-                IoUtils.copy(in, os);
-            }
-        }
-
-        // copy basic attributes to target
-        if (copyAttributes) {
-            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
-            try {
-                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
-            } catch (Throwable x) {
-                // rollback
-                try {
-                    delete(target);
-                } catch (Throwable suppressed) {
-                    x.addSuppressed(suppressed);
-                }
-                throw x;
-            }
-        }
-    }
-
-    @Override
-    public void move(Path source, Path target, CopyOption... options) throws IOException {
-        SftpPath src = toSftpPath(source);
-        SftpFileSystem fsSrc = src.getFileSystem(); 
-        SftpPath dst = toSftpPath(target);
-        
-        if (src.getFileSystem() != dst.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
-        }
-        checkAccess(src);
-
-        boolean replaceExisting = false;
-        boolean copyAttributes = false;
-        boolean noFollowLinks = false;
-        for (CopyOption opt : options) {
-            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
-            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
-            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
-        }
-        LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
-
-        // attributes of source file
-        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
-        if (attrs.isSymbolicLink()) {
-            throw new IOException("Copying of symbolic links not supported");
-        }
-
-        // delete target if it exists and REPLACE_EXISTING is specified
-        Boolean status=IoUtils.checkFileExists(target, linkOptions);
-        if (status == null) {
-            throw new AccessDeniedException("Existence cannot be determined for move target " + target);
-        }
-
-        if (replaceExisting) {
-            deleteIfExists(target);
-        } else if (status.booleanValue()) {
-            throw new FileAlreadyExistsException(target.toString());
-        }
-
-        try (SftpClient sftp = fsSrc.getClient()) {
-            sftp.rename(src.toString(), dst.toString());
-        }
-
-        // copy basic attributes to target
-        if (copyAttributes) {
-            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
-            try {
-                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
-            } catch (Throwable x) {
-                // rollback
-                try {
-                    delete(target);
-                } catch (Throwable suppressed) {
-                    x.addSuppressed(suppressed);
-                }
-                throw x;
-            }
-        }
-    }
-
-    @Override
-    public boolean isSameFile(Path path1, Path path2) throws IOException {
-        SftpPath p1 = toSftpPath(path1);
-        SftpPath p2 = toSftpPath(path2);
-        if (p1.getFileSystem() != p2.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
-        }
-        checkAccess(p1);
-        checkAccess(p2);
-        return p1.equals(p2);
-    }
-
-    @Override
-    public boolean isHidden(Path path) throws IOException {
-        return false;
-    }
-
-    @Override
-    public FileStore getFileStore(Path path) throws IOException {
-        throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") N/A");
-    }
-
-    @Override
-    public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
-        SftpPath l = toSftpPath(link);
-        SftpFileSystem fsLink = l.getFileSystem();
-        SftpPath t = toSftpPath(target);
-        if (fsLink != t.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
-        }
-        try (SftpClient client = fsLink.getClient()) {
-            client.symLink(l.toString(), t.toString());
-        }
-    }
-
-    @Override
-    public Path readSymbolicLink(Path link) throws IOException {
-        SftpPath l = toSftpPath(link);
-        SftpFileSystem fsLink = l.getFileSystem();
-        try (SftpClient client = fsLink.getClient()) {
-            return fsLink.getPath(client.readLink(l.toString()));
-        }
-    }
-
-    @Override
-    public void checkAccess(Path path, AccessMode... modes) throws IOException {
-        SftpPath p = toSftpPath(path);
-        boolean w = false;
-        boolean x = false;
-        if (GenericUtils.length(modes) > 0) {
-            for (AccessMode mode : modes) {
-                switch (mode) {
-                    case READ:
-                        break;
-                    case WRITE:
-                        w = true;
-                        break;
-                    case EXECUTE:
-                        x = true;
-                        break;
-                    default:
-                        throw new UnsupportedOperationException("Unsupported mode: " + mode);
-                }
-            }
-        }
-
-        BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
-        if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
-            throw new NoSuchFileException(path.toString());
-        }
-        
-        SftpFileSystem fs = p.getFileSystem();
-        if (x || (w && fs.isReadOnly())) {
-            throw new AccessDeniedException(path.toString());
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public <V extends FileAttributeView> V getFileAttributeView(final Path path, Class<V> type, final LinkOption... options) {
-        if (type.isAssignableFrom(PosixFileAttributeView.class)) {
-            return (V) new PosixFileAttributeView() {
-                @Override
-                public String name() {
-                    return "view";
-                }
-
-                @SuppressWarnings("synthetic-access")
-                @Override
-                public PosixFileAttributes readAttributes() throws IOException {
-                    SftpPath p = toSftpPath(path);
-                    SftpFileSystem fs = p.getFileSystem();
-                    final SftpClient.Attributes attributes;
-                    try (SftpClient client =fs.getClient()) {
-                        try {
-                            if (followLinks(options)) {
-                                attributes = client.stat(p.toString());
-                            } else {
-                                attributes = client.lstat(p.toString());
-                            }
-                        } catch (SftpException e) {
-                            if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
-                                throw new NoSuchFileException(p.toString());
-                            }
-                            throw e;
-                        }
-                    }
-                    return new PosixFileAttributes() {
-                        @Override
-                        public UserPrincipal owner() {
-                            return attributes.owner != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.owner) : null;
-                        }
-
-                        @Override
-                        public GroupPrincipal group() {
-                            return attributes.group != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.group) : null;
-                        }
-
-                        @Override
-                        public Set<PosixFilePermission> permissions() {
-                            return permissionsToAttributes(attributes.perms);
-                        }
-
-                        @Override
-                        public FileTime lastModifiedTime() {
-                            return FileTime.from(attributes.mtime, TimeUnit.SECONDS);
-                        }
-
-                        @Override
-                        public FileTime lastAccessTime() {
-                            return FileTime.from(attributes.atime, TimeUnit.SECONDS);
-                        }
-
-                        @Override
-                        public FileTime creationTime() {
-                            return FileTime.from(attributes.ctime, TimeUnit.SECONDS);
-                        }
-
-                        @Override
-                        public boolean isRegularFile() {
-                            return attributes.isRegularFile();
-                        }
-
-                        @Override
-                        public boolean isDirectory() {
-                            return attributes.isDirectory();
-                        }
-
-                        @Override
-                        public boolean isSymbolicLink() {
-                            return attributes.isSymbolicLink();
-                        }
-
-                        @Override
-                        public boolean isOther() {
-                            return attributes.isOther();
-                        }
-
-                        @Override
-                        public long size() {
-                            return attributes.size;
-                        }
-
-                        @Override
-                        public Object fileKey() {
-                            // TODO
-                            return null;
-                        }
-                    };
-                }
-
-                @Override
-                public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
-                    if (lastModifiedTime != null) {
-                        setAttribute(path, "lastModifiedTime", lastModifiedTime, options);
-                    }
-                    if (lastAccessTime != null) {
-                        setAttribute(path, "lastAccessTime", lastAccessTime, options);
-                    }
-                    if (createTime != null) {
-                        setAttribute(path, "createTime", createTime, options);
-                    }
-                }
-
-                @Override
-                public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
-                    setAttribute(path, "permissions", perms, options);
-                }
-
-                @Override
-                public void setGroup(GroupPrincipal group) throws IOException {
-                    setAttribute(path, "group", group, options);
-                }
-
-                @Override
-                public UserPrincipal getOwner() throws IOException {
-                    return readAttributes().owner();
-                }
-
-                @Override
-                public void setOwner(UserPrincipal owner) throws IOException {
-                    setAttribute(path, "owner", owner, options);
-                }
-            };
-        } else {
-            throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
-        }
-    }
-
-    @Override
-    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
-        if (type.isAssignableFrom(PosixFileAttributes.class)) {
-            return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
-        }
-
-        throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
-    }
-
-    @Override
-    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
-        String view;
-        String attrs;
-        int i = attributes.indexOf(':');
-        if (i == -1) {
-            view = "basic";
-            attrs = attributes;
-        } else {
-            view = attributes.substring(0, i++);
-            attrs = attributes.substring(i);
-        }
-        SftpPath p = toSftpPath(path);
-        SftpFileSystem fs = p.getFileSystem();
-        Collection<String> views = fs.supportedFileAttributeViews();
-        if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
-            throw new UnsupportedOperationException("readAttributes(" + path + ")[" + attributes + "] view " + view + " not supported: " + views);
-        }
-
-        PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
-        if ("*".equals(attrs)) {
-            attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
-        }
-        Map<String, Object> map = new HashMap<>();
-        for (String attr : attrs.split(",")) {
-            switch (attr) {
-                case "lastModifiedTime":
-                    map.put(attr, v.lastModifiedTime());
-                    break;
-                case "lastAccessTime":
-                    map.put(attr, v.lastAccessTime());
-                    break;
-                case "creationTime":
-                    map.put(attr, v.creationTime());
-                    break;
-                case "size":
-                    map.put(attr, Long.valueOf(v.size()));
-                    break;
-                case "isRegularFile":
-                    map.put(attr, Boolean.valueOf(v.isRegularFile()));
-                    break;
-                case "isDirectory":
-                    map.put(attr, Boolean.valueOf(v.isDirectory()));
-                    break;
-                case "isSymbolicLink":
-                    map.put(attr, Boolean.valueOf(v.isSymbolicLink()));
-                    break;
-                case "isOther":
-                    map.put(attr, Boolean.valueOf(v.isOther()));
-                    break;
-                case "fileKey":
-                    map.put(attr, v.fileKey());
-                    break;
-                case "owner":
-                    map.put(attr, v.owner());
-                    break;
-                case "permissions":
-                    map.put(attr, v.permissions());
-                    break;
-                case "group":
-                    map.put(attr, v.group());
-                    break;
-                default:
-                    if (log.isTraceEnabled()) {
-                        log.trace("readAttributes({})[{}] ignored {}={}", path, attributes, attr, v);
-                    }
-            }
-        }
-        return map;
-    }
-
-    @Override
-    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
-        String view;
-        String attr;
-        int i = attribute.indexOf(':');
-        if (i == -1) {
-            view = "basic";
-            attr = attribute;
-        } else {
-            view = attribute.substring(0, i++);
-            attr = attribute.substring(i);
-        }
-        SftpPath p = toSftpPath(path);
-        SftpFileSystem fs = p.getFileSystem();
-        Collection<String> views = fs.supportedFileAttributeViews();
-        if (GenericUtils.isEmpty(views) || (!view.contains(view))) {
-            throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "] view " + view + " not supported: " + views);
-        }
-
-        SftpClient.Attributes attributes = new SftpClient.Attributes();
-        switch (attr) {
-            case "lastModifiedTime":
-                attributes.mtime((int) ((FileTime) value).to(TimeUnit.SECONDS));
-                break;
-            case "lastAccessTime":
-                attributes.atime((int) ((FileTime) value).to(TimeUnit.SECONDS));
-                break;
-            case "creationTime":
-                attributes.ctime((int) ((FileTime) value).to(TimeUnit.SECONDS));
-                break;
-            case "size":
-                attributes.size(((Number) value).longValue());
-                break;
-            case "permissions": {
-                @SuppressWarnings("unchecked")
-                Set<PosixFilePermission>    attrSet = (Set<PosixFilePermission>) value;
-                attributes.perms(attributesToPermissions(path, attrSet));
-                }
-                break;
-            case "owner":
-                attributes.owner(((UserPrincipal) value).getName());
-                break;
-            case "group":
-                attributes.group(((GroupPrincipal) value).getName());
-                break;
-            case "isRegularFile":
-            case "isDirectory":
-            case "isSymbolicLink":
-            case "isOther":
-            case "fileKey":
-                throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "]"
-                                                       + " unknown view=" + view + " attribute: " + attr);
-            default:
-                if (log.isTraceEnabled()) {
-                    log.trace("setAttribute({})[{}] ignore {}={}", path, attribute, attr, value);
-                }
-        }
-
-        try (SftpClient client = fs.getClient()) {
-            client.setStat(p.toString(), attributes);
-        }
-    }
-
-    private SftpPath toSftpPath(Path path) {
-        ValidateUtils.checkNotNull(path, "No path provided", GenericUtils.EMPTY_OBJECT_ARRAY);
-        if (!(path instanceof SftpPath)) {
-            throw new ProviderMismatchException("Path is not SFTP: " + path);
-        }
-        return (SftpPath) path;
-    }
-
-    static boolean followLinks(LinkOption... paramVarArgs) {
-        boolean bool = true;
-        for (LinkOption localLinkOption : paramVarArgs) {
-            if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
-                bool = false;
-            }
-        }
-        return bool;
-    }
-
-    private Set<PosixFilePermission> permissionsToAttributes(int perms) {
-        Set<PosixFilePermission> p = new HashSet<>();
-        if ((perms & S_IRUSR) != 0) {
-            p.add(PosixFilePermission.OWNER_READ);
-        }
-        if ((perms & S_IWUSR) != 0) {
-            p.add(PosixFilePermission.OWNER_WRITE);
-        }
-        if ((perms & S_IXUSR) != 0) {
-            p.add(PosixFilePermission.OWNER_EXECUTE);
-        }
-        if ((perms & S_IRGRP) != 0) {
-            p.add(PosixFilePermission.GROUP_READ);
-        }
-        if ((perms & S_IWGRP) != 0) {
-            p.add(PosixFilePermission.GROUP_WRITE);
-        }
-        if ((perms & S_IXGRP) != 0) {
-            p.add(PosixFilePermission.GROUP_EXECUTE);
-        }
-        if ((perms & S_IROTH) != 0) {
-            p.add(PosixFilePermission.OTHERS_READ);
-        }
-        if ((perms & S_IWOTH) != 0) {
-            p.add(PosixFilePermission.OTHERS_WRITE);
-        }
-        if ((perms & S_IXOTH) != 0) {
-            p.add(PosixFilePermission.OTHERS_EXECUTE);
-        }
-        return p;
-    }
-
-    protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
-        if (GenericUtils.isEmpty(perms)) {
-            return 0;
-        }
-
-        int pf = 0;
-        for (PosixFilePermission p : perms) {
-            switch (p) {
-                case OWNER_READ:
-                    pf |= S_IRUSR;
-                    break;
-                case OWNER_WRITE:
-                    pf |= S_IWUSR;
-                    break;
-                case OWNER_EXECUTE:
-                    pf |= S_IXUSR;
-                    break;
-                case GROUP_READ:
-                    pf |= S_IRGRP;
-                    break;
-                case GROUP_WRITE:
-                    pf |= S_IWGRP;
-                    break;
-                case GROUP_EXECUTE:
-                    pf |= S_IXGRP;
-                    break;
-                case OTHERS_READ:
-                    pf |= S_IROTH;
-                    break;
-                case OTHERS_WRITE:
-                    pf |= S_IWOTH;
-                    break;
-                case OTHERS_EXECUTE:
-                    pf |= S_IXOTH;
-                    break;
-                default:
-                    if (log.isTraceEnabled()) {
-                        log.trace("attributesToPermissions(" + path + ") ignored " + p);
-                    }
-            }
-        }
-
-        return pf;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
deleted file mode 100644
index d4e0b0b..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.LinkOption;
-import java.nio.file.spi.FileSystemProvider;
-
-import org.apache.sshd.common.file.util.BasePath;
-import org.apache.sshd.common.file.util.ImmutableList;
-
-public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
-    public SftpPath(SftpFileSystem fileSystem, String root, ImmutableList<String> names) {
-        super(fileSystem, root, names);
-    }
-
-    @Override
-    public SftpPath toRealPath(LinkOption... options) throws IOException {
-//        try (SftpClient client = fileSystem.getClient()) {
-//            client.realP
-//        }
-        // TODO: handle links
-        SftpPath absolute = toAbsolutePath();
-        FileSystem fs = getFileSystem();
-        FileSystemProvider provider = fs.provider();
-        provider.checkAccess(absolute);
-        return absolute;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
new file mode 100644
index 0000000..7f86bd7
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.subsystem;
+
+import java.nio.channels.Channel;
+
+import org.apache.sshd.common.NamedResource;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SubsystemClient extends NamedResource, Channel {
+    // marker interface for subsystems
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
new file mode 100644
index 0000000..fc7a02b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient {
+    protected AbstractSftpClient() {
+        super();
+    }
+    
+    @Override
+    public String getName() {
+        return SftpConstants.SFTP_SUBSYSTEM_NAME;
+    }
+
+    @Override
+    public CloseableHandle open(String path) throws IOException {
+        return open(path, Collections.<OpenMode>emptySet());
+    }
+    
+    @Override
+    public CloseableHandle open(String path, OpenMode ... options) throws IOException {
+        return open(path, GenericUtils.of(options));
+    }
+
+    @Override
+    public void rename(String oldPath, String newPath) throws IOException {
+        rename(oldPath, newPath, Collections.<CopyMode>emptySet());
+    }
+    
+    @Override
+    public void rename(String oldPath, String newPath, CopyMode ... options) throws IOException {
+        rename(oldPath, newPath, GenericUtils.of(options));
+    }
+
+    @Override
+    public InputStream read(String path) throws IOException {
+        return read(path, DEFAULT_READ_BUFFER_SIZE);
+    }
+
+    @Override
+    public InputStream read(String path, int bufferSize) throws IOException {
+        return read(path, bufferSize, EnumSet.of(OpenMode.Read));
+    }
+
+    @Override
+    public InputStream read(String path, OpenMode ... mode) throws IOException {
+        return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
+    }
+
+    @Override
+    public InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException {
+        return read(path, bufferSize, GenericUtils.of(mode));
+    }
+
+    @Override
+    public InputStream read(String path, Collection<OpenMode>  mode) throws IOException {
+        return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
+    }
+
+    @Override
+    public int read(Handle handle, long fileOffset, byte[] dst) throws IOException {
+        return read(handle, fileOffset, dst, 0, dst.length);
+    }
+
+    @Override
+    public OutputStream write(String path) throws IOException {
+        return write(path, DEFAULT_WRITE_BUFFER_SIZE);
+    }
+
+    @Override
+    public OutputStream write(String path, int bufferSize) throws IOException {
+        return write(path, bufferSize, EnumSet.of(OpenMode.Write, OpenMode.Create, OpenMode.Truncate));
+    }
+
+    @Override
+    public OutputStream write(String path, OpenMode ... mode) throws IOException {
+        return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
+    }
+
+    @Override
+    public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
+        return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
+    }
+
+    @Override
+    public OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException {
+        return write(path, bufferSize, GenericUtils.of(mode));
+    }
+
+    @Override
+    public void write(Handle handle, long fileOffset, byte[] src) throws IOException {
+        write(handle, fileOffset, src, 0, src.length);
+    }
+
+    @Override
+    public void symLink(String linkPath, String targetPath) throws IOException {
+        link(linkPath, targetPath, true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
new file mode 100644
index 0000000..868ff10
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultCloseableHandle extends CloseableHandle {
+    private final AtomicBoolean open = new AtomicBoolean(true);
+    private final SftpClient client;
+
+    public DefaultCloseableHandle(SftpClient client, String id) {
+        super(id);
+        this.client = ValidateUtils.checkNotNull(client, "No client for id=%s", id);
+    }
+
+    public final SftpClient getSftpClient() {
+        return client;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (open.getAndSet(false)) {
+            client.close(this);
+        }
+    }
+}


[10/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
[SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

* Moved SFTP related definitions to own package


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

Branch: refs/heads/master
Commit: a6e2bf9e4e6f71bde5519085150109799724b579
Parents: 82791d4
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Wed Jul 1 13:56:48 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Wed Jul 1 13:56:48 2015 +0300

----------------------------------------------------------------------
 .../java.nio.file.spi.FileSystemProvider        |    2 +-
 .../sshd/client/session/ClientSession.java      |    2 +-
 .../sshd/client/session/ClientSessionImpl.java  |    8 +-
 .../sshd/client/sftp/AbstractSftpClient.java    |  124 -
 .../client/sftp/DefaultCloseableHandle.java     |   55 -
 .../sshd/client/sftp/DefaultSftpClient.java     | 1174 ---------
 .../org/apache/sshd/client/sftp/SftpClient.java |  309 ---
 .../sshd/client/sftp/SftpFileChannel.java       |  389 ---
 .../apache/sshd/client/sftp/SftpFileSystem.java |  384 ---
 .../client/sftp/SftpFileSystemProvider.java     |  892 -------
 .../org/apache/sshd/client/sftp/SftpPath.java   |   46 -
 .../sshd/client/subsystem/SubsystemClient.java  |   31 +
 .../subsystem/sftp/AbstractSftpClient.java      |  130 +
 .../subsystem/sftp/DefaultCloseableHandle.java  |   55 +
 .../subsystem/sftp/DefaultSftpClient.java       | 1179 +++++++++
 .../sshd/client/subsystem/sftp/SftpClient.java  |  310 +++
 .../client/subsystem/sftp/SftpFileChannel.java  |  389 +++
 .../client/subsystem/sftp/SftpFileSystem.java   |  465 ++++
 .../subsystem/sftp/SftpFileSystemProvider.java  |  892 +++++++
 .../sshd/client/subsystem/sftp/SftpPath.java    |   46 +
 .../file/root/RootedFileSystemProvider.java     |   66 +-
 .../apache/sshd/common/sftp/SftpConstants.java  |  223 --
 .../common/subsystem/sftp/SftpConstants.java    |  223 ++
 .../org/apache/sshd/common/util/io/IoUtils.java |   13 +
 .../java/org/apache/sshd/server/SshServer.java  |   50 +-
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 2280 ------------------
 .../sshd/server/sftp/SftpSubsystemFactory.java  |  139 --
 .../server/sftp/UnsupportedAttributePolicy.java |   36 -
 .../shell/InteractiveProcessShellFactory.java   |   23 +-
 .../sshd/server/shell/ProcessShellFactory.java  |   25 +-
 .../sshd/server/subsystem/SubsystemFactory.java |   38 +
 .../server/subsystem/sftp/SftpSubsystem.java    | 2280 ++++++++++++++++++
 .../subsystem/sftp/SftpSubsystemFactory.java    |  139 ++
 .../sftp/UnsupportedAttributePolicy.java        |   36 +
 .../java/org/apache/sshd/client/ClientTest.java |    2 +-
 .../client/sftp/DefaultCloseableHandleTest.java |   90 -
 .../sshd/client/sftp/SftpFileSystemTest.java    |  286 ---
 .../org/apache/sshd/client/sftp/SftpTest.java   |  651 -----
 .../sftp/DefaultCloseableHandleTest.java        |   92 +
 .../subsystem/sftp/SftpFileSystemTest.java      |  288 +++
 .../sshd/client/subsystem/sftp/SftpTest.java    |  652 +++++
 .../java/org/apache/sshd/server/ServerTest.java |    2 +-
 .../server/sftp/SftpSubsystemFactoryTest.java   |   98 -
 .../sftp/SftpSubsystemFactoryTest.java          |  100 +
 .../sshd/git/pack/GitPackCommandTest.java       |    2 +-
 .../apache/sshd/git/pgm/GitPgmCommandTest.java  |    2 +-
 46 files changed, 7453 insertions(+), 7265 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
index 7691b7c..7d90d38 100644
--- a/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
+++ b/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
@@ -17,5 +17,5 @@
 ## under the License.
 ##
 
-org.apache.sshd.client.sftp.SftpFileSystemProvider
+org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider
 org.apache.sshd.common.file.root.RootedFileSystemProvider

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index 442c5d2..f74f7c3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -33,7 +33,7 @@ import org.apache.sshd.client.channel.ChannelSubsystem;
 import org.apache.sshd.client.channel.ClientChannel;
 import org.apache.sshd.client.future.AuthFuture;
 import org.apache.sshd.client.scp.ScpClient;
-import org.apache.sshd.client.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.common.future.SshFuture;
 import org.apache.sshd.common.scp.ScpTransferEventListener;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index 305c53b..73f1b46 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -41,10 +41,10 @@ import org.apache.sshd.client.future.AuthFuture;
 import org.apache.sshd.client.future.DefaultAuthFuture;
 import org.apache.sshd.client.scp.DefaultScpClient;
 import org.apache.sshd.client.scp.ScpClient;
-import org.apache.sshd.client.sftp.DefaultSftpClient;
-import org.apache.sshd.client.sftp.SftpClient;
-import org.apache.sshd.client.sftp.SftpFileSystem;
-import org.apache.sshd.client.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.DefaultSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.Service;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java
deleted file mode 100644
index 2bcb9ef..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.client.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient {
-    protected AbstractSftpClient() {
-        super();
-    }
-    
-    @Override
-    public CloseableHandle open(String path) throws IOException {
-        return open(path, Collections.<OpenMode>emptySet());
-    }
-    
-    @Override
-    public CloseableHandle open(String path, OpenMode ... options) throws IOException {
-        return open(path, GenericUtils.of(options));
-    }
-
-    @Override
-    public void rename(String oldPath, String newPath) throws IOException {
-        rename(oldPath, newPath, Collections.<CopyMode>emptySet());
-    }
-    
-    @Override
-    public void rename(String oldPath, String newPath, CopyMode ... options) throws IOException {
-        rename(oldPath, newPath, GenericUtils.of(options));
-    }
-
-    @Override
-    public InputStream read(String path) throws IOException {
-        return read(path, DEFAULT_READ_BUFFER_SIZE);
-    }
-
-    @Override
-    public InputStream read(String path, int bufferSize) throws IOException {
-        return read(path, bufferSize, EnumSet.of(OpenMode.Read));
-    }
-
-    @Override
-    public InputStream read(String path, OpenMode ... mode) throws IOException {
-        return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
-    }
-
-    @Override
-    public InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException {
-        return read(path, bufferSize, GenericUtils.of(mode));
-    }
-
-    @Override
-    public InputStream read(String path, Collection<OpenMode>  mode) throws IOException {
-        return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
-    }
-
-    @Override
-    public int read(Handle handle, long fileOffset, byte[] dst) throws IOException {
-        return read(handle, fileOffset, dst, 0, dst.length);
-    }
-
-    @Override
-    public OutputStream write(String path) throws IOException {
-        return write(path, DEFAULT_WRITE_BUFFER_SIZE);
-    }
-
-    @Override
-    public OutputStream write(String path, int bufferSize) throws IOException {
-        return write(path, bufferSize, EnumSet.of(OpenMode.Write, OpenMode.Create, OpenMode.Truncate));
-    }
-
-    @Override
-    public OutputStream write(String path, OpenMode ... mode) throws IOException {
-        return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
-    }
-
-    @Override
-    public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
-        return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
-    }
-
-    @Override
-    public OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException {
-        return write(path, bufferSize, GenericUtils.of(mode));
-    }
-
-    @Override
-    public void write(Handle handle, long fileOffset, byte[] src) throws IOException {
-        write(handle, fileOffset, src, 0, src.length);
-    }
-
-    @Override
-    public void symLink(String linkPath, String targetPath) throws IOException {
-        link(linkPath, targetPath, true);
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java
deleted file mode 100644
index f86c47e..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.client.sftp;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.sftp.SftpClient.CloseableHandle;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class DefaultCloseableHandle extends CloseableHandle {
-    private final AtomicBoolean open = new AtomicBoolean(true);
-    private final SftpClient client;
-
-    public DefaultCloseableHandle(SftpClient client, String id) {
-        super(id);
-        this.client = ValidateUtils.checkNotNull(client, "No client for id=%s", id);
-    }
-
-    public final SftpClient getSftpClient() {
-        return client;
-    }
-
-    @Override
-    public boolean isOpen() {
-        return open.get();
-    }
-
-    @Override
-    public void close() throws IOException {
-        if (open.getAndSet(false)) {
-            client.close(this);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
deleted file mode 100644
index 6b58c36..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
+++ /dev/null
@@ -1,1174 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_APPEND_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_READ_ATTRIBUTES;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_READ_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_WRITE_ATTRIBUTES;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_WRITE_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V3;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V4;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V5;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V6;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_ALL;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_SIZE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_TYPE_REGULAR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_TYPE_SYMLINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_APPEND;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_CREAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_CREATE_NEW;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_CREATE_TRUNCATE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_EXCL;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_OPEN_EXISTING;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_OPEN_OR_CREATE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_READ;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_TRUNC;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_WRITE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_ATTRS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_BLOCK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_CLOSE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_FSETSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_FSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_HANDLE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_INIT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_LINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_LSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_MKDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_NAME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_OPEN;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_OPENDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_READ;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_READDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_READLINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_REALPATH;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_REMOVE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RENAME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RENAME_ATOMIC;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RENAME_OVERWRITE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RMDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_SETSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_STAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_STATUS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_SYMLINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_UNBLOCK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_VERSION;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_WRITE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_EOF;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_OK;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFLNK;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFREG;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.attribute.FileTime;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.SftpException;
-import org.apache.sshd.client.channel.ChannelSubsystem;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.InputStreamWithChannel;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
-import org.apache.sshd.common.util.io.OutputStreamWithChannel;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class DefaultSftpClient extends AbstractSftpClient {
-    private final ClientSession clientSession;
-    private final ChannelSubsystem channel;
-    private final Map<Integer, Buffer> messages;
-    private final AtomicInteger cmdId = new AtomicInteger(100);
-    private final Buffer receiveBuffer = new ByteArrayBuffer();
-    private boolean closing;
-    private int version;
-    private final Map<String, byte[]> extensions = new HashMap<>();
-
-    public DefaultSftpClient(ClientSession clientSession) throws IOException {
-        this.clientSession = clientSession;
-        this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        this.messages = new HashMap<>();
-        this.channel.setOut(new OutputStream() {
-            @Override
-            public void write(int b) throws IOException {
-                write(new byte[] { (byte) b }, 0, 1);
-            }
-            @Override
-            public void write(byte[] b, int off, int len) throws IOException {
-                data(b, off, len);
-            }
-        });
-        this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE));
-        this.channel.open().verify(FactoryManagerUtils.getLongProperty(clientSession, SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT));
-        this.channel.onClose(new Runnable() {
-            @SuppressWarnings("synthetic-access")
-            @Override
-            public void run() {
-                synchronized (messages) {
-                    closing = true;
-                    messages.notifyAll();
-                }
-            }
-        });
-        init();
-    }
-
-    @Override
-    public int getVersion() {
-        return version;
-    }
-
-    @Override
-    public boolean isClosing() {
-        return closing;
-    }
-
-    @Override
-    public void close() throws IOException {
-        if (this.channel.isOpen()) {
-            this.channel.close(false);
-        }
-    }
-
-    /**
-     * Receive binary data
-     */
-    protected int data(byte[] buf, int start, int len) throws IOException {
-        Buffer incoming = new ByteArrayBuffer(buf,  start, len);
-        // If we already have partial data, we need to append it to the buffer and use it
-        if (receiveBuffer.available() > 0) {
-            receiveBuffer.putBuffer(incoming);
-            incoming = receiveBuffer;
-        }
-        // Process commands
-        int rpos = incoming.rpos();
-        for (int count=0; receive(incoming); count++) {
-            if (log.isTraceEnabled()) {
-                log.trace("Processed " + count + " data messages");
-            }
-        }
-
-        int read = incoming.rpos() - rpos;
-        // Compact and add remaining data
-        receiveBuffer.compact();
-        if (receiveBuffer != incoming && incoming.available() > 0) {
-            receiveBuffer.putBuffer(incoming);
-        }
-        return read;
-    }
-
-    /**
-     * Read SFTP packets from buffer
-     */
-    protected boolean receive(Buffer incoming) throws IOException {
-        int rpos = incoming.rpos();
-        int wpos = incoming.wpos();
-        clientSession.resetIdleTimeout();
-        if ((wpos - rpos) > 4) {
-            int length = incoming.getInt();
-            if (length < 5) {
-                throw new IOException("Illegal sftp packet length: " + length);
-            }
-            if ((wpos - rpos) >= (length + 4)) {
-                incoming.rpos(rpos);
-                incoming.wpos(rpos + 4 + length);
-                process(incoming);
-                incoming.rpos(rpos + 4 + length);
-                incoming.wpos(wpos);
-                return true;
-            }
-        }
-        incoming.rpos(rpos);
-        return false;
-    }
-
-    /**
-     * Process an SFTP packet
-     */
-    protected void process(Buffer incoming) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(incoming.available());
-        buffer.putBuffer(incoming);
-        buffer.rpos(5);
-        int id = buffer.getInt();
-        buffer.rpos(0);
-        synchronized (messages) {
-            messages.put(Integer.valueOf(id), buffer);
-            messages.notifyAll();
-        }
-    }
-
-    protected int send(int cmd, Buffer buffer) throws IOException {
-        int id = cmdId.incrementAndGet();
-        
-        try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
-            dos.writeInt(5 + buffer.available());
-            dos.writeByte(cmd);
-            dos.writeInt(id);
-            dos.write(buffer.array(), buffer.rpos(), buffer.available());
-            dos.flush();
-        }
-
-        return id;
-    }
-
-    protected Buffer receive(int id) throws IOException {
-        synchronized (messages) {
-            while (true) {
-                if (closing) {
-                    throw new SshException("Channel has been closed");
-                }
-                Buffer buffer = messages.remove(Integer.valueOf(id));
-                if (buffer != null) {
-                    return buffer;
-                }
-                try {
-                    messages.wait();
-                } catch (InterruptedException e) {
-                    throw (IOException) new InterruptedIOException("Interrupted while waiting for messages").initCause(e);
-                }
-            }
-        }
-    }
-
-    protected Buffer read() throws IOException {
-        try(DataInputStream dis=new DataInputStream(new NoCloseInputStream(channel.getInvertedOut()))) {
-            int length = dis.readInt();
-            if (length < 5) {
-                throw new IllegalArgumentException("Bad length: " + length);
-            }
-            Buffer buffer = new ByteArrayBuffer(length + 4);
-            buffer.putInt(length);
-            int nb = length;
-            while (nb > 0) {
-                int readLen = dis.read(buffer.array(), buffer.wpos(), nb);
-                if (readLen < 0) {
-                    throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb);
-                }
-                buffer.wpos(buffer.wpos() + readLen);
-                nb -= readLen;
-            }
-
-            return buffer;
-        }
-    }
-
-    protected void init() throws IOException {
-        // Init packet
-        try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
-            dos.writeInt(5);
-            dos.writeByte(SSH_FXP_INIT);
-            dos.writeInt(SFTP_V6);
-            dos.flush();
-        }
-
-        Buffer buffer;
-        synchronized (messages) {
-            while (messages.isEmpty()) {
-                try {
-                    messages.wait();
-                } catch (InterruptedException e) {
-                    throw (IOException) new InterruptedIOException("Interruppted init()").initCause(e);
-                }
-            }
-            buffer = messages.remove(messages.keySet().iterator().next());
-
-        }
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_VERSION) {
-            if (id < SFTP_V3) {
-                throw new SshException("Unsupported sftp version " + id);
-            }
-            version = id;
-            while (buffer.available() > 0) {
-                String name = buffer.getString();
-                byte[] data = buffer.getBytes();
-                extensions.put(name, data);
-            }
-        } else if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("init(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-
-            throw new SftpException(substatus, msg);
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    protected void checkStatus(Buffer buffer) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-
-            if (substatus != SSH_FX_OK) {
-                throw new SftpException(substatus, msg);
-            }
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    protected String checkHandle(Buffer buffer) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_HANDLE) {
-            String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
-            return handle;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    protected Attributes checkAttributes(Buffer buffer) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_ATTRS) {
-            return readAttributes(buffer);
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    protected String checkOneName(Buffer buffer) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_NAME) {
-            int len = buffer.getInt();
-            if (len != 1) {
-                throw new SshException("SFTP error: received " + len + " names instead of 1");
-            }
-            String name = buffer.getString(), longName = null;
-            if (version == SFTP_V3) {
-                longName = buffer.getString();
-            }
-            Attributes attrs = readAttributes(buffer);
-            if (log.isTraceEnabled()) {
-                log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
-            }
-            return name;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    protected Attributes readAttributes(Buffer buffer) throws IOException {
-        Attributes attrs = new Attributes();
-        int flags = buffer.getInt();
-        if (version == SFTP_V3) {
-            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-                attrs.flags.add(Attribute.Size);
-                attrs.size = buffer.getLong();
-            }
-            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-                attrs.flags.add(Attribute.UidGid);
-                attrs.uid = buffer.getInt();
-                attrs.gid = buffer.getInt();
-            }
-            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-                attrs.flags.add(Attribute.Perms);
-                attrs.perms = buffer.getInt();
-            }
-            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-                attrs.flags.add(Attribute.AcModTime);
-                attrs.atime = buffer.getInt();
-                attrs.mtime = buffer.getInt();
-            }
-        } else if (version >= SFTP_V4) {
-            attrs.type = buffer.getByte();
-            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-                attrs.flags.add(Attribute.Size);
-                attrs.size = buffer.getLong();
-            }
-            if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
-                attrs.flags.add(Attribute.OwnerGroup);
-                attrs.owner = buffer.getString();
-                attrs.group = buffer.getString();
-            }
-            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-                attrs.flags.add(Attribute.Perms);
-                attrs.perms = buffer.getInt();
-            }
-            
-            // update the permissions according to the type
-            switch (attrs.type) {
-                case SSH_FILEXFER_TYPE_REGULAR:
-                    attrs.perms |= S_IFREG;
-                    break;
-                case SSH_FILEXFER_TYPE_DIRECTORY:
-                    attrs.perms |= S_IFDIR;
-                    break;
-                case SSH_FILEXFER_TYPE_SYMLINK:
-                    attrs.perms |= S_IFLNK;
-                    break;
-                default:    // do nothing
-            }
-
-            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
-                attrs.flags.add(Attribute.AccessTime);
-                attrs.accessTime = readTime(buffer, flags);
-                attrs.atime = (int) attrs.accessTime.to(TimeUnit.SECONDS);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
-                attrs.flags.add(Attribute.CreateTime);
-                attrs.createTime = readTime(buffer, flags);
-                attrs.ctime = (int) attrs.createTime.to(TimeUnit.SECONDS);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
-                attrs.flags.add(Attribute.ModifyTime);
-                attrs.modifyTime = readTime(buffer, flags);
-                attrs.mtime = (int) attrs.modifyTime.to(TimeUnit.SECONDS);
-            }
-            // TODO: acl
-        } else {
-            throw new IllegalStateException("readAttributes - unsupported version: " + version);
-        }
-        return attrs;
-    }
-
-    private FileTime readTime(Buffer buffer, int flags) {
-        long secs = buffer.getLong();
-        long millis = secs * 1000;
-        if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
-            millis += buffer.getInt() / 1000000l;
-        }
-        return FileTime.from(millis, TimeUnit.MILLISECONDS);
-    }
-
-    protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
-        if (version == SFTP_V3) {
-            int flags = 0;
-            for (Attribute a : attributes.flags) {
-                switch (a) {
-                    case Size:
-                        flags |= SSH_FILEXFER_ATTR_SIZE;
-                        break;
-                    case UidGid:
-                        flags |= SSH_FILEXFER_ATTR_UIDGID;
-                        break;
-                    case Perms:
-                        flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
-                        break;
-                    case AcModTime:
-                        flags |= SSH_FILEXFER_ATTR_ACMODTIME;
-                        break;
-                    default:    // do nothing
-                }
-            }
-            buffer.putInt(flags);
-            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-                buffer.putLong(attributes.size);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-                buffer.putInt(attributes.uid);
-                buffer.putInt(attributes.gid);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-                buffer.putInt(attributes.perms);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-                buffer.putInt(attributes.atime);
-                buffer.putInt(attributes.mtime);
-            }
-        } else if (version >= SFTP_V4) {
-            int flags = 0;
-            for (Attribute a : attributes.flags) {
-                switch (a) {
-                    case Size:
-                        flags |= SSH_FILEXFER_ATTR_SIZE;
-                        break;
-                    case OwnerGroup:
-                        flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
-                        break;
-                    case Perms:
-                        flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
-                        break;
-                    case AccessTime:
-                        flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
-                        break;
-                    case ModifyTime:
-                        flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
-                        break;
-                    case CreateTime:
-                        flags |= SSH_FILEXFER_ATTR_CREATETIME;
-                        break;
-                    default:    // do nothing
-                }
-            }
-            buffer.putInt(flags);
-            buffer.putByte(attributes.type);
-            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-                buffer.putLong(attributes.size);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
-                buffer.putString(attributes.owner != null ? attributes.owner : "OWNER@", StandardCharsets.UTF_8);
-                buffer.putString(attributes.group != null ? attributes.group : "GROUP@", StandardCharsets.UTF_8);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-                buffer.putInt(attributes.perms);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
-                buffer.putLong(attributes.accessTime.to(TimeUnit.SECONDS));
-                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
-                    long nanos = attributes.accessTime.to(TimeUnit.NANOSECONDS);
-                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
-                    buffer.putInt((int) nanos);
-                }
-                buffer.putInt(attributes.atime);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
-                buffer.putLong(attributes.createTime.to(TimeUnit.SECONDS));
-                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
-                    long nanos = attributes.createTime.to(TimeUnit.NANOSECONDS);
-                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
-                    buffer.putInt((int) nanos);
-                }
-                buffer.putInt(attributes.atime);
-            }
-            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
-                buffer.putLong(attributes.modifyTime.to(TimeUnit.SECONDS));
-                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
-                    long nanos = attributes.modifyTime.to(TimeUnit.NANOSECONDS);
-                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
-                    buffer.putInt((int) nanos);
-                }
-                buffer.putInt(attributes.atime);
-            }
-            // TODO: acl
-        } else {
-            throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
-        }
-    }
-
-    @Override
-    public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(path);
-        if (version == SFTP_V3) {
-            int mode = 0;
-            for (OpenMode m : options) {
-                switch (m) {
-                    case Read:
-                        mode |= SSH_FXF_READ;
-                        break;
-                    case Write:
-                        mode |= SSH_FXF_WRITE;
-                        break;
-                    case Append:
-                        mode |= SSH_FXF_APPEND;
-                        break;
-                    case Create:
-                        mode |= SSH_FXF_CREAT;
-                        break;
-                    case Truncate:
-                        mode |= SSH_FXF_TRUNC;
-                        break;
-                    case Exclusive:
-                        mode |= SSH_FXF_EXCL;
-                        break;
-                    default:    // do nothing
-                }
-            }
-            buffer.putInt(mode);
-        } else {
-            int mode = 0;
-            int access = 0;
-            if (options.contains(OpenMode.Read)) {
-                access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
-            }
-            if (options.contains(OpenMode.Write)) {
-                access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
-            }
-            if (options.contains(OpenMode.Append)) {
-                access |= ACE4_APPEND_DATA;
-            }
-            if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
-                mode |= SSH_FXF_CREATE_NEW;
-            } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
-                mode |= SSH_FXF_CREATE_TRUNCATE;
-            } else if (options.contains(OpenMode.Create)) {
-                mode |= SSH_FXF_OPEN_OR_CREATE;
-            } else if (options.contains(OpenMode.Truncate)) {
-                mode |= SSH_FXF_TRUNCATE_EXISTING;
-            } else {
-                mode |= SSH_FXF_OPEN_EXISTING;
-            }
-            if (version >= SFTP_V5) {
-                buffer.putInt(access);
-            }
-            buffer.putInt(mode);
-        }
-        writeAttributes(buffer, new Attributes());
-        return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
-    }
-
-    @Override
-    public void close(Handle handle) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
-        checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
-    }
-
-    @Override
-    public void remove(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(path);
-        checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
-    }
-
-    @Override
-    public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(oldPath);
-        buffer.putString(newPath);
-        
-        int numOptions = GenericUtils.size(options);
-        if (version >= SFTP_V5) {
-            int opts = 0;
-            if (numOptions > 0) {
-                for (CopyMode opt : options) {
-                    switch (opt) {
-                        case Atomic:
-                            opts |= SSH_FXP_RENAME_ATOMIC;
-                            break;
-                        case Overwrite:
-                            opts |= SSH_FXP_RENAME_OVERWRITE;
-                            break;
-                        default:    // do nothing
-                    }
-                }
-            }
-            buffer.putInt(opts);
-        } else if (numOptions > 0) {
-            throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
-                                                  + " - copy options can not be used with this SFTP version: " + options);
-        }
-        checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
-    }
-
-    @Override
-    public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
-        buffer.putLong(fileOffset);
-        buffer.putInt(len);
-        return checkData(receive(send(SSH_FXP_READ, buffer)), dstoff, dst);
-    }
-
-    protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-
-            if (substatus == SSH_FX_EOF) {
-                return -1;
-            }
-
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_DATA) {
-            int len = buffer.getInt();
-            buffer.getRawBytes(dst, dstoff, len);
-            return len;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    @Override
-    public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
-        // do some bounds checking first
-        if ((fileOffset < 0) || (srcoff < 0) || (len < 0)) {
-            throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
-                                             + " are non-negative values: file-offset=" + fileOffset
-                                             + ", src-offset=" + srcoff + ", len=" + len);
-        }
-        if ((srcoff + len) > src.length) {
-            throw new IllegalArgumentException("write(" + handle + ")"
-                                             + " cannot read bytes " + srcoff + " to " + (srcoff + len)
-                                             + " when array is only of length " + src.length);
-        }
-
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
-        buffer.putLong(fileOffset);
-        buffer.putBytes(src, srcoff, len);
-        checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
-    }
-
-    @Override
-    public void mkdir(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(path.length() +  Long.SIZE /* some extra fields */);
-        buffer.putString(path, StandardCharsets.UTF_8);
-        buffer.putInt(0);
-        if (version != SFTP_V3) {
-            buffer.putByte((byte) 0);
-        }
-        checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
-    }
-
-    @Override
-    public void rmdir(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(path.length() +  Long.SIZE /* some extra fields */);
-        buffer.putString(path);
-        checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
-    }
-
-    @Override
-    public CloseableHandle openDir(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(path);
-        return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
-    }
-
-    @Override
-    public DirEntry[] readDir(Handle handle) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
-        return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
-    }
-
-    protected DirEntry[] checkDir(Buffer buffer) throws IOException {
-        int length = buffer.getInt();
-        int type = buffer.getByte();
-        int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
-            }
-            if (substatus == SSH_FX_EOF) {
-                return null;
-            }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_NAME) {
-            int len = buffer.getInt();
-            DirEntry[] entries = new DirEntry[len];
-            for (int i = 0; i < len; i++) {
-                String name = buffer.getString();
-                String longName = (version == SFTP_V3) ? buffer.getString() : null;
-                Attributes attrs = readAttributes(buffer);
-                if (log.isTraceEnabled()) {
-                    log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
-                }
-
-                entries[i] = new DirEntry(name, longName, attrs);
-            }
-            return entries;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
-        }
-    }
-
-    @Override
-    public String canonicalPath(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(path);
-        return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
-    }
-
-    @Override
-    public Attributes stat(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(path);
-        if (version >= SFTP_V4) {
-            buffer.putInt(SSH_FILEXFER_ATTR_ALL);
-        }
-        return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
-    }
-
-    @Override
-    public Attributes lstat(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(path);
-        if (version >= SFTP_V4) {
-            buffer.putInt(SSH_FILEXFER_ATTR_ALL);
-        }
-        return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
-    }
-
-    @Override
-    public Attributes stat(Handle handle) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
-        if (version >= SFTP_V4) {
-            buffer.putInt(SSH_FILEXFER_ATTR_ALL);
-        }
-        return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
-    }
-
-    @Override
-    public void setStat(String path, Attributes attributes) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(path);
-        writeAttributes(buffer, attributes);
-        checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
-    }
-
-    @Override
-    public void setStat(Handle handle, Attributes attributes) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
-        writeAttributes(buffer, attributes);
-        checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
-    }
-
-    @Override
-    public String readLink(String path) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(path);
-        return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
-    }
-
-    @Override
-    public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
-        Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */);
-        if (version < SFTP_V6) {
-            if (!symbolic) {
-                throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
-            }
-            buffer.putString(targetPath);
-            buffer.putString(linkPath);
-            checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
-        } else {
-            buffer.putString(targetPath);
-            buffer.putString(linkPath);
-            buffer.putBoolean(symbolic);
-            checkStatus(receive(send(SSH_FXP_LINK, buffer)));
-        }
-    }
-
-    @Override
-    public void lock(Handle handle, long offset, long length, int mask) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
-        buffer.putLong(offset);
-        buffer.putLong(length);
-        buffer.putInt(mask);
-        checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
-    }
-
-    @Override
-    public void unlock(Handle handle, long offset, long length) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
-        buffer.putLong(offset);
-        buffer.putLong(length);
-        checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
-    }
-
-    @Override
-    public Iterable<DirEntry> readDir(final String path) throws IOException {
-        return new Iterable<DirEntry>() {
-            @Override
-            public Iterator<DirEntry> iterator() {
-                return new Iterator<DirEntry>() {
-                    private CloseableHandle handle;
-                    private DirEntry[] entries;
-                    private int index;
-
-                    {
-                        open();
-                        load();
-                    }
-
-                    @Override
-                    public boolean hasNext() {
-                        return (entries != null) && (index < entries.length);
-                    }
-
-                    @Override
-                    public DirEntry next() {
-                        DirEntry entry = entries[index++];
-                        if (index >= entries.length) {
-                            load();
-                        }
-                        return entry;
-                    }
-
-                    @SuppressWarnings("synthetic-access")
-                    private void open() {
-                        try {
-                            handle = openDir(path);
-                            if (log.isDebugEnabled()) {
-                                log.debug("readDir(" + path + ") handle=" + handle);
-                            }
-                        } catch (IOException e) {
-                            if (log.isDebugEnabled()) {
-                                log.debug("readDir(" + path + ") failed (" + e.getClass().getSimpleName() + ") to open dir: " + e.getMessage());
-                            }
-                            throw new RuntimeException(e);
-                        }
-                    }
-
-                    @SuppressWarnings("synthetic-access")
-                    private void load() {
-                        try {
-                            entries = readDir(handle);
-                            index = 0;
-                            if (entries == null) {
-                                handle.close();
-                            }
-                        } catch (IOException e) {
-                            entries = null;
-                            try {
-                                handle.close();
-                            } catch (IOException t) {
-                                if (log.isTraceEnabled()) {
-                                    log.trace(t.getClass().getSimpleName() + " while close handle=" + handle
-                                            + " due to " + e.getClass().getSimpleName() + " [" + e.getMessage() + "]"
-                                            + ": " + t.getMessage());
-                                }
-                            }
-                            throw new RuntimeException(e);
-                        }
-                    }
-
-                    @Override
-                    public void remove() {
-                        throw new UnsupportedOperationException("readDir(" + path + ") Iterator#remove() N/A");
-                    }
-                };
-            }
-        };
-    }
-
-    @Override
-    public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
-        if (bufferSize < MIN_READ_BUFFER_SIZE) {
-            throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE);
-        }
-
-        return new InputStreamWithChannel() {
-            private byte[] bb = new byte[1];
-            private byte[] buffer = new byte[bufferSize];
-            private int index;
-            private int available;
-            private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
-            private long offset;
-
-            @Override
-            public boolean isOpen() {
-                return (handle != null) && handle.isOpen();
-            }
-
-            @Override
-            public int read() throws IOException {
-                int read = read(bb, 0, 1);
-                if (read > 0) {
-                    return bb[0];
-                }
-
-                return read;
-            }
-
-            @Override
-            public int read(byte[] b, int off, int len) throws IOException {
-                if (!isOpen()) {
-                    throw new IOException("read(" + path + ") stream closed");
-                }
-
-                int idx = off;
-                while (len > 0) {
-                    if (index >= available) {
-                        available = DefaultSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
-                        if (available < 0) {
-                            if (idx == off) {
-                                return -1;
-                            } else {
-                                break;
-                            }
-                        }
-                        offset += available;
-                        index = 0;
-                    }
-                    if (index >= available) {
-                        break;
-                    }
-                    int nb = Math.min(len, available - index);
-                    System.arraycopy(buffer, index, b, idx, nb);
-                    index += nb;
-                    idx += nb;
-                    len -= nb;
-                }
-                return idx - off;
-            }
-
-            @Override
-            public void close() throws IOException {
-                if (isOpen()) {
-                    try {
-                        handle.close();
-                    } finally {
-                        handle = null;
-                    }
-                }
-            }
-        };
-    }
-
-    @Override
-    public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
-        if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
-            throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE);
-        }
-
-        return new OutputStreamWithChannel() {
-            private byte[] bb = new byte[1];
-            private byte[] buffer = new byte[bufferSize];
-            private int index;
-            private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
-            private long offset;
-
-            @Override
-            public boolean isOpen() {
-                return (handle != null) && handle.isOpen();
-            }
-
-            @Override
-            public void write(int b) throws IOException {
-                bb[0] = (byte) b;
-                write(bb, 0, 1);
-            }
-
-            @Override
-            public void write(byte[] b, int off, int len) throws IOException {
-                if (!isOpen()) {
-                    throw new IOException("write(" + path + ")[len=" + len + "] stream is closed");
-                }
-
-                do {
-                    int nb = Math.min(len, buffer.length - index);
-                    System.arraycopy(b, off, buffer, index, nb);
-                    index += nb;
-                    if (index == buffer.length) {
-                        flush();
-                    }
-                    off += nb;
-                    len -= nb;
-                } while (len > 0);
-            }
-
-            @Override
-            public void flush() throws IOException {
-                if (!isOpen()) {
-                    throw new IOException("flush(" + path + ") stream is closed");
-                }
-
-                DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
-                offset += index;
-                index = 0;
-            }
-
-            @Override
-            public void close() throws IOException {
-                if (isOpen()) {
-                    try {
-                        try {
-                            if (index > 0) {
-                                flush();
-                            }
-                        } finally {
-                            handle.close();
-                        }
-                    } finally {
-                        handle = null;
-                    }
-                }
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java
deleted file mode 100644
index 739a408..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFLNK;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFMT;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFREG;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.Channel;
-import java.nio.file.attribute.FileTime;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface SftpClient extends Closeable {
-
-    enum OpenMode {
-        Read,
-        Write,
-        Append,
-        Create,
-        Truncate,
-        Exclusive
-    }
-
-    enum CopyMode {
-        Atomic,
-        Overwrite
-    }
-
-    enum Attribute {
-        Size,
-        UidGid,
-        Perms,
-        AcModTime,
-        OwnerGroup,
-        AccessTime,
-        ModifyTime,
-        CreateTime,
-    }
-
-    public static class Handle {
-        public final String id;
-        public Handle(String id) {
-            this.id = ValidateUtils.checkNotNullAndNotEmpty(id, "No handle ID", GenericUtils.EMPTY_OBJECT_ARRAY);
-        }
-        
-        @Override
-        public String toString() {
-            return id;
-        }
-    }
-
-    public static abstract class CloseableHandle extends Handle implements Channel, Closeable {
-        protected CloseableHandle(String id) {
-            super(id);
-        }
-    }
-
-    public static class Attributes {
-        public final Set<Attribute> flags = EnumSet.noneOf(Attribute.class);
-        public long size;
-        public byte type;
-        public int uid;
-        public int gid;
-        public int perms;
-        public int atime;
-        public int ctime;
-        public int mtime;
-        public String owner;
-        public String group;
-        public FileTime accessTime;
-        public FileTime createTime;
-        public FileTime modifyTime;
-
-        @Override
-        public String toString() {
-            return "type=" + type
-                 + ";size=" + size
-                 + ";uid=" + uid
-                 + ";gid=" + gid
-                 + ";perms=0x" + Integer.toHexString(perms)
-                 + ";flags=" + flags
-                 + ";owner=" + owner
-                 + ";group=" + group
-                 + ";aTime=(" + atime + ")[" + accessTime + "]"
-                 + ";cTime=(" + ctime + ")[" + createTime + "]"
-                 + ";mTime=(" + mtime + ")[" + modifyTime + "]"
-                 ;
-        }
-
-        public Attributes size(long size) {
-            flags.add(Attribute.Size);
-            this.size = size;
-            return this;
-        }
-        public Attributes owner(String owner) {
-            flags.add(Attribute.OwnerGroup);
-            this.owner = owner;
-            if (GenericUtils.isEmpty(group)) {
-                group = "GROUP@";
-            }
-            return this;
-        }
-        public Attributes group(String group) {
-            flags.add(Attribute.OwnerGroup);
-            this.group = group;
-            if (GenericUtils.isEmpty(owner)) {
-                owner = "OWNER@";
-            }
-            return this;
-        }
-        public Attributes owner(int uid, int gid) {
-            flags.add(Attribute.UidGid);
-            this.uid = uid;
-            this.gid = gid;
-            return this;
-        }
-        public Attributes perms(int perms) {
-            flags.add(Attribute.Perms);
-            this.perms = perms;
-            return this;
-        }
-        public Attributes atime(int atime) {
-            flags.add(Attribute.AccessTime);
-            this.atime = atime;
-            this.accessTime = FileTime.from(atime, TimeUnit.SECONDS);
-            return this;
-        }
-        public Attributes ctime(int ctime) {
-            flags.add(Attribute.CreateTime);
-            this.ctime = ctime;
-            this.createTime = FileTime.from(atime, TimeUnit.SECONDS);
-            return this;
-        }
-        public Attributes mtime(int mtime) {
-            flags.add(Attribute.ModifyTime);
-            this.mtime = mtime;
-            this.modifyTime = FileTime.from(atime, TimeUnit.SECONDS);
-            return this;
-        }
-        public Attributes time(int atime, int mtime) {
-            flags.add(Attribute.AcModTime);
-            this.atime = atime;
-            this.mtime = mtime;
-            return this;
-        }
-        public Attributes accessTime(FileTime atime) {
-            flags.add(Attribute.AccessTime);
-            this.atime = (int) atime.to(TimeUnit.SECONDS);
-            this.accessTime = atime;
-            return this;
-        }
-        public Attributes createTime(FileTime ctime) {
-            flags.add(Attribute.CreateTime);
-            this.ctime = (int) ctime.to(TimeUnit.SECONDS);
-            this.createTime = ctime;
-            return this;
-        }
-        public Attributes modifyTime(FileTime mtime) {
-            flags.add(Attribute.ModifyTime);
-            this.mtime = (int) mtime.to(TimeUnit.SECONDS);
-            this.modifyTime = mtime;
-            return this;
-        }
-        public boolean isRegularFile() {
-            return (perms & S_IFMT) == S_IFREG;
-        }
-        public boolean isDirectory() {
-            return (perms & S_IFMT) == S_IFDIR;
-        }
-        public boolean isSymbolicLink() {
-            return (perms & S_IFMT) == S_IFLNK;
-        }
-        public boolean isOther() {
-            return !isRegularFile() && !isDirectory() && !isSymbolicLink();
-        }
-    }
-
-    public static class DirEntry {
-        public String filename;
-        public String longFilename;
-        public Attributes attributes;
-        public DirEntry(String filename, String longFilename, Attributes attributes) {
-            this.filename = filename;
-            this.longFilename = longFilename;
-            this.attributes = attributes;
-        }
-    }
-
-    int getVersion();
-
-    boolean isClosing();
-
-    //
-    // Low level API
-    //
-
-    CloseableHandle open(String path) throws IOException;
-    CloseableHandle open(String path, OpenMode ... options) throws IOException;
-    CloseableHandle open(String path, Collection<OpenMode> options) throws IOException;
-
-    void close(Handle handle) throws IOException;
-
-    void remove(String path) throws IOException;
-
-    void rename(String oldPath, String newPath) throws IOException;
-    void rename(String oldPath, String newPath, CopyMode... options) throws IOException;
-    void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException;
-
-    int read(Handle handle, long fileOffset, byte[] dst) throws IOException;
-    int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException;
-
-    void write(Handle handle, long fileOffset, byte[] src) throws IOException;
-    void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException;
-
-    void mkdir(String path) throws IOException;
-
-    void rmdir(String path) throws IOException;
-
-    CloseableHandle openDir(String path) throws IOException;
-
-    DirEntry[] readDir(Handle handle) throws IOException;
-
-    String canonicalPath(String canonical) throws IOException;
-
-    Attributes stat(String path) throws IOException;
-
-    Attributes lstat(String path) throws IOException;
-
-    Attributes stat(Handle handle) throws IOException;
-
-    void setStat(String path, Attributes attributes) throws IOException;
-
-    void setStat(Handle handle, Attributes attributes) throws IOException;
-
-    String readLink(String path) throws IOException;
-
-    void symLink(String linkPath, String targetPath) throws IOException;
-
-    void link(String linkPath, String targetPath, boolean symbolic) throws IOException;
-
-    void lock(Handle handle, long offset, long length, int mask) throws IOException;
-
-    void unlock(Handle handle, long offset, long length) throws IOException;
-
-    //
-    // High level API
-    //
-
-    Iterable<DirEntry> readDir(String path) throws IOException;
-
-    // default values used if none specified
-    int MIN_BUFFER_SIZE=Byte.MAX_VALUE, MIN_READ_BUFFER_SIZE=MIN_BUFFER_SIZE, MIN_WRITE_BUFFER_SIZE=MIN_BUFFER_SIZE;
-    int IO_BUFFER_SIZE=32 * 1024, DEFAULT_READ_BUFFER_SIZE=IO_BUFFER_SIZE, DEFAULT_WRITE_BUFFER_SIZE=IO_BUFFER_SIZE;
-    long DEFAULT_WAIT_TIMEOUT=TimeUnit.SECONDS.toMillis(30L);
-
-    /**
-     * Property that can be used on the {@link org.apache.sshd.common.FactoryManager}
-     * to control the internal timeout used by the client to open a channel.
-     * If not specified then {@link #DEFAULT_CHANNEL_OPEN_TIMEOUT} value
-     * is used
-     */
-    String SFTP_CHANNEL_OPEN_TIMEOUT = "sftp-channel-open-timeout";
-        long DEFAULT_CHANNEL_OPEN_TIMEOUT = DEFAULT_WAIT_TIMEOUT;
-
-    InputStream read(String path) throws IOException;
-    InputStream read(String path, int bufferSize) throws IOException;
-    InputStream read(String path, OpenMode ... mode) throws IOException;
-    InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException;
-    InputStream read(String path, Collection<OpenMode> mode) throws IOException;
-    InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
-
-    OutputStream write(String path) throws IOException;
-    OutputStream write(String path, int bufferSize) throws IOException;
-    OutputStream write(String path, OpenMode ... mode) throws IOException;
-    OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException;
-    OutputStream write(String path, Collection<OpenMode> mode) throws IOException;
-    OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
-
-}


[06/10] mina-sshd git commit: [SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index 85f5d6d..1b60133 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -25,7 +25,6 @@ import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -43,7 +42,6 @@ import org.apache.sshd.common.io.mina.MinaServiceFactory;
 import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
 import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.server.auth.UserAuth;
@@ -63,9 +61,9 @@ import org.apache.sshd.server.session.ServerConnectionServiceFactory;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.server.session.ServerUserAuthServiceFactory;
 import org.apache.sshd.server.session.SessionFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
 import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
 import org.apache.sshd.server.shell.ProcessShellFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 
 /**
  * The SshServer class is the main entry point for the server side of the SSH protocol.
@@ -390,16 +388,18 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         boolean error = false;
         Map<String, String> options = new LinkedHashMap<String, String>();
 
-        for (int i = 0; i < args.length; i++) {
-            if ("-p".equals(args[i])) {
-                if (i + 1 >= args.length) {
-                    System.err.println("option requires an argument: " + args[i]);
+        int numArgs = GenericUtils.length(args);
+        for (int i = 0; i < numArgs; i++) {
+            String argName = args[i];
+            if ("-p".equals(argName)) {
+                if (i + 1 >= numArgs) {
+                    System.err.println("option requires an argument: " + argName);
                     break;
                 }
                 port = Integer.parseInt(args[++i]);
-            } else if ("-io".equals(args[i])) {
-                if (i + 1 >= args.length) {
-                    System.err.println("option requires an argument: " + args[i]);
+            } else if ("-io".equals(argName)) {
+                if (i + 1 >= numArgs) {
+                    System.err.println("option requires an argument: " + argName);
                     break;
                 }
                 provider = args[++i];
@@ -408,12 +408,12 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
                 } else if ("nio2".endsWith(provider)) {
                     System.setProperty(IoServiceFactory.class.getName(), Nio2ServiceFactory.class.getName());
                 } else {
-                    System.err.println("provider should be mina or nio2: " + args[i]);
+                    System.err.println("provider should be mina or nio2: " + argName);
                     break;
                 }
-            } else if ("-o".equals(args[i])) {
-                if (i + 1 >= args.length) {
-                    System.err.println("option requires and argument: " + args[i]);
+            } else if ("-o".equals(argName)) {
+                if (i + 1 >= numArgs) {
+                    System.err.println("option requires and argument: " + argName);
                     error = true;
                     break;
                 }
@@ -425,12 +425,12 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
                     break;
                 }
                 options.put(opt.substring(0, idx), opt.substring(idx + 1));
-            } else if (args[i].startsWith("-")) {
-                System.err.println("illegal option: " + args[i]);
+            } else if (argName.startsWith("-")) {
+                System.err.println("illegal option: " + argName);
                 error = true;
                 break;
             } else {
-                System.err.println("extra argument: " + args[i]);
+                System.err.println("extra argument: " + argName);
                 error = true;
                 break;
             }
@@ -458,23 +458,17 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
                 @Override
                 public boolean authenticate(String username, String password, ServerSession session) {
-                    return username != null && username.equals(password);
+                    return (username != null) && username.equals(password);
                 }
             });
         sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
         sshd.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
         sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(new CommandFactory() {
-            @Override
-            public Command createCommand(String command) {
-                EnumSet<ProcessShellFactory.TtyOptions> ttyOptions;
-                if (OsUtils.isUNIX()) {
-                    ttyOptions = EnumSet.of(ProcessShellFactory.TtyOptions.ONlCr);
-                } else {
-                    ttyOptions = EnumSet.of(ProcessShellFactory.TtyOptions.Echo, ProcessShellFactory.TtyOptions.ICrNl, ProcessShellFactory.TtyOptions.ONlCr);
+                @Override
+                public Command createCommand(String command) {
+                    return new ProcessShellFactory(GenericUtils.split(command, ' ')).create();
                 }
-                return new ProcessShellFactory(command.split(" "), ttyOptions).create();
-            }
-        }).build());
+            }).build());
         sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
         sshd.start();