You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2015/02/23 16:30:02 UTC

[01/15] mina-sshd git commit: Upgrade to 1.0.0-SNAPSHOT and remove unsupported modules

Repository: mina-sshd
Updated Branches:
  refs/heads/master 964e76890 -> 656f0d17e


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/java/org/apache/sshd/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/SftpTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/SftpTest.java
deleted file mode 100644
index 6cd79f7..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/SftpTest.java
+++ /dev/null
@@ -1,263 +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.sftp;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.Vector;
-
-import com.jcraft.jsch.ChannelSftp;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.Logger;
-import org.apache.sshd.SshServer;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.sftp.subsystem.SftpSubsystem;
-import org.apache.sshd.sftp.util.BogusPasswordAuthenticator;
-import org.apache.sshd.sftp.util.EchoShellFactory;
-import org.apache.sshd.sftp.util.JSchLogger;
-import org.apache.sshd.sftp.util.SimpleUserInfo;
-import org.apache.sshd.sftp.util.Utils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class SftpTest {
-
-    private SshServer sshd;
-    private int port;
-    private com.jcraft.jsch.Session session;
-
-    @Before
-    public void setUp() throws Exception {
-        port = Utils.getFreePort();
-
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setPort(port);
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
-        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
-        sshd.start();
-
-        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 {
-        session.disconnect();
-        sshd.stop();
-    }
-
-    @Test
-    @Ignore
-    public void testExternal() throws Exception {
-        System.out.println("SFTP subsystem available on port " + port);
-        Thread.sleep(5 * 60000);
-    }
-
-    @Test
-    public void testSftp() throws Exception {
-        String d = "0123456789\n";
-
-        File root = new File("target/scp");
-        File target = new File("target/scp/out.txt");
-        root.mkdirs();
-        assertTrue(root.exists());
-
-        for (int j = 10; j <= 10; j++) {
-            String data = "";
-            for (int i = 0; i < j; i++) {
-                data = data + d;
-            }
-
-            target.delete();
-            assertFalse(target.exists());
-            sendFile("target/scp/out.txt", data);
-            assertFileLength(target, data.length(), 5000);
-
-            target.delete();
-            assertFalse(target.exists());
-        }
-        root.delete();
-    }
-
-    @Test
-    public void testReadWriteWithOffset() throws Exception {
-        File root = new File("target/scp");
-        String unixPath = "target/scp/out.txt";
-        File target = new File(unixPath);
-        root.mkdirs();
-        assertTrue(root.exists());
-
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        c.put(new ByteArrayInputStream("0123456789".getBytes()), unixPath);
-
-        assertTrue(target.exists());
-        assertEquals("0123456789", readFile(unixPath));
-
-        OutputStream os = c.put(unixPath, null, ChannelSftp.APPEND, -5);
-        os.write("a".getBytes());
-        os.close();
-        c.disconnect();
-
-        assertTrue(target.exists());
-        assertEquals("01234a", readFile(unixPath));
-
-        target.delete();
-        assertFalse(target.exists());
-        root.delete();
-    }
-
-    @Test
-    public void testReadDir() throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        Vector res = c.ls("target/classes/org/apache/sshd/");
-        for (Object f : res) {
-            System.out.println(f.toString());
-        }
-    }
-
-    /*
-     * TODO: upgrade to a more recent version of ganymed to be able to test that
-
-    @Test
-    public void testBigFileWithGanymed() throws Exception {
-        final Connection conn = new Connection("localhost", port);
-        conn.connect(null, 5000, 0);
-        conn.authenticateWithPassword("sshd", "sshd");
-        final SFTPv3Client sftp_client = new SFTPv3Client(conn);
-
-        StringBuilder sb = new StringBuilder();
-        for (int i = 1; i < 1000; i++) {
-            sb.append("0123456789");
-            if (i % 10 == 0) {
-                sb.append("\n");
-            }
-        }
-        sb.append("\n");
-        byte[] buffer = sb.toString().getBytes();
-
-        // Upload
-
-        SFTPv3FileHandle handle = sftp_client.openFileRW("target/bigfile.txt");
-
-        long offset = 0;
-        for (int i = 0; i < 100; i++) {
-            sftp_client.write(handle, offset, buffer, 0, buffer.length);
-            offset += buffer.length;
-        }
-
-        sftp_client.closeFile(handle);
-
-        handle = sftp_client.openFileRW("target/bigfile.txt");
-
-        offset = 0;
-        buffer = new byte[32768];
-        for (;;) {
-            int len = sftp_client.read(handle, offset, buffer, 0, buffer.length);
-            if (len >= 0) {
-                offset += len;
-            } else {
-                break;
-            }
-        }
-
-        sftp_client.closeFile(handle);
-
-        sftp_client.close();
-
-    }
-    */
-
-    protected void assertFileLength(File file, long length, long timeout) throws Exception {
-        boolean ok = false;
-        while (timeout > 0) {
-            if (file.exists() && file.length() == length) {
-                if (!ok) {
-                    ok = true;
-                } else {
-                    return;
-                }
-            } else {
-                ok = false;
-            }
-            Thread.sleep(100);
-            timeout -= 100;
-        }
-        assertTrue(file.exists());
-        assertEquals(length, file.length());
-    }
-
-    protected String readFile(String path) throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        InputStream is = c.get(path);
-        try {
-            byte[] buffer = new byte[256];
-            int count;
-            while (-1 != (count = is.read(buffer))) {
-                bos.write(buffer, 0, count);
-            }
-        } finally {
-            is.close();
-        }
-
-        c.disconnect();
-        return new String(bos.toByteArray());
-    }
-
-    protected void sendFile(String path, String data) throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        c.put(new ByteArrayInputStream(data.getBytes()), path);
-        c.disconnect();
-    }
-
-    public static void main(String[] args) throws Exception {
-        SshServer sshd = SshServer.setUpDefaultServer();
-        sshd.setPort(8001);
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
-        sshd.start();
-        Thread.sleep(100000);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/BogusPasswordAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/BogusPasswordAuthenticator.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/BogusPasswordAuthenticator.java
deleted file mode 100644
index 69e047a..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/BogusPasswordAuthenticator.java
+++ /dev/null
@@ -1,34 +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.sftp.util;
-
-import org.apache.sshd.server.PasswordAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class BogusPasswordAuthenticator implements PasswordAuthenticator {
-
-    public boolean authenticate(String username, String password, ServerSession session) {
-        return username != null && username.equals(password);
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/EchoShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/EchoShellFactory.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/EchoShellFactory.java
deleted file mode 100644
index c4b4827..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/EchoShellFactory.java
+++ /dev/null
@@ -1,111 +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.sftp.util;
-
-import org.apache.sshd.common.Factory;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-
-import java.io.*;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class EchoShellFactory implements Factory<Command> {
-
-    public Command create() {
-        return new EchoShell();
-    }
-
-    public static class EchoShell implements Command, Runnable {
-
-        private InputStream in;
-        private OutputStream out;
-        private OutputStream err;
-        private ExitCallback callback;
-        private Environment environment;
-        private Thread thread;
-
-        public InputStream getIn() {
-            return in;
-        }
-
-        public OutputStream getOut() {
-            return out;
-        }
-
-        public OutputStream getErr() {
-            return err;
-        }
-
-        public Environment getEnvironment() {
-            return environment;
-        }
-
-        public void setInputStream(InputStream in) {
-            this.in = in;
-        }
-
-        public void setOutputStream(OutputStream out) {
-            this.out = out;
-        }
-
-        public void setErrorStream(OutputStream err) {
-            this.err = err;
-        }
-
-        public void setExitCallback(ExitCallback callback) {
-            this.callback = callback;
-        }
-
-        public void start(Environment env) throws IOException {
-            environment = env;
-            thread = new Thread(this, "EchoShell");
-            thread.start();
-        }
-
-        public void destroy() {
-            thread.interrupt();
-        }
-
-        public void run() {
-            BufferedReader r = new BufferedReader(new InputStreamReader(in));
-            try {
-                for (;;) {
-                    String s = r.readLine();
-                    if (s == null) {
-                        return;
-                    }
-                    out.write((s + "\n").getBytes());
-                    out.flush();
-                    if ("exit".equals(s)) {
-                        return;
-                    }
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-            } finally {
-                callback.onExit(0);
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/JSchLogger.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/JSchLogger.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/JSchLogger.java
deleted file mode 100644
index a7a4d62..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/JSchLogger.java
+++ /dev/null
@@ -1,56 +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.sftp.util;
-
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class JSchLogger implements Logger {
-
-    public static void init() {
-        JSch.setLogger(new JSchLogger());
-    }
-
-    private org.slf4j.Logger log = LoggerFactory.getLogger("org.jcraft.jsch.JSch");
-
-    public boolean isEnabled(int level) {
-        switch (level) {
-            case DEBUG: return log.isDebugEnabled();
-            case INFO:  return log.isInfoEnabled();
-            case WARN:  return log.isWarnEnabled();
-            case ERROR: return log.isErrorEnabled();
-            case FATAL: return log.isErrorEnabled();
-        }
-        return false;
-    }
-
-    public void log(int level, String message) {
-        switch (level) {
-            case DEBUG: log.debug(message); break;
-            case INFO:  log.info(message); break;
-            case WARN:  log.warn(message); break;
-            case ERROR: log.error(message); break;
-            case FATAL: log.error(message); break;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/SimpleUserInfo.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/SimpleUserInfo.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/SimpleUserInfo.java
deleted file mode 100644
index f3cf54f..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/SimpleUserInfo.java
+++ /dev/null
@@ -1,63 +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.sftp.util;
-
-import com.jcraft.jsch.UIKeyboardInteractive;
-import com.jcraft.jsch.UserInfo;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SimpleUserInfo implements UserInfo, UIKeyboardInteractive {
-
-    private final String password;
-
-    public SimpleUserInfo(String password) {
-        this.password = password;
-    }
-
-    public String getPassphrase() {
-        return null;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public boolean promptPassword(String message) {
-        return true;
-    }
-
-    public boolean promptPassphrase(String message) {
-        return false;
-    }
-
-    public boolean promptYesNo(String message) {
-        return true;
-    }
-
-    public void showMessage(String message) {
-    }
-
-    public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
-        return new String[] { password };
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/Utils.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/Utils.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/Utils.java
deleted file mode 100644
index cc09591..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/util/Utils.java
+++ /dev/null
@@ -1,58 +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.sftp.util;
-
-import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
-
-import java.io.File;
-import java.net.ServerSocket;
-import java.net.URISyntaxException;
-import java.net.URL;
-
-public class Utils {
-
-    public static FileKeyPairProvider createTestHostKeyProvider() {
-        return createTestKeyPairProvider("hostkey.pem");
-    }
-
-    public static FileKeyPairProvider createTestKeyPairProvider(String resource) {
-        return new FileKeyPairProvider(new String[] { getFile(resource) });
-    }
-
-    public static int getFreePort() throws Exception {
-        ServerSocket s = new ServerSocket(0);
-        try {
-            return s.getLocalPort();
-        } finally {
-            s.close();
-        }
-    }
-
-    private static String getFile(String resource) {
-        URL url = Utils.class.getClassLoader().getResource(resource);
-        File f;
-        try {
-            f = new File(url.toURI());
-        } catch(URISyntaxException e) {
-            f = new File(url.getPath());
-        }
-        return f.toString();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/resources/hostkey.pem
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/resources/hostkey.pem b/sshd-sftp/src/test/resources/hostkey.pem
deleted file mode 100644
index 18d68ac..0000000
--- a/sshd-sftp/src/test/resources/hostkey.pem
+++ /dev/null
@@ -1,30 +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.
-
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDdfIWeSV4o68dRrKSzFd/Bk51E65UTmmSrmW0O1ohtzi6HzsDP
-jXgCtlTt3FqTcfFfI92IlTr4JWqC9UK1QT1ZTeng0MkPQmv68hDANHbt5CpETZHj
-W5q4OOgWhVvj5IyOC2NZHtKlJBkdsMAa15ouOOJLzBvAvbqOR/yUROsEiQIDAQAB
-AoGBANG3JDW6NoP8rF/zXoeLgLCj+tfVUPSczhGFVrQkAk4mWfyRkhN0WlwHFOec
-K89MpkV1ij/XPVzU4MNbQ2yod1KiDylzvweYv+EaEhASCmYNs6LS03punml42SL9
-97tOmWfVJXxlQoLiY6jHPU97vTc65k8gL+gmmrpchsW0aqmZAkEA/c8zfmKvY37T
-cxcLLwzwsqqH7g2KZGTf9aRmx2ebdW+QKviJJhbdluDgl1TNNFj5vCLznFDRHiqJ
-wq0wkZ39cwJBAN9l5v3kdXj21UrurNPdlV0n2GZBt2vblooQC37XHF97r2zM7Ou+
-Lg6MyfJClyguhWL9dxnGbf3btQ0l3KDstxMCQCRaiEqjAfIjWVATzeNIXDWLHXso
-b1kf5cA+cwY+vdKdTy4IeUR+Y/DXdvPWDqpf0C11aCVMohdLCn5a5ikFUycCQDhV
-K/BuAallJNfmY7JxN87r00fF3ojWMJnT/fIYMFFrkQrwifXQWTDWE76BSDibsosJ
-u1TGksnm8zrDh2UVC/0CQFrHTiSl/3DHvWAbOJawGKg46cnlDcAhSyV8Frs8/dlP
-7YGG3eqkw++lsghqmFO6mRUTKsBmiiB2wgLGhL5pyYY=
------END RSA PRIVATE KEY-----

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/resources/log4j.properties b/sshd-sftp/src/test/resources/log4j.properties
deleted file mode 100644
index 9172dc9..0000000
--- a/sshd-sftp/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,38 +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.
-#
-#
-
-#
-# The logging properties used during tests..
-#
-log4j.rootLogger=INFO, stdout
-#log4j.logger.org.apache.sshd=TRACE
-#log4j.logger.org.apache.sshd.common.channel.Window=DEBUG
-
-# CONSOLE appender
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n
-
-# File appender
-log4j.appender.out=org.apache.log4j.FileAppender
-log4j.appender.out.layout=org.apache.log4j.PatternLayout
-log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n
-log4j.appender.out.file=target/servicemix-test.log
-log4j.appender.out.append=true


[12/15] mina-sshd git commit: [SSHD-378] Switch to nio FileSystem api for commands (scp and sftp subsystem)

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/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
new file mode 100644
index 0000000..38e6181
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.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.common.file.root;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+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;
+import java.nio.file.Paths;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * File system provider which provides a rooted file system.
+ * The file system only gives access to files under the root directory.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RootedFileSystemProvider extends FileSystemProvider {
+
+    final Map<Path, RootedFileSystem> fileSystems = new HashMap<>();
+
+    @Override
+    public String getScheme() {
+        return "root";
+    }
+
+    @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();
+                }
+            }
+            RootedFileSystem rootedFs = new RootedFileSystem(this, path, env);
+            this.fileSystems.put(localPath2, rootedFs);
+            return rootedFs;
+        }
+    }
+
+    @Override
+    public FileSystem getFileSystem(URI uri) {
+        synchronized (fileSystems) {
+            RootedFileSystem fileSystem = null;
+            try {
+                fileSystem = fileSystems.get(uriToPath(uri).toRealPath());
+            } catch (IOException ignore) {
+            }
+            if (fileSystem == null) {
+                throw new FileSystemNotFoundException();
+            }
+            return fileSystem;
+        }
+    }
+
+    @Override
+    public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
+        ensureDirectory(path);
+        return new RootedFileSystem(this, path, env);
+    }
+
+    protected Path uriToPath(URI uri) {
+        String scheme = uri.getScheme();
+        if ((scheme == null) || (!scheme.equalsIgnoreCase(getScheme()))) {
+            throw new IllegalArgumentException("URI scheme is not '" + getScheme() + "'");
+        }
+        try {
+            String root = uri.getRawSchemeSpecificPart();
+            int i = root.indexOf("!/");
+            if (i != -1) {
+                root = root.substring(0, i);
+            }
+            return Paths.get(new URI(root)).toAbsolutePath();
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e.getMessage(), e);
+        }
+    }
+
+    private boolean ensureDirectory(Path path) {
+        if (!Files.isDirectory(path)) {
+            throw new UnsupportedOperationException();
+        }
+        return true;
+    }
+
+    @Override
+    public Path getPath(URI uri) {
+        String str = uri.getSchemeSpecificPart();
+        int i = str.indexOf("!/");
+        if (i == -1) {
+            throw new IllegalArgumentException("URI: " + uri + " does not contain path info ex. root:file://foo/bar!/");
+        }
+        return getFileSystem(uri).getPath(str.substring(i + 1));
+    }
+
+    @Override
+    public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
+        Path r = unroot(path);
+        return provider(r).newInputStream(r, options);
+    }
+
+    @Override
+    public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
+        Path r = unroot(path);
+        return provider(r).newOutputStream(r, options);
+    }
+
+    @Override
+    public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        Path r = unroot(path);
+        return provider(r).newFileChannel(r, options, attrs);
+    }
+
+    @Override
+    public AsynchronousFileChannel newAsynchronousFileChannel(Path path, Set<? extends OpenOption> options, ExecutorService executor, FileAttribute<?>... attrs) throws IOException {
+        Path r = unroot(path);
+        return provider(r).newAsynchronousFileChannel(r, options, executor, attrs);
+    }
+
+    @Override
+    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        Path r = unroot(path);
+        return provider(r).newByteChannel(path, options, attrs);
+    }
+
+    @Override
+    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+        Path r = unroot(dir);
+        return provider(r).newDirectoryStream(r, filter);
+    }
+
+    @Override
+    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+        Path r = unroot(dir);
+        provider(r).createDirectory(r, attrs);
+    }
+
+    @Override
+    public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
+        Path l = unroot(link);
+        Path t = unroot(target, false);
+        provider(l).createSymbolicLink(l, t, attrs);
+    }
+
+    @Override
+    public void createLink(Path link, Path existing) throws IOException {
+        Path l = unroot(link);
+        Path e = unroot(existing);
+        provider(l).createLink(l, e);
+    }
+
+    @Override
+    public void delete(Path path) throws IOException {
+        Path r = unroot(path);
+        provider(r).delete(r);
+    }
+
+    @Override
+    public boolean deleteIfExists(Path path) throws IOException {
+        Path r = unroot(path);
+        return provider(r).deleteIfExists(r);
+    }
+
+    @Override
+    public Path readSymbolicLink(Path link) throws IOException {
+        Path r = unroot(link);
+        return root(link.getFileSystem(), provider(r).readSymbolicLink(r));
+
+    }
+
+    @Override
+    public void copy(Path source, Path target, CopyOption... options) throws IOException {
+        Path s = unroot(source);
+        Path t = unroot(target);
+        provider(s).copy(s, t, options);
+    }
+
+    @Override
+    public void move(Path source, Path target, CopyOption... options) throws IOException {
+        Path s = unroot(source);
+        Path t = unroot(target);
+        provider(s).move(s, t, options);
+    }
+
+    @Override
+    public boolean isSameFile(Path path, Path path2) throws IOException {
+        Path r = unroot(path);
+        Path r2 = unroot(path2);
+        return provider(r).isSameFile(r, r2);
+    }
+
+    @Override
+    public boolean isHidden(Path path) throws IOException {
+        Path r = unroot(path);
+        return provider(r).isHidden(r);
+    }
+
+    @Override
+    public FileStore getFileStore(Path path) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void checkAccess(Path path, AccessMode... modes) throws IOException {
+        Path r = unroot(path);
+        provider(r).checkAccess(r, modes);
+    }
+
+    @Override
+    public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+        Path r = unroot(path);
+        return provider(r).getFileAttributeView(r, type, options);
+    }
+
+    @Override
+    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+        Path r = unroot(path);
+        return provider(r).readAttributes(r, type, options);
+    }
+
+    @Override
+    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+        Path r = unroot(path);
+        return provider(r).readAttributes(r, attributes, options);
+    }
+
+    @Override
+    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+        Path r = unroot(path);
+        provider(r).setAttribute(r, attribute, value, options);
+    }
+
+    private FileSystemProvider provider(Path path) {
+        return path.getFileSystem().provider();
+    }
+
+    private Path root(FileSystem  fs, Path nat) {
+        RootedFileSystem rfs = (RootedFileSystem) fs;
+        if (nat.isAbsolute()) {
+            return rfs.getPath("/" + rfs.getRoot().relativize(nat).toString());
+        } else {
+            return rfs.getPath(nat.toString());
+        }
+    }
+
+    private Path unroot(Path path) {
+        return unroot(path, true);
+    }
+
+    private Path unroot(Path path, boolean absolute) {
+        if (path == null) {
+            throw new NullPointerException();
+        }
+        if (!(path instanceof RootedPath)) {
+            throw new ProviderMismatchException();
+        }
+        RootedPath p = (RootedPath) path;
+        if (absolute || p.isAbsolute()) {
+            String r = p.toAbsolutePath().toString();
+            return p.getFileSystem().getRoot().resolve(r.substring(1));
+        } else {
+            return p.getFileName().getRoot().getFileSystem().getPath(p.toString());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedPath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedPath.java b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedPath.java
new file mode 100644
index 0000000..e7be13e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedPath.java
@@ -0,0 +1,48 @@
+/*
+ * 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.file.root;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.LinkOption;
+
+import org.apache.sshd.common.file.util.BasePath;
+import org.apache.sshd.common.file.util.ImmutableList;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RootedPath extends BasePath<RootedPath, RootedFileSystem> {
+
+    public RootedPath(RootedFileSystem fileSystem, String root, ImmutableList<String> names) {
+        super(fileSystem, root, names);
+    }
+
+    public URI toUri() {
+        // TODO
+        return null;
+    }
+
+    public RootedPath toRealPath(LinkOption... options) throws IOException {
+        RootedPath absolute = toAbsolutePath();
+        fileSystem.provider().checkAccess(absolute);
+        return absolute;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemFactory.java
index 21d6cc7..2d74509 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemFactory.java
@@ -18,14 +18,15 @@
  */
 package org.apache.sshd.common.file.virtualfs;
 
-import java.util.HashMap;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Paths;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.sshd.common.Session;
 import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.nativefs.NativeFileSystemView;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
 
 /**
  * SSHd file system factory to reduce the visibility to a physical folder.
@@ -69,11 +70,9 @@ public class VirtualFileSystemFactory implements FileSystemFactory {
         return homeDir;
     }
 
-    public FileSystemView createFileSystemView(Session session) {
+    public FileSystem createFileSystem(Session session) throws IOException {
         String dir = computeRootDir(session.getUsername());
-        Map<String, String> roots = new HashMap<String, String>();
-        roots.put("/", dir);
-        return new NativeFileSystemView(session.getUsername(), roots, "/", '/', false);
+        return new RootedFileSystemProvider().newFileSystem(Paths.get(dir), null);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 517cb36..9827507 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -20,17 +20,26 @@ package org.apache.sshd.common.scp;
 
 import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
+import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+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.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
 import org.apache.sshd.common.util.DirectoryScanner;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,22 +79,22 @@ public class ScpHelper {
     public static final int S_IWOTH =  0000002;
     public static final int S_IXOTH =  0000001;
 
-    protected final FileSystemView root;
+    protected final FileSystem fileSystem;
     protected final InputStream in;
     protected final OutputStream out;
 
-    public ScpHelper(InputStream in, OutputStream out, FileSystemView root) {
+    public ScpHelper(InputStream in, OutputStream out, FileSystem fileSystem) {
         this.in = in;
         this.out = out;
-        this.root = root;
+        this.fileSystem = fileSystem;
     }
 
-    public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
+    public void receive(Path path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
         if (shouldBeDir) {
-            if (!path.doesExist()) {
+            if (!Files.exists(path)) {
                 throw new SshException("Target directory " + path.toString() + " does not exists");
             }
-            if (!path.isDirectory()) {
+            if (!Files.isDirectory(path)) {
                 throw new SshException("Target directory " + path.toString() + " is not a directory");
             }
         }
@@ -136,7 +145,7 @@ public class ScpHelper {
     }
 
 
-    public void receiveDir(String header, SshFile path, long[] time, boolean preserve, int bufferSize) throws IOException {
+    public void receiveDir(String header, Path path, long[] time, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Receiving directory {}", path);
         }
@@ -151,26 +160,26 @@ public class ScpHelper {
         if (length != 0) {
             throw new IOException("Expected 0 length for directory but got " + length);
         }
-        SshFile file;
-        if (path.doesExist() && path.isDirectory()) {
-            file = root.getFile(path, name);
-        } else if (!path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory()) {
+        Path file;
+        if (Files.exists(path) && Files.isDirectory(path)) {
+            file = path.resolve(name);
+        } else if (!Files.exists(path) && Files.exists(path.getParent()) && Files.isDirectory(path.getParent())) {
             file = path;
         } else {
             throw new IOException("Can not write to " + path);
         }
-        if (!(file.doesExist() && file.isDirectory()) && !file.mkdir()) {
-            throw new IOException("Could not create directory " + file);
+        if (!(Files.exists(file) && Files.isDirectory(file))) {
+            Files.createDirectory(file);
         }
 
         if (preserve) {
-            Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
-            attrs.put(SshFile.Attribute.Permissions, fromOctalPerms(perms));
+            setOctalPerms(file, perms);
             if (time != null) {
-                attrs.put(SshFile.Attribute.LastModifiedTime, time[0]);
-                attrs.put(SshFile.Attribute.LastAccessTime, time[1]);
+                Files.getFileAttributeView(file, BasicFileAttributeView.class)
+                        .setTimes(FileTime.from(time[0], TimeUnit.SECONDS),
+                                FileTime.from(time[1], TimeUnit.SECONDS),
+                                null);
             }
-            file.setAttributes(attrs);
         }
 
         ack();
@@ -198,7 +207,7 @@ public class ScpHelper {
 
     }
 
-    public void receiveFile(String header, SshFile path, long[] time, boolean preserve, int bufferSize) throws IOException {
+    public void receiveFile(String header, Path path, long[] time, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Receiving file {}", path);
         }
@@ -211,7 +220,7 @@ public class ScpHelper {
         }
 
         String perms = header.substring(1, 5);
-        long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6)));
+        final long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6)));
         String name = header.substring(header.indexOf(' ', 6) + 1);
         if (length < 0L) { // TODO consider throwing an exception...
             log.warn("receiveFile(" + path + ") bad length in header: " + header);
@@ -225,50 +234,78 @@ public class ScpHelper {
         }
 
 
-        SshFile file;
-        if (path.doesExist() && path.isDirectory()) {
-            file = root.getFile(path, name);
-        } else if (path.doesExist() && path.isFile()) {
+        Path file;
+        if (Files.exists(path) && Files.isDirectory(path)) {
+            file = path.resolve(name);
+        } else if (Files.exists(path) && Files.isRegularFile(path)) {
             file = path;
-        } else if (!path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory()) {
+        } else if (!Files.exists(path) && Files.exists(path.getParent()) && Files.isDirectory(path.getParent())) {
             file = path;
         } else {
             throw new IOException("Can not write to " + path);
         }
-        if (file.doesExist() && file.isDirectory()) {
+        if (Files.exists(file) && Files.isDirectory(file)) {
             throw new IOException("File is a directory: " + file);
-        } else if (file.doesExist() && !file.isWritable()) {
+        } else if (Files.exists(file) && !Files.isWritable(file)) {
             throw new IOException("Can not write to file: " + file);
         }
-        if (file.doesExist()) {
-            file.truncate();
-        }
-        OutputStream os = file.createOutputStream(0);
-        try {
-            ack();
+        InputStream is = new FilterInputStream(in) {
+            long remaining = length;
+            @Override
+            public int read() throws IOException {
+                if (remaining > 0) {
+                    remaining--;
+                    return super.read();
+                } else{
+                    return -1;
+                }
+            }
+
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException {
+                int nb = len;
+                if (nb > remaining) {
+                    nb = (int) remaining;
+                }
+                if (nb > 0) {
+                    int read = super.read(b, off, nb);
+                    remaining -= read;
+                    return read;
+                } else {
+                    return -1;
+                }
+            }
 
-            byte[] buffer = new byte[bufSize];
-            while (length > 0) {
-                int len = (int) Math.min(length, buffer.length);
-                len = in.read(buffer, 0, len);
-                if (len <= 0) {
-                    throw new IOException("End of stream reached");
+            @Override
+            public long skip(long n) throws IOException {
+                long skipped = super.skip(n);
+                remaining -= skipped;
+                return skipped;
+            }
+
+            @Override
+            public int available() throws IOException {
+                int av = super.available();
+                if (av > remaining) {
+                    return (int) remaining;
+                } else {
+                    return av;
                 }
-                os.write(buffer, 0, len);
-                length -= len;
             }
-        } finally {
-            os.close();
+        };
+        try (OutputStream os = Files.newOutputStream(file)) {
+            ack();
+            copy(is, os, bufSize);
         }
 
         if (preserve) {
-            Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
-            attrs.put(SshFile.Attribute.Permissions, fromOctalPerms(perms));
+            setOctalPerms(file, perms);
             if (time != null) {
-                attrs.put(SshFile.Attribute.LastModifiedTime, time[0]);
-                attrs.put(SshFile.Attribute.LastAccessTime, time[1]);
+                Files.getFileAttributeView(file, BasicFileAttributeView.class)
+                        .setTimes(FileTime.from(time[0], TimeUnit.SECONDS),
+                                FileTime.from(time[1], TimeUnit.SECONDS),
+                                null);
             }
-            file.setAttributes(attrs);
         }
 
         ack();
@@ -309,10 +346,10 @@ public class ScpHelper {
                 }
                 String[] included = new DirectoryScanner(basedir, pattern).scan();
                 for (String path : included) {
-                    SshFile file = root.getFile(basedir + "/" + path);
-                    if (file.isFile()) {
+                    Path file = fileSystem.getPath(basedir + "/" + path);
+                    if (Files.isRegularFile(file)) {
                         sendFile(file, preserve, bufferSize);
-                    } else if (file.isDirectory()) {
+                    } else if (Files.isDirectory(file)) {
                         if (!recursive) {
                             out.write(ScpHelper.WARNING);
                             out.write((path + " not a regular file\n").getBytes());
@@ -331,13 +368,13 @@ public class ScpHelper {
                     basedir = pattern.substring(0, lastSep);
                     pattern = pattern.substring(lastSep + 1);
                 }
-                SshFile file = root.getFile(basedir + "/" + pattern);
-                if (!file.doesExist()) {
+                Path file = fileSystem.getPath(basedir + "/" + pattern);
+                if (!Files.exists(file)) {
                     throw new IOException(file + ": no such file or directory");
                 }
-                if (file.isFile()) {
+                if (Files.isRegularFile(file)) {
                     sendFile(file, preserve, bufferSize);
-                } else if (file.isDirectory()) {
+                } else if (Files.isDirectory(file)) {
                     if (!recursive) {
                         throw new IOException(file + " not a regular file");
                     } else {
@@ -350,7 +387,7 @@ public class ScpHelper {
         }
     }
 
-    public void sendFile(SshFile path, boolean preserve, int bufferSize) throws IOException {
+    public void sendFile(Path path, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Sending file {}", path);
         }
@@ -359,15 +396,15 @@ public class ScpHelper {
             throw new IOException("sendFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_SEND_BUFFER_SIZE + ")");
         }
 
-        Map<SshFile.Attribute,Object> attrs =  path.getAttributes(true);
+        BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
         if (preserve) {
             StringBuffer buf = new StringBuffer();
             buf.append("T");
-            buf.append(attrs.get(SshFile.Attribute.LastModifiedTime));
+            buf.append(basic.lastModifiedTime().to(TimeUnit.SECONDS));
             buf.append(" ");
             buf.append("0");
             buf.append(" ");
-            buf.append(attrs.get(SshFile.Attribute.LastAccessTime));
+            buf.append(basic.lastAccessTime().to(TimeUnit.SECONDS));
             buf.append(" ");
             buf.append("0");
             buf.append("\n");
@@ -378,17 +415,17 @@ public class ScpHelper {
 
         StringBuffer buf = new StringBuffer();
         buf.append("C");
-        buf.append(preserve ? toOctalPerms((EnumSet<SshFile.Permission>) attrs.get(SshFile.Attribute.Permissions)) : "0644");
+        buf.append(preserve ? getOctalPerms(path) : "0644");
         buf.append(" ");
-        buf.append(attrs.get(SshFile.Attribute.Size)); // length
+        buf.append(basic.size()); // length
         buf.append(" ");
-        buf.append(path.getName());
+        buf.append(path.getFileName().toString());
         buf.append("\n");
         out.write(buf.toString().getBytes());
         out.flush();
         readAck(false);
 
-        long fileSize = path.getSize();
+        long fileSize = Files.size(path);
         if (fileSize < 0L) { // TODO consider throwing an exception...
             log.warn("sendFile(" + path + ") bad file size: " + fileSize);
         }
@@ -400,36 +437,27 @@ public class ScpHelper {
             bufSize = MIN_SEND_BUFFER_SIZE;
         }
 
-        InputStream is = path.createInputStream(0);
-        try {
-            byte[] buffer = new byte[bufSize];
-            for (;;) {
-                int len = is.read(buffer, 0, buffer.length);
-                if (len == -1) {
-                    break;
-                }
-                out.write(buffer, 0, len);
-            }
-        } finally {
-            is.close();
+        // TODO: use bufSize
+        try (InputStream in = Files.newInputStream(path)) {
+            copy(in, out, bufSize);
         }
         ack();
         readAck(false);
     }
 
-    public void sendDir(SshFile path, boolean preserve, int bufferSize) throws IOException {
+    public void sendDir(Path path, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Sending directory {}", path);
         }
-        Map<SshFile.Attribute,Object> attrs =  path.getAttributes(true);
+        BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
         if (preserve) {
             StringBuffer buf = new StringBuffer();
             buf.append("T");
-            buf.append(attrs.get(SshFile.Attribute.LastModifiedTime));
+            buf.append(basic.lastModifiedTime().to(TimeUnit.SECONDS));
             buf.append(" ");
             buf.append("0");
             buf.append(" ");
-            buf.append(attrs.get(SshFile.Attribute.LastAccessTime));
+            buf.append(basic.lastAccessTime().to(TimeUnit.SECONDS));
             buf.append(" ");
             buf.append("0");
             buf.append("\n");
@@ -440,21 +468,23 @@ public class ScpHelper {
 
         StringBuffer buf = new StringBuffer();
         buf.append("D");
-        buf.append(preserve ? toOctalPerms((EnumSet<SshFile.Permission>) attrs.get(SshFile.Attribute.Permissions)) : "0755");
+        buf.append(preserve ? getOctalPerms(path) : "0755");
         buf.append(" ");
         buf.append("0"); // length
         buf.append(" ");
-        buf.append(path.getName());
+        buf.append(path.getFileName().toString());
         buf.append("\n");
         out.write(buf.toString().getBytes());
         out.flush();
         readAck(false);
 
-        for (SshFile child : path.listSshFiles()) {
-            if (child.isFile()) {
-                sendFile(child, preserve, bufferSize);
-            } else if (child.isDirectory()) {
-                sendDir(child, preserve, bufferSize);
+        try (DirectoryStream<Path> children = Files.newDirectoryStream(path)) {
+            for (Path child : children) {
+                if (Files.isRegularFile(child)) {
+                    sendFile(child, preserve, bufferSize);
+                } else if (Files.isDirectory(child)) {
+                    sendDir(child, preserve, bufferSize);
+                }
             }
         }
 
@@ -468,55 +498,90 @@ public class ScpHelper {
         return new long[] { Long.parseLong(numbers[0]), Long.parseLong(numbers[2]) };
     }
 
-    public static String toOctalPerms(EnumSet<SshFile.Permission> perms) {
+    public static String getOctalPerms(Path path) throws IOException {
         int pf = 0;
-        for (SshFile.Permission p : perms) {
-            switch (p) {
-                case UserRead:      pf |= S_IRUSR; break;
-                case UserWrite:     pf |= S_IWUSR; break;
-                case UserExecute:   pf |= S_IXUSR; break;
-                case GroupRead:     pf |= S_IRGRP; break;
-                case GroupWrite:    pf |= S_IWGRP; break;
-                case GroupExecute:  pf |= S_IXGRP; break;
-                case OthersRead:    pf |= S_IROTH; break;
-                case OthersWrite:   pf |= S_IWOTH; break;
-                case OthersExecute: pf |= S_IXOTH; break;
+        if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(path);
+            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;
+                }
+            }
+        } else {
+            if (Files.isReadable(path)) {
+                pf |= S_IRUSR | S_IRGRP | S_IROTH;
+            }
+            if (Files.isWritable(path)) {
+                pf |= S_IWUSR | S_IWGRP | S_IWOTH;
+            }
+            if (Files.isExecutable(path)) {
+                pf |= S_IXUSR | S_IXGRP | S_IXOTH;
             }
         }
         return String.format("%04o", pf);
     }
 
-    public static EnumSet<SshFile.Permission> fromOctalPerms(String str) {
+    public static void setOctalPerms(Path path, String str) throws IOException {
         int perms = Integer.parseInt(str, 8);
-        EnumSet<SshFile.Permission> p = EnumSet.noneOf(SshFile.Permission.class);
+        EnumSet<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
         if ((perms & S_IRUSR) != 0) {
-            p.add(SshFile.Permission.UserRead);
+            p.add(PosixFilePermission.OWNER_READ);
         }
         if ((perms & S_IWUSR) != 0) {
-            p.add(SshFile.Permission.UserWrite);
+            p.add(PosixFilePermission.OWNER_WRITE);
         }
         if ((perms & S_IXUSR) != 0) {
-            p.add(SshFile.Permission.UserExecute);
+            p.add(PosixFilePermission.OWNER_EXECUTE);
         }
         if ((perms & S_IRGRP) != 0) {
-            p.add(SshFile.Permission.GroupRead);
+            p.add(PosixFilePermission.GROUP_READ);
         }
         if ((perms & S_IWGRP) != 0) {
-            p.add(SshFile.Permission.GroupWrite);
+            p.add(PosixFilePermission.GROUP_WRITE);
         }
         if ((perms & S_IXGRP) != 0) {
-            p.add(SshFile.Permission.GroupExecute);
+            p.add(PosixFilePermission.GROUP_EXECUTE);
         }
         if ((perms & S_IROTH) != 0) {
-            p.add(SshFile.Permission.OthersRead);
+            p.add(PosixFilePermission.OTHERS_READ);
         }
         if ((perms & S_IWOTH) != 0) {
-            p.add(SshFile.Permission.OthersWrite);
+            p.add(PosixFilePermission.OTHERS_WRITE);
         }
         if ((perms & S_IXOTH) != 0) {
-            p.add(SshFile.Permission.OthersExecute);
+            p.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+        if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+            Files.setPosixFilePermissions(path, p);
+        } else {
+            log.warn("Unable to set file permissions because the underlying file system does not support posix permissions");
         }
-        return p;
     }
 
     public void ack() throws IOException {
@@ -545,4 +610,17 @@ public class ScpHelper {
         return c;
     }
 
+    private static long copy(InputStream source, OutputStream sink, int bufferSize)
+            throws IOException
+    {
+        long nread = 0L;
+        byte[] buf = new byte[bufferSize];
+        int n;
+        while ((n = source.read(buf)) > 0) {
+            sink.write(buf, 0, n);
+            nread += n;
+        }
+        return nread;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index ab10ef4..ac4bab2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -492,7 +492,7 @@ public class ChannelSession extends AbstractServerChannel {
         // If the shell wants to be aware of the file system, let's do that too
         if (command instanceof FileSystemAware) {
             FileSystemFactory factory = ((ServerSession) session).getFactoryManager().getFileSystemFactory();
-            ((FileSystemAware) command).setFileSystemView(factory.createFileSystemView(session));
+            ((FileSystemAware) command).setFileSystem(factory.createFileSystem(session));
         }
         // If the shell wants to use non-blocking io
         if (command instanceof AsyncCommand) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
index 68dc9ef..d68e0b9 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
@@ -25,9 +25,9 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.nio.file.FileSystem;
 
 import org.apache.sshd.common.file.FileSystemAware;
-import org.apache.sshd.common.file.FileSystemView;
 import org.apache.sshd.common.scp.ScpHelper;
 import org.apache.sshd.common.util.ThreadUtils;
 import org.apache.sshd.server.Command;
@@ -53,7 +53,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
     protected boolean optF;
     protected boolean optD;
     protected boolean optP; // TODO: handle modification times
-    protected FileSystemView root;
+    protected FileSystem fileSystem;
     protected String path;
     protected InputStream in;
     protected OutputStream out;
@@ -237,8 +237,8 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
         this.callback = callback;
     }
 
-    public void setFileSystemView(FileSystemView view) {
-        this.root = view;
+    public void setFileSystem(FileSystem fs) {
+        this.fileSystem = fs;
     }
 
     public void start(Environment env) throws IOException {
@@ -274,15 +274,23 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
         }
 
         executors = null;
+
+        try {
+            fileSystem.close();
+        } catch (UnsupportedOperationException e) {
+            // Ignore
+        } catch (IOException e) {
+            log.debug("Error closing FileSystem", e);
+        }
     }
 
     public void run() {
         int exitValue = ScpHelper.OK;
         String exitMessage = null;
-        ScpHelper helper = new ScpHelper(in, out, root);
+        ScpHelper helper = new ScpHelper(in, out, fileSystem);
         try {
             if (optT) {
-                helper.receive(root.getFile(path), optR, optD, optP, receiveBufferSize);
+                helper.receive(fileSystem.getPath(path), optR, optD, optP, receiveBufferSize);
             } else if (optF) {
                 helper.send(Collections.singletonList(path), optR, optP, sendBufferSize);
             } else {


[03/15] mina-sshd git commit: Upgrade to 1.0.0-SNAPSHOT and remove unsupported modules

Posted by gn...@apache.org.
Upgrade to 1.0.0-SNAPSHOT and remove unsupported modules

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

Branch: refs/heads/master
Commit: 80d8a51afe356e3ebc91676a1ccba4fd845a24b4
Parents: 964e768
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Feb 23 16:20:07 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:20:07 2015 +0100

----------------------------------------------------------------------
 assembly/pom.xml                                |   4 +-
 pom.xml                                         |   2 +-
 sshd-core/pom.xml                               |   4 +-
 sshd-git/pom.xml                                |   4 +-
 sshd-pam/pom.xml                                |  52 --
 .../server/pam/PAMPasswordAuthenticator.java    |  58 --
 sshd-sftp/pom.xml                               |  82 --
 .../main/java/org/apache/sshd/sftp/Handle.java  |  36 -
 .../main/java/org/apache/sshd/sftp/Reply.java   |  43 -
 .../main/java/org/apache/sshd/sftp/Request.java |  50 --
 .../java/org/apache/sshd/sftp/SftpSession.java  |  36 -
 .../main/java/org/apache/sshd/sftp/Sftplet.java |  74 --
 .../org/apache/sshd/sftp/reply/BaseReply.java   |  53 --
 .../apache/sshd/sftp/reply/FileAttributes.java  | 166 ----
 .../sshd/sftp/reply/SshFxpAttrsReply.java       |  66 --
 .../apache/sshd/sftp/reply/SshFxpDataReply.java |  96 ---
 .../sshd/sftp/reply/SshFxpHandleReply.java      |  66 --
 .../apache/sshd/sftp/reply/SshFxpNameReply.java | 150 ----
 .../sshd/sftp/reply/SshFxpStatusReply.java      | 164 ----
 .../sshd/sftp/reply/SshFxpVersionReply.java     |  61 --
 .../apache/sshd/sftp/request/BaseRequest.java   |  58 --
 .../sshd/sftp/request/SshFxpCloseRequest.java   |  83 --
 .../sftp/request/SshFxpFsetstatRequest.java     |  52 --
 .../sshd/sftp/request/SshFxpFstatRequest.java   |  74 --
 .../sshd/sftp/request/SshFxpInitRequest.java    |  51 --
 .../sshd/sftp/request/SshFxpLstatRequest.java   |  72 --
 .../sshd/sftp/request/SshFxpMkdirRequest.java   |  65 --
 .../sshd/sftp/request/SshFxpOpenRequest.java    |  99 ---
 .../sshd/sftp/request/SshFxpOpendirRequest.java |  64 --
 .../sshd/sftp/request/SshFxpReadRequest.java    | 109 ---
 .../sshd/sftp/request/SshFxpReaddirRequest.java |  74 --
 .../sftp/request/SshFxpRealpathRequest.java     |  78 --
 .../sshd/sftp/request/SshFxpRemoveRequest.java  |  64 --
 .../sshd/sftp/request/SshFxpRenameRequest.java  |  76 --
 .../sshd/sftp/request/SshFxpRmdirRequest.java   |  64 --
 .../sshd/sftp/request/SshFxpSetstatRequest.java |  52 --
 .../sshd/sftp/request/SshFxpStatRequest.java    |  71 --
 .../sshd/sftp/request/SshFxpWriteRequest.java   | 110 ---
 .../sshd/sftp/request/UnsupportedRequest.java   |  56 --
 .../apache/sshd/sftp/subsystem/BaseHandle.java  |  48 --
 .../sftp/subsystem/DefaultSftpletContainer.java |  90 ---
 .../sshd/sftp/subsystem/DirectoryHandle.java    |  65 --
 .../apache/sshd/sftp/subsystem/FileHandle.java  |  86 --
 .../apache/sshd/sftp/subsystem/Serializer.java  | 535 -------------
 .../sshd/sftp/subsystem/SftpConstants.java      | 305 -------
 .../sshd/sftp/subsystem/SftpSubsystem.java      | 786 -------------------
 .../java/org/apache/sshd/sftp/SftpTest.java     | 263 -------
 .../sftp/util/BogusPasswordAuthenticator.java   |  34 -
 .../apache/sshd/sftp/util/EchoShellFactory.java | 111 ---
 .../org/apache/sshd/sftp/util/JSchLogger.java   |  56 --
 .../apache/sshd/sftp/util/SimpleUserInfo.java   |  63 --
 .../java/org/apache/sshd/sftp/util/Utils.java   |  58 --
 sshd-sftp/src/test/resources/hostkey.pem        |  30 -
 sshd-sftp/src/test/resources/log4j.properties   |  38 -
 54 files changed, 7 insertions(+), 5200 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/assembly/pom.xml
----------------------------------------------------------------------
diff --git a/assembly/pom.xml b/assembly/pom.xml
index 7ebd56e..aa24ba0 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -24,12 +24,12 @@
     <parent>
         <groupId>org.apache.sshd</groupId>
         <artifactId>sshd</artifactId>
-        <version>0.13.1-SNAPSHOT</version>
+        <version>1.0.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.apache.sshd</groupId>
     <artifactId>apache-sshd</artifactId>
-    <version>0.13.1-SNAPSHOT</version>
+    <version>1.0.0-SNAPSHOT</version>
     <name>Apache Mina SSHD :: Assembly</name>
     <packaging>pom</packaging>
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ca326d2..063f47e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <groupId>org.apache.sshd</groupId>
     <artifactId>sshd</artifactId>
-    <version>0.13.1-SNAPSHOT</version>
+    <version>1.0.0-SNAPSHOT</version>
     <name>Apache Mina SSHD</name>
     <packaging>pom</packaging>
     <inceptionYear>2008</inceptionYear>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-core/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-core/pom.xml b/sshd-core/pom.xml
index 15753b7..d8ff2a1 100644
--- a/sshd-core/pom.xml
+++ b/sshd-core/pom.xml
@@ -24,11 +24,11 @@
     <parent>
         <groupId>org.apache.sshd</groupId>
         <artifactId>sshd</artifactId>
-        <version>0.13.1-SNAPSHOT</version>
+        <version>1.0.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>sshd-core</artifactId>
-    <version>0.13.1-SNAPSHOT</version>
+    <version>1.0.0-SNAPSHOT</version>
     <name>Apache Mina SSHD :: Core</name>
     <packaging>jar</packaging>
     <inceptionYear>2008</inceptionYear>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-git/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-git/pom.xml b/sshd-git/pom.xml
index 8cdebdd..c1c1f5b 100644
--- a/sshd-git/pom.xml
+++ b/sshd-git/pom.xml
@@ -24,11 +24,11 @@
     <parent>
         <groupId>org.apache.sshd</groupId>
         <artifactId>sshd</artifactId>
-        <version>0.13.1-SNAPSHOT</version>
+        <version>1.0.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>sshd-git</artifactId>
-    <version>0.13.1-SNAPSHOT</version>
+    <version>1.0.0-SNAPSHOT</version>
     <name>Apache Mina SSHD :: Git</name>
     <packaging>jar</packaging>
     <inceptionYear>2008</inceptionYear>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-pam/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-pam/pom.xml b/sshd-pam/pom.xml
deleted file mode 100644
index 15ff731..0000000
--- a/sshd-pam/pom.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
-
-    <!--
-
-        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.
-    -->
-
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.apache.sshd</groupId>
-        <artifactId>sshd</artifactId>
-        <version>0.13.0-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>sshd-pam</artifactId>
-    <version>0.13.0-SNAPSHOT</version>
-    <name>Apache Mina SSHD :: PAM</name>
-    <!--
-    <packaging>bundle</packaging>
-    -->
-
-    <properties>
-        <projectRoot>${basedir}/..</projectRoot>
-    </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>net.sf.jpam</groupId>
-            <artifactId>jpam</artifactId>
-        </dependency>
-    </dependencies>
-
-</project>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-pam/src/main/java/org/apache/sshd/server/pam/PAMPasswordAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-pam/src/main/java/org/apache/sshd/server/pam/PAMPasswordAuthenticator.java b/sshd-pam/src/main/java/org/apache/sshd/server/pam/PAMPasswordAuthenticator.java
deleted file mode 100644
index a63c6a1..0000000
--- a/sshd-pam/src/main/java/org/apache/sshd/server/pam/PAMPasswordAuthenticator.java
+++ /dev/null
@@ -1,58 +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.pam;
-
-import net.sf.jpam.Pam;
-import net.sf.jpam.PamReturnValue;
-import org.apache.sshd.server.PasswordAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A password authenticator using PAM (Pluggable Authentication Module).
- * Such an authenticator can be used to integrate into an Unix operating
- * system. 
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class PAMPasswordAuthenticator implements PasswordAuthenticator {
-
-    private static final Logger LOG = LoggerFactory.getLogger(PAMPasswordAuthenticator.class);
-
-    private String service = "sshd";
-
-    public String getService() {
-        return service;
-    }
-
-    public void setService(String service) {
-        this.service = service;
-    }
-
-    public boolean authenticate(String username, String password, ServerSession session) {
-        LOG.info("Authenticating user {} using PAM", username);
-        PamReturnValue val = new Pam(service).authenticate(username, password);
-        LOG.info("Result: {}", val);
-        if (PamReturnValue.PAM_SUCCESS.equals(val)) {
-            return true;
-        }
-        return false;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-sftp/pom.xml b/sshd-sftp/pom.xml
deleted file mode 100644
index 50cb5b4..0000000
--- a/sshd-sftp/pom.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
-
-    <!--
-
-        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.
-    -->
-
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.apache.sshd</groupId>
-        <artifactId>sshd</artifactId>
-        <version>0.13.0-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>sshd-sftp</artifactId>
-    <version>0.13.0-SNAPSHOT</version>
-    <name>Apache Mina SSHD :: SFTP</name>
-    <packaging>jar</packaging>
-    <inceptionYear>2008</inceptionYear>
-
-    <properties>
-        <projectRoot>${basedir}/..</projectRoot>
-    </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-log4j12</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.jcraft</groupId>
-            <artifactId>jsch</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcpg-jdk15on</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcpkix-jdk15on</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <resources>
-            <resource>
-                <directory>src/main/filtered-resources</directory>
-                <filtering>true</filtering>
-            </resource>
-        </resources>
-    </build>
-
-</project>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Handle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Handle.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/Handle.java
deleted file mode 100644
index c2a726d..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Handle.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.sftp;
-
-import org.apache.sshd.common.file.SshFile;
-
-import java.io.Closeable;
-
-/**
- * A sftp handle.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface Handle extends Closeable {
-
-    String getId();
-
-    SshFile getFile();
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Reply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Reply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/Reply.java
deleted file mode 100644
index 98f9c87..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Reply.java
+++ /dev/null
@@ -1,43 +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.sftp;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Common ssh reply interface.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface Reply {
-
-    /**
-     * Returns the reply message type.
-     *
-     * @return the message type
-     */
-    SftpConstants.Type getMessage();
-
-	/**
-	 * Returns the name of the reply code.
-	 * 
-	 * @return The name of the reply code.
-	 */
-	String getName();
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Request.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Request.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/Request.java
deleted file mode 100644
index 7803a8f..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Request.java
+++ /dev/null
@@ -1,50 +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.sftp;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Common ssh request interface.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface Request {
-
-    /**
-     * Returns the request id.
-     *
-     * @return The request id.
-     */
-    int getId();
-
-    /**
-     * Returns the message type.
-     *
-     * @return The message type.
-     */
-    SftpConstants.Type getMessage();
-
-    /**
-     * Returns the request name.
-     *
-     * @return The request name.
-     */
-    String getName();
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpSession.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpSession.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpSession.java
deleted file mode 100644
index a6956db..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpSession.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.sftp;
-
-import org.apache.sshd.common.Session;
-import org.apache.sshd.common.file.SshFile;
-
-public interface SftpSession {
-
-    int getVersion();
-
-    Session getSession();
-
-    Handle getHandle(String id);
-
-    Handle createFileHandle(SshFile file, int flags);
-
-    Handle createDirectoryHandle(SshFile file);
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Sftplet.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Sftplet.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/Sftplet.java
deleted file mode 100644
index 6cd4542..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/Sftplet.java
+++ /dev/null
@@ -1,74 +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.sftp;
-
-import org.apache.sshd.common.Session;
-
-import java.io.IOException;
-
-
-
-/**
- * Similar to org.apache.ftpserver.ftplet.Ftplet.
- * For custom command handling. For example adopting monoring tools, individual loggings, event processing,
- * customizing command processing.
- * There should be a ServerSession interface to avoid an implementation dependency.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface Sftplet {
-	/**
-	 * Client connect notification method.
-	 * 
-	 * @param session The according session.
-	 */
-	void onConnect(SftpSession session);
-
-	/**
-	 * Client disconnect notification method.
-	 * 
-	 * @param session The according session.
-	 */
-	void onDisconnect(SftpSession session);
-
-	/**
-	 * Called before the server invoke the command.
-	 * 
-	 * @param session     The according session.
-	 * @param sftpRequest The sftp request.
-	 * 
-	 * @return If null, the standard processing goes on.
-	 *         If not null, there will be no further processing and this reply will be returned to client.
-	 */
-	Reply beforeCommand(SftpSession session, Request sftpRequest);
-
-	/**
-	 * Called after the server as invoked the command.
-	 * 
-	 * @param session     The according session.
-	 * @param sftpRequest The sftp request.
-	 * @param sftpReply   The sftp reply.
-	 * 
-	 * @return If null, default sftp reply will be used.
-	 *         If not null, there will be no further processing and this reply will be returned to client.
-	 *         
-	 * @throws IOException If an error occured. 
-	 */
-	Reply afterCommand(SftpSession session, Request sftpRequest, Reply sftpReply) throws IOException;
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/BaseReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/BaseReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/BaseReply.java
deleted file mode 100644
index 3cbea00..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/BaseReply.java
+++ /dev/null
@@ -1,53 +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.sftp.reply;
-
-import org.apache.sshd.sftp.Reply;
-
-public abstract class BaseReply implements Reply {
-
-    private final int id;
-
-    /**
-     * Creates a Request instance.
-     *
-     * @param id The request id.
-     */
-    public BaseReply(final int id) {
-        this.id = id;
-    }
-
-    /**
-     * Returns the request id.
-     *
-     * @return The request id.
-     */
-    public int getId() {
-        return id;
-    }
-
-    /**
-     * Returns the request name.
-     *
-     * @return The request name.
-     */
-    public String getName() {
-        return getMessage().toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/FileAttributes.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/FileAttributes.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/FileAttributes.java
deleted file mode 100644
index 16c0f52..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/FileAttributes.java
+++ /dev/null
@@ -1,166 +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.sftp.reply;
-
-import org.apache.sshd.common.file.SshFile;
-
-import static org.apache.sshd.sftp.subsystem.SftpConstants.*;
-
-/**
- * Data container for file attributes in relies.
- * TODO: implement
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class FileAttributes {
-
-    int flags = 0;
-    byte type;
-    long size;
-    long allocationSize;
-    String owner;
-    String group;
-    int permissions;
-    long accessTime;
-    int accessTimeNanos;
-    long modifyTime;
-    int modifyTimeNanos;
-    long createTime;
-    int createTimeNanos;
-    String acl;
-    int attribBits;
-    int attribBitsValid;
-    byte textHint;
-    String mimeType;
-    int linkCount;
-    String untranslatedName;
-    int extended;
-
-    public FileAttributes() {
-    }
-
-    public FileAttributes(SshFile file, int flags) {
-        // Type
-        if (file.isFile()) {
-            setType((byte) SSH_FILEXFER_TYPE_REGULAR);
-        } else if (file.isDirectory()) {
-            setType((byte) SSH_FILEXFER_TYPE_DIRECTORY);
-        } else {
-            setType((byte) SSH_FILEXFER_TYPE_UNKNOWN);
-        }
-        // Size
-        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-            setSize(file.getSize());
-        }
-        // Permissions
-        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-            setPermissions((file.isReadable() ? S_IRUSR : 0) | (file.isWritable() ? S_IWUSR : 0) | (file.isExecutable() ? S_IXUSR : 0));
-        }
-        // Times
-        if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
-            setAccessTime(file.getLastModified() / 1000);
-        }
-        if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
-            setCreateTime(file.getLastModified() / 1000);
-        }
-        if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
-            setModifyTime(file.getLastModified() / 1000);
-        }
-    }
-
-    public int getFlags() {
-        return flags;
-    }
-
-    public byte getType() {
-        return type;
-    }
-
-    public void setType(byte type) {
-        this.type = type;
-    }
-
-    public long getSize() {
-        return size;
-    }
-
-    public void setSize(long size) {
-        this.flags |= SSH_FILEXFER_ATTR_SIZE;
-        this.size = size;
-    }
-
-    public long getAllocationSize() {
-        return allocationSize;
-    }
-
-    public void setAllocationSize(long allocationSize) {
-        this.flags |= SSH_FILEXFER_ATTR_ALLOCATION_SIZE;
-        this.allocationSize = allocationSize;
-    }
-
-    public String getOwner() {
-        return owner;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public void setOwnerGroup(String owner, String group) {
-        this.flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
-        this.owner = owner;
-        this.group = group;
-    }
-
-    public int getPermissions() {
-        return permissions;
-    }
-
-    public void setPermissions(int permissions) {
-        this.flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
-        this.permissions = permissions;
-    }
-
-    public long getAccessTime() {
-        return accessTime;
-    }
-
-    public void setAccessTime(long accessTime) {
-        this.flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
-        this.accessTime = accessTime;
-    }
-
-    public long getModifyTime() {
-        return modifyTime;
-    }
-
-    public void setModifyTime(long modifyTime) {
-        this.flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
-        this.modifyTime = modifyTime;
-    }
-
-    public long getCreateTime() {
-        return createTime;
-    }
-
-    public void setCreateTime(long createTime) {
-        this.flags |= SSH_FILEXFER_ATTR_CREATETIME;
-        this.createTime = createTime;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpAttrsReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpAttrsReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpAttrsReply.java
deleted file mode 100644
index e825cd0..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpAttrsReply.java
+++ /dev/null
@@ -1,66 +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.sftp.reply;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_ATTRS' reply.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpAttrsReply extends BaseReply {
-
-    private final FileAttributes attrs;
-
-    /**
-     * Creates a SshFxpAttrsReply instance.
-     *
-     * @param id    The reply id.
-     * @param attrs The attributes.
-     */
-    public SshFxpAttrsReply(final int id, final FileAttributes attrs) {
-        super(id);
-        this.attrs = attrs;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_ATTRS;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[attrs=" + attrs + "]";
-	}
-
-	/**
-	 * Returns the attributes.
-	 * 
-	 * @return the attributes.
-	 */
-	public FileAttributes getAttributes() {
-		return attrs;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpDataReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpDataReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpDataReply.java
deleted file mode 100644
index 7a02d4e..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpDataReply.java
+++ /dev/null
@@ -1,96 +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.sftp.reply;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_DATA' reply.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpDataReply extends BaseReply {
-
-	private final byte[] data;
-    private final int offset;
-    private final int length;
-    private final boolean eof;
-
-	/**
-	 * Creates a SshFxpData instance.
-	 * 
-	 * @param id   The reply id.
-	 * @param data The transfer data.
-	 */
-	public SshFxpDataReply(final int id, final byte[] data) {
-        this(id, data, 0, data.length, false);
-	}
-
-	/**
-	 * Creates a SshFxpData instance.
-	 * 
-	 * @param id      The reply id.
-     * @param data    The transfer data.
-     * @param offset  The offset in the data.
-     * @param length  The length of data.
-	 * @param eof     The EOF flag.
-	 */
-	public SshFxpDataReply(final int id, final byte[] data, final int offset, final int length, final boolean eof) {
-        super(id);
-		this.data = data;
-        this.offset = offset;
-        this.length = length;
-		this.eof  = eof;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public SftpConstants.Type getMessage() {
-		return SftpConstants.Type.SSH_FXP_DATA;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[data=<data(len=" + length + ")>, eof=" + eof + "]";
-	}
-
-	/**
-	 * Returns the data.
-	 * 
-	 * @return The data.
-	 */
-	public byte[] getData() {
-		return data;
-	}
-
-    public int getOffset() {
-        return offset;
-    }
-
-    public int getLength() {
-        return length;
-    }
-
-    public boolean isEof() {
-        return eof;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpHandleReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpHandleReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpHandleReply.java
deleted file mode 100644
index 266ebf7..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpHandleReply.java
+++ /dev/null
@@ -1,66 +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.sftp.reply;
-
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_HANDLE' reply.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpHandleReply extends BaseReply {
-
-	private final Handle handle;
-
-	/**
-	 * Creates a SshFxpHandleReply instance.
-	 * 
-	 * @param id       The reply id.
-	 * @param handle   The handle.
-	 */
-	public SshFxpHandleReply(final int id, final Handle handle) {
-        super(id);
-		this.handle = handle;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_HANDLE;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-		return getName() + "[handle=" + handle.getId() + ", file=" + handle.getFile().getAbsolutePath();
-	}
-
-	/**
-	 * Returns the handle.
-	 * 
-	 * @return The handle.
-	 */
-	public Handle getHandle() {
-		return handle;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpNameReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpNameReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpNameReply.java
deleted file mode 100644
index 17e4efe..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpNameReply.java
+++ /dev/null
@@ -1,150 +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.sftp.reply;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.sshd.common.file.SshFile;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_NAME' reply.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpNameReply extends BaseReply {
-	/**
-	 * Contains informations of requested files.
-	 */
-	public static class ReplyFile {
-
-        private final SshFile file;
-		private final String fileName;
-		private final String longName;
-		private final FileAttributes attrs;
-
-		/**
-		 * Creates ReplyFile instance.
-		 * 
-		 * @param fileName The file name.
-		 * @param longName The virtual absolute file path.
-		 * @param attrs    File attributes.
-		 */
-		public ReplyFile(final SshFile file, final String fileName, final String longName, final FileAttributes attrs) {
-            this.file = file;
-			this.fileName = fileName;
-			this.longName = longName;
-			this.attrs = attrs;
-		}
-
-        public SshFile getFile() {
-            return file;
-        }
-
-        public String getFileName() {
-            return fileName;
-        }
-
-        public String getLongName() {
-            return longName;
-        }
-
-        /**
-         * Returns the file attributes.
-         *
-         * @return The file attributes.
-         */
-        public FileAttributes getAttrs() {
-            return attrs;
-        }
-
-        public String toString() {
-            return "fileName=" + fileName + ", longName=" + longName;
-        }
-
-    }
-
-	private List<ReplyFile> files = new ArrayList<ReplyFile>();
-    private boolean eol;
-
-    /**
-	 * Creates a SshFxpHandleReply instance.
-	 * 
-	 * @param id         The reply id.
-	 */
-	public SshFxpNameReply(final int id) {
-		super(id);
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_NAME;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-		StringBuffer fs = new StringBuffer();
-		fs.append(getName());
-        fs.append("[");
-		fs.append("\n");
-		for (ReplyFile f : files) {
-            fs.append("    ");
-			fs.append(f.toString());
-			fs.append(",\n");
-	    }
-        fs.append("]");
-
-		return fs.toString();
-	}
-
-	/**
-	 * Add a file to the reply.
-	 * 
-	 * @param sshFile  The ssh file.
-	 * @param filename The file name.
-	 * @param longname The long file message.
-	 * @param attrs    The file attributes.
-	 */
-	public void addFile(final SshFile sshFile, final String filename, final String longname, final FileAttributes attrs) {
-		files.add(new ReplyFile(sshFile, filename, longname, attrs));
-	}
-
-	/**
-	 * Returns the files.
-	 * 
-	 * @return the files.
-	 */
-	public Collection<ReplyFile> getFiles() {
-		return files;
-	}
-
-    public boolean isEol() {
-        return eol;
-    }
-
-    public void setEol(boolean eol) {
-        this.eol = eol;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpStatusReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpStatusReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpStatusReply.java
deleted file mode 100644
index 9722480..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpStatusReply.java
+++ /dev/null
@@ -1,164 +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.sftp.reply;
-
-import java.io.IOException;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-import static org.apache.sshd.sftp.subsystem.SftpConstants.*;
-
-/**
- * Data container for 'SSH_FXP_STATUS' reply.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpStatusReply extends BaseReply {
-
-	private String substatusAsSTR;
-	private String msg;
-	private String lang;
-	private final int substatus;
-
-    /**
-     * Creates a SshFxpStatusReply instance.
-     *
-     * @param id        The reply id.
-     * @param substatus The sub status.
-     * @param msg       The status message.
-     *
-     * @throws IOException If the given reply is unsupported.
-     */
-    public SshFxpStatusReply(final int id, final int substatus, final String msg)
-            throws IOException {
-        this(id, substatus, msg, "");
-    }
-
-	/**
-	 * Creates a SshFxpStatusReply instance.
-	 * 
-	 * @param id        The reply id.
-	 * @param substatus The sub status.
-	 * @param msg       The status message.
-	 * @param lang      The lang status message.
-	 * 
-	 * @throws IOException If the given reply is unsupported. 
-	 */
-	public SshFxpStatusReply(final int id, final int substatus, final String msg, final String lang)
-			throws IOException {
-        super(id);
-		this.substatus = substatus;
-		this.lang = lang;
-		this.msg = msg;
-
-    	switch (substatus) {
-		case SSH_FX_FILE_ALREADY_EXISTS:
-			substatusAsSTR = "SSH_FX_FILE_ALREADY_EXISTS";
-			break;
-		case SSH_FX_DIR_NOT_EMPTY:
-			substatusAsSTR = "SSH_FX_DIR_NOT_EMPTY";
-			break;
-		case SSH_FX_EOF:
-			substatusAsSTR = "SSH_FX_EOF";
-			break;
-		case SSH_FX_FILE_IS_A_DIRECTORY:
-			substatusAsSTR = "SSH_FX_FILE_IS_A_DIRECTORY";
-			break;
-		case SSH_FX_INVALID_HANDLE:
-			substatusAsSTR = "SSH_FX_INVALID_HANDLE";
-			break;
-		case SSH_FX_NO_SUCH_FILE:
-			substatusAsSTR = "SSH_FX_NO_SUCH_FILE";
-			break;
-		case SSH_FX_NO_SUCH_PATH:
-			substatusAsSTR = "SSH_FX_NO_SUCH_PATH";
-			break;
-		case SSH_FX_NOT_A_DIRECTORY:
-			substatusAsSTR = "SSH_FX_NOT_A_DIRECTORY";
-			break;
-		case SSH_FX_OK:
-			substatusAsSTR = "SSH_FX_OK";
-			break;
-		case SSH_FX_OP_UNSUPPORTED:
-			substatusAsSTR = "SSH_FX_OP_UNSUPPORTED";
-			break;
-		case SSH_FX_FAILURE:
-			substatusAsSTR = "SSH_FX_FAILURE";
-			break;
-		case SSH_FX_PERMISSION_DENIED:
-			substatusAsSTR = "SSH_FX_PERMISSION_DENIED";
-			break;
-		case SSH_FXP_MKDIR:
-			substatusAsSTR = "SSH_FXP_MKDIR";
-			break;
-		case SSH_FXP_REMOVE:
-			substatusAsSTR = "SSH_FXP_REMOVE";
-			break;
-		case SSH_FXP_RMDIR:
-			substatusAsSTR = "SSH_FXP_RMDIR";
-			break;
-		case SSH_FX_WRITE_PROTECT:
-			substatusAsSTR = "SSH_FX_WRITE_PROTECT";
-			break;
-		default:
-			throw new IOException("Internal error - unexpected substatus: " + substatus);
-		}
-	}
-
-	/**
-	 * Returns the substatus.
-	 * 
-	 * @return The substatus.
-	 */
-	public int getSubstatus() {
-		return substatus;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-		return getName() + "[status=" + substatusAsSTR + ", msg=" + msg + "]";
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public SftpConstants.Type getMessage() {
-		return SftpConstants.Type.SSH_FXP_STATUS;
-	}
-
-	/**
-	 * Returns the status message.
-	 * 
-	 * @return The status message.
-	 */
-	public String getMsg() {
-		return msg;
-	}
-
-	/**
-	 * Returns the long status message.
-	 * 
-	 * @return The long status message.
-	 */
-	public String getLang() {
-		return lang;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpVersionReply.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpVersionReply.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpVersionReply.java
deleted file mode 100644
index c744942..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/reply/SshFxpVersionReply.java
+++ /dev/null
@@ -1,61 +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.sftp.reply;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_VERSION' reply.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpVersionReply extends BaseReply {
-
-	/**
-	 * Creates a SshFxpVersionReply instance.
-	 * 
-	 * @param version The requested version.
-	 */
-	public SshFxpVersionReply(final int version) {
-		super(version);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public SftpConstants.Type getMessage() {
-		return SftpConstants.Type.SSH_FXP_VERSION;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-		return getName() + "[version=" + getId() + "]";
-	}
-
-	/**
-	 * Returns the requested version.
-	 * 
-	 * @return The requested version.
-	 */
-	public int getVersion() {
-		return getId();
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/BaseRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/BaseRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/BaseRequest.java
deleted file mode 100644
index 2e2cfc9..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/BaseRequest.java
+++ /dev/null
@@ -1,58 +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.sftp.request;
-
-import org.apache.sshd.sftp.Request;
-
-/**
- * Common ssh request interface.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public abstract class BaseRequest implements Request {
-
-	private final int id;
-	
-	/**
-	 * Creates a Request instance.
-	 * 
-	 * @param id The request id.
-	 */
-	public BaseRequest(final int id) {
-		this.id = id;
-	}
-
-	/**
-	 * Returns the request id.
-	 * 
-	 * @return The request id.
-	 */
-	public int getId() {
-		return id;
-	}
-
-	/**
-	 * Returns the request name.
-	 * 
-	 * @return The request name.
-	 */
-	public String getName() {
-        return getMessage().toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpCloseRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpCloseRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpCloseRequest.java
deleted file mode 100644
index 8ebfd35..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpCloseRequest.java
+++ /dev/null
@@ -1,83 +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.sftp.request;
-
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_CLOSE' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpCloseRequest extends BaseRequest {
-	private final Handle handle;
-	private final String handleId;
-
-	/**
-	 * Create SshFxpCloseRequest instance.
-	 * 
-	 * @param id       The request id.
-	 * @param handleId The according handle id.
-	 * @param handle   The according file handle.
-	 */
-	public SshFxpCloseRequest(final int id, final String handleId, final Handle handle) {
-		super(id);
-		this.handleId = handleId;
-		this.handle = handle;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public SftpConstants.Type getMessage() {
-		return SftpConstants.Type.SSH_FXP_CLOSE;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-		String sh;
-		if (handle != null && handle.getFile() != null) {
-			sh = handle.getFile().getAbsolutePath();
-		} else {
-			sh = "";
-		}
-        return getName() + "[handle=" + handleId + ", file=" + sh + "]";
-	}
-
-	/**
-	 * Returns the according handle.
-	 * 
-	 * @return The according handle.
-	 */
-	public Handle getHandle() {
-		return handle;
-	}
-
-	/**
-	 * Returns the handle id.
-	 * 
-	 * @return The handle id.
-	 */
-	public String getHandleId() {
-		return handleId;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFsetstatRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFsetstatRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFsetstatRequest.java
deleted file mode 100644
index a19c08c..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFsetstatRequest.java
+++ /dev/null
@@ -1,52 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_FSETSTAT' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpFsetstatRequest extends BaseRequest {
-
-	/**
-	 * Creates a SshFxpFsetstatRequest instance.
-	 * 
-	 * @param id The request id.
-	 */
-	public SshFxpFsetstatRequest(final int id) {
-		super(id);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_SETSTAT;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[]";
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFstatRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFstatRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFstatRequest.java
deleted file mode 100644
index 6dbfba4..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpFstatRequest.java
+++ /dev/null
@@ -1,74 +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.sftp.request;
-
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_FSTAT' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpFstatRequest extends BaseRequest {
-	private final String handle;
-	private final Handle handleRef;
-
-	/**
-	 * Creates a SshFxpFstatRequest instance.
-	 * 
-	 * @param id        The request id.
-	 * @param handle    The handle.
-	 * @param handleRef The handle reference.
-	 */
-	public SshFxpFstatRequest(final int id, final String handle, final Handle handleRef) {
-		super(id);
-		this.handle = handle;
-		this.handleRef = handleRef;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_FSTAT;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-		String ps;
-		if (handleRef != null && handleRef.getFile() != null) {
-			ps = handleRef.getFile().getAbsolutePath();
-		} else {
-			ps = "";
-		}
-        return getName() + "[handle=" + handle + ", file=" + ps + "]";
-	}
-
-	/**
-	 * Returns the handle id.
-	 * 
-	 * @return The handle id.
-	 */
-	public String getHandleId() {
-		return handle;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpInitRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpInitRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpInitRequest.java
deleted file mode 100644
index be54767..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpInitRequest.java
+++ /dev/null
@@ -1,51 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_INIT' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpInitRequest extends BaseRequest {
-	/**
-	 * Creates a SshFxpInitRequest instance.
-	 * 
-	 * @param id The request id.
-	 */
-	public SshFxpInitRequest(final int id) {
-		super(id);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_INIT;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[version=" + getId() + "]";
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpLstatRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpLstatRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpLstatRequest.java
deleted file mode 100644
index 9251063..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpLstatRequest.java
+++ /dev/null
@@ -1,72 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_STAT' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpLstatRequest extends BaseRequest {
-
-	private final String path;
-    private final int flags;
-
-	/**
-	 * Creates a SshFxpLstatRequest instance.
-	 * 
-	 * @param id    The request id.
-     * @param path  The requested path.
-     * @param flags The stat flags.
-	 */
-	public SshFxpLstatRequest(final int id, final String path, final int flags) {
-		super(id);
-		this.path = path;
-        this.flags = flags;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_STAT;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-
-    public int getFlags() {
-        return flags;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpMkdirRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpMkdirRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpMkdirRequest.java
deleted file mode 100644
index 8c04e49..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpMkdirRequest.java
+++ /dev/null
@@ -1,65 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_MKDIR' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpMkdirRequest extends BaseRequest {
-	
-	private final String path;
-
-	/**
-	 * Creates a SshFxpMkdirRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param path The path to create.
-	 */
-	public SshFxpMkdirRequest(final int id, final String path) {
-		super(id);
-		this.path = path;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_MKDIR;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpenRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpenRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpenRequest.java
deleted file mode 100644
index 9dec41e..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpenRequest.java
+++ /dev/null
@@ -1,99 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_OPEN' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpOpenRequest extends BaseRequest {
-    private final String path;
-    private final int acc;
-	private final int flags;
-
-	/**
-	 * Creates a SshFxpOpenRequest instance.
-	 * 
-	 * @param id     The request id.
-	 * @param path   The path.
-	 * @param flags  The flags.
-	 */
-	public SshFxpOpenRequest(final int id, String path, final int flags) {
-        this(id, path, 0, flags);
-	}
-
-	/**
-	 * Creates a SshFxpOpenRequest instance.
-	 * 
-	 * @param id     The request id.
-	 * @param path   The path.
-	 * @param acc    The acc.
-	 * @param flags  The flags.
-	 */
-	public SshFxpOpenRequest(final int id, String path, final int acc, final int flags) {
-		super(id);
-		this.path = path;
-		this.acc = acc;
-		this.flags = flags;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_OPEN;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + ", acc=" + acc + ", flags=" + flags + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-
-	/**
-	 * Returns the flags.
-	 * 
-	 * @return The flags.
-	 */
-	public int getFlags() {
-		return flags;
-	}
-
-	/**
-	 * Returns the acc.
-	 * 
-	 * @return The acc.
-	 */
-	public int getAcc() {
-		return acc;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpendirRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpendirRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpendirRequest.java
deleted file mode 100644
index ac2e281..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpOpendirRequest.java
+++ /dev/null
@@ -1,64 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_OPENDIR' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpOpendirRequest extends BaseRequest {
-	private final String path;
-
-	/**
-	 * Create a SshFxpOpendirRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param path The directory path to open.
-	 */
-	public SshFxpOpendirRequest(final int id, final String path) {
-		super(id);
-		this.path = path;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_OPENDIR;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReadRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReadRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReadRequest.java
deleted file mode 100644
index 74fa654..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReadRequest.java
+++ /dev/null
@@ -1,109 +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.sftp.request;
-
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_READ' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpReadRequest extends BaseRequest {
-	private final String handleId;
-	private final long offset;
-	private final Handle handle;
-	private final int length;
-
-	/**
-	 * Creates a SshFxpReadRequest instance.
-	 * 
-	 * @param id       The request id.
-	 * @param handleId The according file handle id.
-	 * @param offset   The read offset.
-	 * @param length   The length.
-	 * @param handle   The according file handle.
-	 */
-	public SshFxpReadRequest(
-			final int id, final String handleId, final long offset, final int length, final Handle handle) {
-		super(id);
-		this.handleId = handleId;
-		this.offset = offset;
-		this.length = length;
-		this.handle = handle;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_READ;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        String ps;
-        if (handle != null && handle.getFile() != null) {
-            ps = handle.getFile().getAbsolutePath();
-        } else {
-            ps = "";
-        }
-        return getName() + "[handle=" + handleId + ", file=" + ps + ", offset=" + offset + ", length=" + length + "]";
-	}
-
-	/**
-	 * Returns the according handle.
-	 * 
-	 * @return The according handle.
-	 */
-	public Handle getHandle() {
-		return handle;
-	}
-
-	/**
-	 * Returns the handle id.
-	 * 
-	 * @return The handle id.
-	 */
-	public String getHandleId() {
-		return handleId;
-	}
-
-	/**
-	 * Returns the length.
-	 * 
-	 * @return The length.
-	 */
-	public int getLength() {
-		return length;
-	}
-
-	/**
-	 * Returns the offset.
-	 * 
-	 * @return The offset.
-	 */
-	public long getOffset() {
-		return offset;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReaddirRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReaddirRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReaddirRequest.java
deleted file mode 100644
index e3338fe..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpReaddirRequest.java
+++ /dev/null
@@ -1,74 +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.sftp.request;
-
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_READDIR' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpReaddirRequest extends BaseRequest {
-	private final String handleId;
-	private final Handle handle;
-
-	/**
-	 * Creates a SshFxpReaddirRequest instance.
-	 * 
-	 * @param id       The request id.
-	 * @param handleId The according handle id.
-	 * @param handle   The according file handle.
-	 */
-	public SshFxpReaddirRequest(final int id, final String handleId, final Handle handle) {
-		super(id);
-		this.handleId = handleId;
-		this.handle = handle;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_READDIR;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        String ps;
-        if (handle != null && handle.getFile() != null) {
-            ps = handle.getFile().getAbsolutePath();
-        } else {
-            ps = "";
-        }
-        return getName() + "[handle=" + handleId + ", file=" + ps + "]";
-	}
-
-	/**
-	 * Returns the handle id.
-	 * 
-	 * @return The handle id.
-	 */
-	public String getHandleId() {
-		return handleId;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRealpathRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRealpathRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRealpathRequest.java
deleted file mode 100644
index 064c651..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRealpathRequest.java
+++ /dev/null
@@ -1,78 +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.sftp.request;
-
-import java.util.List;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_REALPATH' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpRealpathRequest extends BaseRequest {
-	private final String path;
-    private final byte options;
-    private final List<String> compose;
-
-	/**
-	 * Creates a SshFxpRealpathRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param path The requested file path.
-	 */
-	public SshFxpRealpathRequest(final int id, final String path, final byte options, final List<String> compose) {
-		super(id);
-		this.path = path;
-        this.options = options;
-        this.compose = compose;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_REALPATH;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-
-    public byte getOptions() {
-        return options;
-    }
-
-    public List<String> getCompose() {
-        return compose;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRemoveRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRemoveRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRemoveRequest.java
deleted file mode 100644
index 664b4b2..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRemoveRequest.java
+++ /dev/null
@@ -1,64 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_REMOVE' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpRemoveRequest extends BaseRequest {
-	private final String path;
-
-	/**
-	 * Create a SshFxpRemoveRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param path The file path to remove.
-	 */
-	public SshFxpRemoveRequest(final int id, final String path) {
-		super(id);
-		this.path = path;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_REMOVE;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRenameRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRenameRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRenameRequest.java
deleted file mode 100644
index cd0a3c6..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRenameRequest.java
+++ /dev/null
@@ -1,76 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_RENAME' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpRenameRequest extends BaseRequest {
-	private final String oldPath;
-	private final String newPath;
-
-	/**
-	 * Create a SshFxpRenameRequest instance.
-	 * 
-	 * @param id      The request id.
-	 * @param oldPath The old path.
-	 * @param newPath The new path.
-	 */
-	public SshFxpRenameRequest(final int id, final String oldPath, final String newPath) {
-		super(id);
-		this.oldPath = oldPath;
-		this.newPath = newPath;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_RENAME;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[old=" + oldPath + ", new=" + newPath + "]";
-	}
-
-	/**
-	 * Returns the old path.
-	 * 
-	 * @return The old path.
-	 */
-	public String getOldPath() {
-		return oldPath;
-	}
-
-	/**
-	 * Returns the new path.
-	 * 
-	 * @return The new path.
-	 */
-	public String getNewPath() {
-		return newPath;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRmdirRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRmdirRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRmdirRequest.java
deleted file mode 100644
index 46263ef..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpRmdirRequest.java
+++ /dev/null
@@ -1,64 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_RMDIR' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpRmdirRequest extends BaseRequest {
-	private final String path;
-
-	/**
-	 * Creates a SshFxpRmdirRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param path The requested file path.
-	 */
-	public SshFxpRmdirRequest(final int id, final String path) {
-		super(id);
-		this.path = path;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_RMDIR;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-}


[06/15] mina-sshd git commit: [SSHD-377] Create a nio FileSystem implementation for sftp

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/test/java/org/apache/sshd/common/file/util/BasePathTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/file/util/BasePathTest.java b/sshd-core/src/test/java/org/apache/sshd/common/file/util/BasePathTest.java
new file mode 100644
index 0000000..55c6b74
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/file/util/BasePathTest.java
@@ -0,0 +1,560 @@
+/*
+ * 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.file.util;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class BasePathTest {
+
+    private TestFileSystem fileSystem;
+
+    @Before
+    public void setUp() {
+        fileSystem = new TestFileSystem(null);
+    }
+
+    @Test
+    public void testPathParsing() {
+        assertPathEquals("/", "/");
+        assertPathEquals("/foo", "/foo");
+        assertPathEquals("/foo", "/", "foo");
+        assertPathEquals("/foo/bar", "/foo/bar");
+        assertPathEquals("/foo/bar", "/", "foo", "bar");
+        assertPathEquals("/foo/bar", "/foo", "bar");
+        assertPathEquals("/foo/bar", "/", "foo/bar");
+        assertPathEquals("foo/bar/baz", "foo/bar/baz");
+        assertPathEquals("foo/bar/baz", "foo", "bar", "baz");
+        assertPathEquals("foo/bar/baz", "foo/bar", "baz");
+        assertPathEquals("foo/bar/baz", "foo", "bar/baz");
+    }
+
+    @Test
+    public void testPathParsing_withExtraSeparators() {
+        assertPathEquals("/foo/bar", "///foo/bar");
+        assertPathEquals("/foo/bar", "/foo///bar//");
+        assertPathEquals("/foo/bar/baz", "/foo", "/bar", "baz/");
+        //assertPathEquals("/foo/bar/baz", "/foo\\/bar//\\\\/baz\\/");
+    }
+
+    @Test
+    public void testRootPath() {
+        new PathTester(fileSystem, "/")
+                .root("/")
+                .test("/");
+    }
+
+    @Test
+    public void testRelativePath_singleName() {
+        new PathTester(fileSystem, "test")
+                .names("test")
+                .test("test");
+
+        Path path = parsePath("test");
+        assertEquals(path, path.getFileName());
+    }
+
+    @Test
+    public void testRelativePath_twoNames() {
+        PathTester tester = new PathTester(fileSystem, "foo/bar")
+                .names("foo", "bar");
+
+        tester.test("foo/bar");
+    }
+
+    @Test
+    public void testRelativePath_fourNames() {
+        new PathTester(fileSystem, "foo/bar/baz/test")
+                .names("foo", "bar", "baz", "test")
+                .test("foo/bar/baz/test");
+    }
+
+    @Test
+    public void testAbsolutePath_singleName() {
+        new PathTester(fileSystem, "/foo")
+                .root("/")
+                .names("foo")
+                .test("/foo");
+    }
+
+    @Test
+    public void testAbsolutePath_twoNames() {
+        new PathTester(fileSystem, "/foo/bar")
+                .root("/")
+                .names("foo", "bar")
+                .test("/foo/bar");
+    }
+
+    @Test
+    public void testAbsoluteMultiNamePath_fourNames() {
+        new PathTester(fileSystem, "/foo/bar/baz/test")
+                .root("/")
+                .names("foo", "bar", "baz", "test")
+                .test("/foo/bar/baz/test");
+    }
+
+    @Test
+    public void testResolve_fromRoot() {
+        Path root = parsePath("/");
+
+        assertResolvedPathEquals("/foo", root, "foo");
+        assertResolvedPathEquals("/foo/bar", root, "foo/bar");
+        assertResolvedPathEquals("/foo/bar", root, "foo", "bar");
+        assertResolvedPathEquals("/foo/bar/baz/test", root, "foo/bar/baz/test");
+        assertResolvedPathEquals("/foo/bar/baz/test", root, "foo", "bar/baz", "test");
+    }
+
+    @Test
+    public void testResolve_fromAbsolute() {
+        Path path = parsePath("/foo");
+
+        assertResolvedPathEquals("/foo/bar", path, "bar");
+        assertResolvedPathEquals("/foo/bar/baz/test", path, "bar/baz/test");
+        assertResolvedPathEquals("/foo/bar/baz/test", path, "bar/baz", "test");
+        assertResolvedPathEquals("/foo/bar/baz/test", path, "bar", "baz", "test");
+    }
+
+    @Test
+    public void testResolve_fromRelative() {
+        Path path = parsePath("foo");
+
+        assertResolvedPathEquals("foo/bar", path, "bar");
+        assertResolvedPathEquals("foo/bar/baz/test", path, "bar/baz/test");
+        assertResolvedPathEquals("foo/bar/baz/test", path, "bar", "baz", "test");
+        assertResolvedPathEquals("foo/bar/baz/test", path, "bar/baz", "test");
+    }
+
+    @Test
+    public void testResolve_withThisAndParentDirNames() {
+        Path path = parsePath("/foo");
+
+        assertResolvedPathEquals("/foo/bar/../baz", path, "bar/../baz");
+        assertResolvedPathEquals("/foo/bar/../baz", path, "bar", "..", "baz");
+        assertResolvedPathEquals("/foo/./bar/baz", path, "./bar/baz");
+        assertResolvedPathEquals("/foo/./bar/baz", path, ".", "bar/baz");
+    }
+
+    @Test
+    public void testResolve_givenAbsolutePath() {
+        assertResolvedPathEquals("/test", parsePath("/foo"), "/test");
+        assertResolvedPathEquals("/test", parsePath("foo"), "/test");
+    }
+
+    @Test
+    public void testResolve_givenEmptyPath() {
+        assertResolvedPathEquals("/foo", parsePath("/foo"), "");
+        assertResolvedPathEquals("foo", parsePath("foo"), "");
+    }
+
+    @Test
+    public void testResolve_againstEmptyPath() {
+        assertResolvedPathEquals("foo/bar", parsePath(""), "foo/bar");
+    }
+
+    @Test
+    public void testResolveSibling_givenEmptyPath() {
+        Path path = parsePath("foo/bar");
+        Path resolved = path.resolveSibling("");
+        assertPathEquals("foo", resolved);
+
+        path = parsePath("foo");
+        resolved = path.resolveSibling("");
+        assertPathEquals("", resolved);
+    }
+
+    @Test
+    public void testResolveSibling_againstEmptyPath() {
+        Path path = parsePath("");
+        Path resolved = path.resolveSibling("foo");
+        assertPathEquals("foo", resolved);
+
+        path = parsePath("");
+        resolved = path.resolveSibling("");
+        assertPathEquals("", resolved);
+    }
+
+    @Test
+    public void testRelativize_bothAbsolute() {
+        assertRelativizedPathEquals("b/c", parsePath("/a"), "/a/b/c");
+        assertRelativizedPathEquals("c/d", parsePath("/a/b"), "/a/b/c/d");
+    }
+
+    @Test
+    public void testRelativize_bothRelative() {
+        assertRelativizedPathEquals("b/c", parsePath("a"), "a/b/c");
+        assertRelativizedPathEquals("d", parsePath("a/b/c"), "a/b/c/d");
+    }
+
+    @Test
+    public void testRelativize_againstEmptyPath() {
+        assertRelativizedPathEquals("foo/bar", parsePath(""), "foo/bar");
+    }
+
+    @Test
+    public void testRelativize_oneAbsoluteOneRelative() {
+        try {
+            parsePath("/foo/bar").relativize(parsePath("foo"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            parsePath("foo").relativize(parsePath("/foo/bar"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testNormalize_withParentDirName() {
+        assertNormalizedPathEquals("/foo/baz", "/foo/bar/../baz");
+        assertNormalizedPathEquals("/foo/baz", "/foo", "bar", "..", "baz");
+    }
+
+    @Test
+    public void testNormalize_withThisDirName() {
+        assertNormalizedPathEquals("/foo/bar/baz", "/foo/bar/./baz");
+        assertNormalizedPathEquals("/foo/bar/baz", "/foo", "bar", ".", "baz");
+    }
+
+    @Test
+    public void testNormalize_withThisAndParentDirNames() {
+        assertNormalizedPathEquals("foo/test", "foo/./bar/../././baz/../test");
+    }
+
+    @Test
+    public void testNormalize_withLeadingParentDirNames() {
+        assertNormalizedPathEquals("../../foo/baz", "../../foo/bar/../baz");
+    }
+
+    @Test
+    public void testNormalize_withLeadingThisAndParentDirNames() {
+        assertNormalizedPathEquals("../../foo/baz", "./.././.././foo/bar/../baz");
+    }
+
+    @Test
+    public void testNormalize_withExtraParentDirNamesAtRoot() {
+        assertNormalizedPathEquals("/", "/..");
+        assertNormalizedPathEquals("/", "/../../..");
+        assertNormalizedPathEquals("/", "/foo/../../..");
+        assertNormalizedPathEquals("/", "/../foo/../../bar/baz/../../../..");
+    }
+
+    @Test
+    public void testPathWithExtraSlashes() {
+        assertPathEquals("/foo/bar/baz", parsePath("/foo/bar/baz/"));
+        assertPathEquals("/foo/bar/baz", parsePath("/foo//bar///baz"));
+        assertPathEquals("/foo/bar/baz", parsePath("///foo/bar/baz"));
+    }
+
+    private void assertResolvedPathEquals(String expected, Path path, String firstResolvePath,
+                                          String... moreResolvePaths) {
+        Path resolved = path.resolve(firstResolvePath);
+        for (String additionalPath : moreResolvePaths) {
+            resolved = resolved.resolve(additionalPath);
+        }
+        assertPathEquals(expected, resolved);
+
+        Path relative = parsePath(firstResolvePath, moreResolvePaths);
+        resolved = path.resolve(relative);
+        assertPathEquals(expected, resolved);
+
+        // assert the invariant that p.relativize(p.resolve(q)).equals(q) when q does not have a root
+        // p = path, q = relative, p.resolve(q) = resolved
+        if (relative.getRoot() == null) {
+            assertEquals(relative, path.relativize(resolved));
+        }
+    }
+
+    private void assertRelativizedPathEquals(String expected, Path path, String relativizePath) {
+        Path relativized = path.relativize(parsePath(relativizePath));
+        assertPathEquals(expected, relativized);
+    }
+
+    private void assertNormalizedPathEquals(String expected, String first, String... more) {
+        assertPathEquals(expected, parsePath(first, more).normalize());
+    }
+
+    private void assertPathEquals(String expected, String first, String... more) {
+        assertPathEquals(expected, parsePath(first, more));
+    }
+
+    private void assertPathEquals(String expected, Path path) {
+        assertEquals(parsePath(expected), path);
+    }
+
+    private Path parsePath(String first, String... more) {
+        return fileSystem.getPath(first, more);
+    }
+
+    private static class TestFileSystem extends BaseFileSystem<TestPath> {
+
+        public TestFileSystem(FileSystemProvider fileSystemProvider) {
+            super(fileSystemProvider);
+        }
+
+        @Override
+        protected TestPath create(String root, ImmutableList<String> names) {
+            return new TestPath(this, root, names);
+        }
+
+        @Override
+        public void close() throws IOException {
+
+        }
+
+        @Override
+        public boolean isOpen() {
+            return false;
+        }
+
+        @Override
+        public Set<String> supportedFileAttributeViews() {
+            return null;
+        }
+
+        @Override
+        public UserPrincipalLookupService getUserPrincipalLookupService() {
+            return null;
+        }
+    }
+
+    private static class TestPath extends BasePath<TestPath, TestFileSystem> {
+
+        public TestPath(TestFileSystem fileSystem, String root, ImmutableList<String> names) {
+            super(fileSystem, root, names);
+        }
+
+        @Override
+        protected TestPath create(String root, ImmutableList<String> names) {
+            return new TestPath(fileSystem, root, names);
+        }
+
+        @Override
+        public URI toUri() {
+            return null;
+        }
+
+        @Override
+        public Path toRealPath(LinkOption... options) throws IOException {
+            return null;
+        }
+    }
+
+    public static class PathTester {
+
+        private final FileSystem fileSystem;
+        private final String string;
+        private String root;
+        private ImmutableList<String> names = new ImmutableList<>(new String[0]);
+
+        public PathTester(FileSystem fileSystem, String string) {
+            this.fileSystem = fileSystem;
+            this.string = string;
+        }
+
+        public PathTester root(String root) {
+            this.root = root;
+            return this;
+        }
+
+        public PathTester names(Collection<String> names) {
+            this.names = new ImmutableList<>(names.toArray(new String[names.size()]));
+            return this;
+        }
+
+        public PathTester names(String... names) {
+            return names(Arrays.asList(names));
+        }
+
+        public void test(String first, String... more) {
+            Path path = fileSystem.getPath(first, more);
+            test(path);
+        }
+
+        public void test(Path path) {
+            assertEquals(string, path.toString());
+
+            testRoot(path);
+            testNames(path);
+            testParents(path);
+            testStartsWith(path);
+            testEndsWith(path);
+            testSubpaths(path);
+        }
+
+        private void testRoot(Path path) {
+            if (root != null) {
+                assertTrue(path + ".isAbsolute() should be true", path.isAbsolute());
+                assertNotNull(path + ".getRoot() should not be null", path.getRoot());
+                assertEquals(root, path.getRoot().toString());
+            } else {
+                assertFalse(path + ".isAbsolute() should be false", path.isAbsolute());
+                assertNull(path + ".getRoot() should be null", path.getRoot());
+            }
+        }
+
+        private void testNames(Path path) {
+            assertEquals(names.size(), path.getNameCount());
+            assertEquals(names, names(path));
+            for (int i = 0; i < names.size(); i++) {
+                assertEquals(names.get(i), path.getName(i).toString());
+                // don't test individual names if this is an individual name
+                if (names.size() > 1) {
+                    new PathTester(fileSystem, names.get(i))
+                            .names(names.get(i))
+                            .test(path.getName(i));
+                }
+            }
+            if (names.size() > 0) {
+                String fileName = names.get(names.size() - 1);
+                assertEquals(fileName, path.getFileName().toString());
+                // don't test individual names if this is an individual name
+                if (names.size() > 1) {
+                    new PathTester(fileSystem, fileName)
+                            .names(fileName)
+                            .test(path.getFileName());
+                }
+            }
+        }
+
+        private void testParents(Path path) {
+            Path parent = path.getParent();
+
+            if (root != null && names.size() >= 1 || names.size() > 1) {
+                assertNotNull(parent);
+            }
+
+            if (parent != null) {
+                String parentName = names.size() == 1 ? root :
+                        string.substring(0, string.lastIndexOf('/'));
+                new PathTester(fileSystem, parentName)
+                        .root(root)
+                        .names(names.subList(0, names.size() - 1))
+                        .test(parent);
+            }
+        }
+
+        private void testSubpaths(Path path) {
+            if (path.getRoot() == null) {
+                assertEquals(path, path.subpath(0, path.getNameCount()));
+            }
+
+            if (path.getNameCount() > 1) {
+                String stringWithoutRoot = root == null ? string : string.substring(root.length());
+
+                // test start + 1 to end and start to end - 1 subpaths... this recursively tests all subpaths
+                // actually tests most possible subpaths multiple times but... eh
+                Path startSubpath = path.subpath(1, path.getNameCount());
+                List<String> startNames = split(stringWithoutRoot, "/")
+                        .subList(1, path.getNameCount());
+
+                new PathTester(fileSystem, join(startNames, "/"))
+                        .names(startNames)
+                        .test(startSubpath);
+
+                Path endSubpath = path.subpath(0, path.getNameCount() - 1);
+                List<String> endNames = split(stringWithoutRoot, "/")
+                        .subList(0, path.getNameCount() - 1);
+
+                new PathTester(fileSystem, join(endNames, "/"))
+                        .names(endNames)
+                        .test(endSubpath);
+            }
+        }
+
+        private void testStartsWith(Path path) {
+            // empty path doesn't start with any path
+            if (root != null || !names.isEmpty()) {
+                Path other = path;
+                while (other != null) {
+                    assertTrue(path + ".startsWith(" + other + ") should be true",
+                            path.startsWith(other));
+                    assertTrue(path + ".startsWith(" + other + ") should be true",
+                            path.startsWith(other.toString()));
+                    other = other.getParent();
+                }
+            }
+        }
+
+        private void testEndsWith(Path path) {
+            // empty path doesn't start with any path
+            if (root != null || !names.isEmpty()) {
+                Path other = path;
+                while (other != null) {
+                    assertTrue(path + ".endsWith(" + other + ") should be true",
+                            path.endsWith(other));
+                    assertTrue(path + ".endsWith(" + other + ") should be true",
+                            path.endsWith(other.toString()));
+                    if (other.getRoot() != null && other.getNameCount() > 0) {
+                        other = other.subpath(0, other.getNameCount());
+                    } else if (other.getNameCount() > 1) {
+                        other = other.subpath(1, other.getNameCount());
+                    } else {
+                        other = null;
+                    }
+                }
+            }
+        }
+
+        private static List<String> names(Path path) {
+            List<String> list = new ArrayList<>();
+            for (Path p : path) {
+                list.add(p.toString());
+            }
+            return list;
+        }
+
+        private List<String> split(String string, String sep) {
+            return Arrays.asList(string.split(sep));
+        }
+
+        private static String join(Iterable<String> strings, String sep) {
+            StringBuilder sb = new StringBuilder();
+            for (String s : strings) {
+                if (sb.length() > 0) {
+                    sb.append(sep);
+                }
+                sb.append(s);
+            }
+            return sb.toString();
+        }
+    }
+
+}


[09/15] mina-sshd git commit: [SSHD-408] Implement sftp v4, v5 and v6

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/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
index 5313f6e..700e61d 100644
--- 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
@@ -25,35 +25,52 @@ 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.SeekableByteChannel;
+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.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.nio.file.attribute.AclEntry;
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
-import java.util.EnumSet;
+import java.util.Collections;
 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.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.NamedFactory;
 import org.apache.sshd.common.file.FileSystemAware;
@@ -131,40 +148,6 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
     }
 
-    //
-    // File attributes
-    //
-    enum Attribute {
-        Size,               // long
-        Uid,                // int
-        Owner,              // String
-        Gid,                // int
-        Group,              // String
-        IsDirectory,        // boolean
-        IsRegularFile,      // boolean
-        IsSymbolicLink,     // boolean
-        Permissions,        // EnumSet<Permission>
-        CreationTime,       // long
-        LastModifiedTime,   // long
-        LastAccessTime,     // long
-        NLink               // int
-    }
-
-    //
-    // File permissions
-    //
-    enum Permission {
-        UserRead,
-        UserWrite,
-        UserExecute,
-        GroupRead,
-        GroupWrite,
-        GroupExecute,
-        OthersRead,
-        OthersWrite,
-        OthersExecute
-    }
-
     public enum UnsupportedAttributePolicy {
         Ignore,
         Warn,
@@ -176,9 +159,14 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
      */
     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 = 3; // Working implementation from v3
-    public static final int HIGHER_SFTP_IMPL = 3; //  .. up to
-    public static final String ALL_SFTP_IMPL = "3";
+    public static final int HIGHER_SFTP_IMPL = 6; //  .. up to
+    public static final String ALL_SFTP_IMPL = "3,4,5,6";
     public static final int  MAX_PACKET_LENGTH = 1024 * 16;
 
     public static final int SSH_FXP_INIT =             1;
@@ -200,7 +188,10 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     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;
+    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;
@@ -209,23 +200,81 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     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_FILE_ALREADY_EXISTS = 11; // Not in v3, but we need it
-
-    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_EXTENDED =    0x80000000;
+    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;
@@ -233,6 +282,63 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     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
@@ -255,6 +361,10 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     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;
 
     private ExitCallback callback;
     private InputStream in;
@@ -271,7 +381,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
 
     private int version;
-    private Map<String, Handle> handles = new HashMap<>();
+    private final Map<String, byte[]> extensions = new HashMap<>();
+    private final Map<String, Handle> handles = new HashMap<>();
 
     private UnsupportedAttributePolicy unsupportedAttributePolicy = UnsupportedAttributePolicy.Warn;
 
@@ -335,25 +446,64 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
     }
 
-    protected static class FileHandle extends Handle {
-        SeekableByteChannel channel;
+    protected class FileHandle extends Handle {
+        final FileChannel channel;
+        final List<FileLock> locks = new ArrayList<>();
 
-        public FileHandle(Path file, int flags) throws IOException {
+        public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
             super(file);
             Set<OpenOption> options = new HashSet<>();
-            if ((flags & SSH_FXF_READ) != 0) {
+            if ((access & ACE4_READ_DATA) != 0 || (access & ACE4_READ_ATTRIBUTES) != 0) {
                 options.add(StandardOpenOption.READ);
             }
-            if ((flags & SSH_FXF_WRITE) != 0) {
+            if ((access & ACE4_WRITE_DATA) != 0 || (access & ACE4_WRITE_ATTRIBUTES) != 0) {
                 options.add(StandardOpenOption.WRITE);
             }
-            if ((flags & SSH_FXF_APPEND) != 0) {
+            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;
+            }
+            if ((flags & SSH_FXF_APPEND_DATA) != 0) {
                 options.add(StandardOpenOption.APPEND);
             }
-            if ((flags & SSH_FXF_TRUNC) != 0) {
-                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+            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);
             }
-            channel = Files.newByteChannel(file, options);
+            this.channel = channel;
         }
 
         public int read(byte[] data, long offset) throws IOException {
@@ -370,6 +520,32 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         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;
+        }
     }
 
     public SftpSubsystem() {
@@ -411,7 +587,10 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     }
 
     public void setFileSystem(FileSystem fileSystem) {
-        this.fileSystem = fileSystem;
+        if (fileSystem != this.fileSystem) {
+            this.fileSystem = fileSystem;
+            this.defaultDir = fileSystem.getRootDirectories().iterator().next();
+        }
     }
 
     public void setExitCallback(ExitCallback callback) {
@@ -435,7 +614,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         try {
             pendingFuture = executors.submit(this);
         } catch (RuntimeException e) {    // e.g., RejectedExecutionException
-            log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.getMessage(), e);
+            log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.toString(), e);
             throw new IOException(e);
         }
     }
@@ -495,405 +674,95 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         int id = buffer.getInt();
         switch (type) {
             case SSH_FXP_INIT: {
-                log.debug("Received SSH_FXP_INIT (version={})", id);
-                // see https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt - section 4 - Protocol Initialization
-                if (length < 5) { // we don't care about extensions
-                    throw new IllegalArgumentException("Incomplete SSH_FXP_INIT data: length=" + length);
-                }
-                version = id;
-                if (version >= LOWER_SFTP_IMPL) {
-                    version = Math.min(version, HIGHER_SFTP_IMPL);
-                    buffer.clear();
-                    buffer.putByte((byte) SSH_FXP_VERSION);
-                    buffer.putInt(version);
-                    send(buffer);
-                } else {
-                    // We only support version 3 (Version 1 and 2 are not common)
-                    sendStatus(id, SSH_FX_OP_UNSUPPORTED, "SFTP server only support versions " + ALL_SFTP_IMPL);
-                }
-
+                doInit(buffer, id);
                 break;
             }
             case SSH_FXP_OPEN: {
-                if (session.getFactoryManager().getProperties() != null) {
-                    String maxHandlesString = session.getFactoryManager().getProperties().get(MAX_OPEN_HANDLES_PER_SESSION);
-                    if (maxHandlesString != null) {
-                        int maxHandleCount = Integer.parseInt(maxHandlesString);
-                        if (handles.size() > maxHandleCount) {
-                            sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
-                            break;
-                        }
-                    }
-                }
-
-                String path = buffer.getString();
-                int pflags = buffer.getInt();
-                Map<Attribute, Object> attrs = readAttrs(buffer);
-                log.debug("Received SSH_FXP_OPEN (path={}, pflags={}, attrs={})", new Object[] { path, pflags, attrs });
-                try {
-                    Path file = resolveFile(path);
-                    if (Files.exists(file)) {
-                        if ((pflags & SSH_FXF_READ) != 0 && !Files.isReadable(file)) {
-                            sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not read " + path);
-                            return;
-                        }
-                        if ((pflags & SSH_FXF_WRITE) != 0 && !Files.isWritable(file)) {
-                            sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not write " + path);
-                            return;
-                        }
-                        if (((pflags & SSH_FXF_CREAT) != 0) && ((pflags & SSH_FXF_EXCL) != 0)) {
-                            sendStatus(id, SSH_FX_FAILURE, path);
-                            return;
-                        }
-                    } else {
-                        if (((pflags & SSH_FXF_CREAT) != 0)) {
-                            Files.createFile(file);
-                        } else {
-                            sendStatus(id, SSH_FX_NO_SUCH_FILE, "No such file " + path);
-                            return;
-                        }
-                    }
-                    if (((pflags & SSH_FXF_CREAT) != 0)) {
-                        setAttributes(file, attrs);
-                    }
-                    String handle = UUID.randomUUID().toString();
-                    handles.put(handle, new FileHandle(file, pflags));
-                    sendHandle(id, handle);
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage() == null ? "" : e.getMessage());
-                }
+                doOpen(buffer, id);
                 break;
             }
             case SSH_FXP_CLOSE: {
-                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_FAILURE, handle, "");
-                    } else {
-                        handles.remove(handle);
-                        h.close();
-                        sendStatus(id, SSH_FX_OK, "", "");
-                    }
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doClose(buffer, id);
                 break;
             }
             case SSH_FXP_READ: {
-                String handle = buffer.getString();
-                long offset = buffer.getLong();
-                int len = buffer.getInt();
-                log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})", new Object[] { handle, offset, len });
-                try {
-                    Handle p = handles.get(handle);
-                    if (!(p instanceof FileHandle)) {
-                        sendStatus(id, SSH_FX_FAILURE, handle);
-                    } else {
-                        FileHandle fh = (FileHandle) p;
-                        byte[] b = new byte[Math.min(len, Buffer.MAX_LEN)];
-                        len = fh.read(b, offset);
-                        if (len >= 0) {
-                            Buffer buf = new Buffer(len + 5);
-                            buf.putByte((byte) SSH_FXP_DATA);
-                            buf.putInt(id);
-                            buf.putBytes(b, 0, len);
-                            send(buf);
-                        } else {
-                            sendStatus(id, SSH_FX_EOF, "");
-                        }
-                    }
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doRead(buffer, id);
                 break;
             }
             case SSH_FXP_WRITE: {
-                String handle = buffer.getString();
-                long offset = buffer.getLong();
-                byte[] data = buffer.getBytes();
-                log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])", new Object[] { handle, offset, data.length });
-                try {
-                    Handle p = handles.get(handle);
-                    if (!(p instanceof FileHandle)) {
-                        sendStatus(id, SSH_FX_FAILURE, handle);
-                    } else {
-                        FileHandle fh = (FileHandle) p;
-                        fh.write(data, offset);
-                        sendStatus(id, SSH_FX_OK, "");
-                    }
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doWrite(buffer, id);
                 break;
             }
             case SSH_FXP_LSTAT: {
-                String path = buffer.getString();
-                log.debug("Received SSH_FXP_LSTAT (path={})", path);
-                try {
-                    Path p = resolveFile(path);
-                    sendAttrs(id, p, false);
-                } catch (FileNotFoundException e) {
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doLStat(buffer, id);
                 break;
             }
             case SSH_FXP_FSTAT: {
-                String handle = buffer.getString();
-                log.debug("Received SSH_FXP_FSTAT (handle={})", handle);
-                try {
-                    Handle p = handles.get(handle);
-                    if (p == null) {
-                        sendStatus(id, SSH_FX_FAILURE, handle);
-                    } else {
-                        sendAttrs(id, p.getFile(), true);
-                    }
-                } catch (FileNotFoundException e) {
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doFStat(buffer, id);
                 break;
             }
             case SSH_FXP_SETSTAT: {
-                String path = buffer.getString();
-                Map<Attribute, 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 (FileNotFoundException e) {
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                } catch (UnsupportedOperationException e) {
-                    sendStatus(id, SSH_FX_FAILURE, "");
-                }
+                doSetStat(buffer, id);
                 break;
             }
             case SSH_FXP_FSETSTAT: {
-                String handle = buffer.getString();
-                Map<Attribute, 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_FAILURE, handle);
-                    } else {
-                        setAttributes(p.getFile(), attrs);
-                        sendStatus(id, SSH_FX_OK, "");
-                    }
-                } catch (FileNotFoundException e) {
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException | UnsupportedOperationException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doFSetStat(buffer, id);
                 break;
             }
             case SSH_FXP_OPENDIR: {
-                String path = buffer.getString();
-                log.debug("Received SSH_FXP_OPENDIR (path={})", path);
-                try {
-                    Path p = resolveFile(path);
-                    if (!Files.exists(p)) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
-                    } else if (!Files.isDirectory(p)) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, 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, SSH_FX_FAILURE, e.getMessage());
-                }
+                doOpenDir(buffer, id);
                 break;
             }
             case SSH_FXP_READDIR: {
-                String handle = buffer.getString();
-                log.debug("Received SSH_FXP_READDIR (handle={})", handle);
-                try {
-                    Handle p = handles.get(handle);
-                    if (!(p instanceof DirectoryHandle)) {
-                        sendStatus(id, SSH_FX_FAILURE, handle);
-                    } else if (((DirectoryHandle) p).isDone()) {
-                        sendStatus(id, SSH_FX_EOF, "", "");
-                    } else if (!Files.exists(p.getFile())) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().toString());
-                    } else if (!Files.isDirectory(p.getFile())) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().toString());
-                    } else if (!Files.isReadable(p.getFile())) {
-                        sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().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, SSH_FX_FAILURE, e.getMessage());
-                }
+                doReadDir(buffer, id);
                 break;
             }
             case SSH_FXP_REMOVE: {
-                String path = buffer.getString();
-                log.debug("Received SSH_FXP_REMOVE (path={})", path);
-                try {
-                    Path p = resolveFile(path);
-                    if (!Files.exists(p)) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-                    } else if (Files.isDirectory(p)) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-                    } else {
-                        Files.delete(p);
-                        sendStatus(id, SSH_FX_OK, "");
-                    }
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doRemove(buffer, id);
                 break;
             }
             case SSH_FXP_MKDIR: {
-                String path = buffer.getString();
-                Map<Attribute, Object> attrs = readAttrs(buffer);
-
-                log.debug("Received SSH_FXP_MKDIR (path={})", path);
-                // attrs
-                try {
-                    Path p = resolveFile(path);
-                    if (Files.exists(p)) {
-                        if (Files.isDirectory(p)) {
-                            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 (AccessDeniedException e) {
-                    sendStatus(id, SSH_FX_PERMISSION_DENIED, e.getMessage());
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doMakeDirectory(buffer, id);
                 break;
             }
             case SSH_FXP_RMDIR: {
-                String path = buffer.getString();
-                log.debug("Received SSH_FXP_RMDIR (path={})", path);
-                // attrs
-                try {
-                    Path p = resolveFile(path);
-                    if (Files.isDirectory(p)) {
-                        Files.delete(p);
-                        sendStatus(id, SSH_FX_OK, "");
-                    } else {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-                    }
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doRemoveDirectory(buffer, id);
                 break;
             }
             case SSH_FXP_REALPATH: {
-                String path = buffer.getString();
-                log.debug("Received SSH_FXP_REALPATH (path={})", path);
-                if (path.trim().length() == 0) {
-                    path = ".";
-                }
-                try {
-                    Path p = resolveFile(path).toAbsolutePath().normalize();
-                    sendPath(id, p, false);
-                } catch (FileNotFoundException e) {
-                    e.printStackTrace();
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException e) {
-                    e.printStackTrace();
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doRealPath(buffer, id);
                 break;
             }
             case SSH_FXP_STAT: {
-                String path = buffer.getString();
-                log.debug("Received SSH_FXP_STAT (path={})", path);
-                try {
-                    Path p = resolveFile(path);
-                    sendAttrs(id, p, true);
-                } catch (FileNotFoundException e) {
-                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doStat(buffer, id);
                 break;
             }
             case SSH_FXP_RENAME: {
-                String oldPath = buffer.getString();
-                String newPath = buffer.getString();
-                log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={})", oldPath, newPath);
-                try {
-                    Path o = resolveFile(oldPath);
-                    Path n = resolveFile(newPath);
-                    if (!Files.exists(o)) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, o.toString());
-                    } else if (Files.exists(n)) {
-                        sendStatus(id, SSH_FX_FAILURE, n.toString());
-                    } else {
-                        Files.move(o, n);
-                        sendStatus(id, SSH_FX_OK, "");
-                    }
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doRename(buffer, id);
                 break;
             }
             case SSH_FXP_READLINK: {
-                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 " + type + " is unsupported or not implemented");
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                doReadLink(buffer, id);
                 break;
             }
             case SSH_FXP_SYMLINK: {
-                String linkpath = buffer.getString();
-                String targetpath = 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 " + type + " is unsupported or not implemented");
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                }
+                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: {
@@ -904,6 +773,627 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
     }
 
+    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.error("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();
+        log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}, line={})", handle, 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();
+        log.debug("Received SSH_FXP_EXTENDED(version-select) (version={})", version);
+        if (Integer.toString(SFTP_V3).equals(ver)) {
+            version = SFTP_V3;
+        } else if (Integer.toString(SFTP_V4).equals(ver)) {
+            version = SFTP_V4;
+        } else if (Integer.toString(SFTP_V5).equals(ver)) {
+            version = SFTP_V5;
+        } else if (Integer.toString(SFTP_V6).equals(ver)) {
+            version = SFTP_V6;
+        } else {
+            sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + ver);
+            return;
+        }
+        sendStatus(id, SSH_FX_OK, "");
+    }
+
+    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();
+        log.debug("Received SSH_FXP_BLOCK (handle={}, offset={}, length={}, mask={})", new Object[] { handle, offset, length, 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();
+        log.debug("Received SSH_FXP_UNBLOCK (handle={}, offset={}, length={})", new Object[] { handle, offset, 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();
+        log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})", new Object[] { linkpath, targetpath, 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();
+        }
+        log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags={})", new Object[] { oldPath, newPath, 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();
+        }
+        log.debug("Received SSH_FXP_STAT (path={}, flags={})", path, 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);
+        if (path.trim().length() == 0) {
+            path = ".";
+        }
+        try {
+            if (version < SFTP_V6) {
+                Path p = resolveFile(path).toAbsolutePath().normalize();
+                if (!Files.exists(p)) {
+                    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 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)) {
+                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);
+            if (Files.exists(p)) {
+                if (Files.isDirectory(p)) {
+                    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);
+            if (!Files.exists(p)) {
+                sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+            } else if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) {
+                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);
+        try {
+            Handle p = handles.get(handle);
+            if (!(p instanceof DirectoryHandle)) {
+                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+            } else if (((DirectoryHandle) p).isDone()) {
+                sendStatus(id, SSH_FX_EOF, "", "");
+            } else if (!Files.exists(p.getFile())) {
+                sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().toString());
+            } else if (!Files.isDirectory(p.getFile())) {
+                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, p.getFile().toString());
+            } else if (!Files.isReadable(p.getFile())) {
+                sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().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);
+            if (!Files.exists(p)) {
+                sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+            } else if (!Files.isDirectory(p)) {
+                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();
+        }
+        log.debug("Received SSH_FXP_FSTAT (handle={}, flags={})", handle, 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();
+        }
+        log.debug("Received SSH_FXP_LSTAT (path={}, flags={})", path, 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();
+        byte[] data = buffer.getBytes();
+        log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])", new Object[] { handle, offset, data.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, 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();
+        log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})", new Object[]{handle, offset, len});
+        try {
+            Handle p = handles.get(handle);
+            if (!(p instanceof FileHandle)) {
+                sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+            } else {
+                FileHandle fh = (FileHandle) p;
+                byte[] b = new byte[Math.min(len, Buffer.MAX_LEN)];
+                len = fh.read(b, offset);
+                if (len >= 0) {
+                    Buffer buf = new Buffer(len + 5);
+                    buf.putByte((byte) SSH_FXP_DATA);
+                    buf.putInt(id);
+                    buf.putBytes(b, 0, 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 {
+        if (session.getFactoryManager().getProperties() != null) {
+            String maxHandlesString = session.getFactoryManager().getProperties().get(MAX_OPEN_HANDLES_PER_SESSION);
+            if (maxHandlesString != null) {
+                int maxHandleCount = Integer.parseInt(maxHandlesString);
+                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);
+        log.debug("Received SSH_FXP_OPEN (path={}, access={}, pflags={}, attrs={})", new Object[]{path, access, 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 {
+        log.debug("Received SSH_FXP_INIT (version={})", id);
+        version = id;
+        while (buffer.available() > 0) {
+            String name = buffer.getString();
+            byte[] data = buffer.getBytes();
+            extensions.put(name, data);
+        }
+
+        int low = LOWER_SFTP_IMPL;
+        int hig = HIGHER_SFTP_IMPL;
+        String all = ALL_SFTP_IMPL;
+
+        if (session.getFactoryManager().getProperties() != null) {
+            String sftpVersion = session.getFactoryManager().getProperties().get(SFTP_VERSION);
+            if (sftpVersion != null) {
+                low = hig = Integer.parseInt(sftpVersion);
+                all = sftpVersion;
+            }
+        }
+        if (version >= low) {
+            version = Math.min(version, hig);
+            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);
+        } else {
+            // We only support version >= 3 (Version 1 and 2 are not common)
+            sendStatus(id, SSH_FX_OP_UNSUPPORTED, "SFTP server only support versions " + all);
+        }
+    }
+
     protected void sendHandle(int id, String handle) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putByte((byte) SSH_FXP_HANDLE);
@@ -912,19 +1402,15 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         send(buffer);
     }
 
-    protected void sendAttrs(int id, Path file, boolean followLinks) throws IOException {
+    protected void sendAttrs(int id, Path file, int flags, boolean followLinks) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putByte((byte) SSH_FXP_ATTRS);
         buffer.putInt(id);
-        writeAttrs(buffer, file, followLinks);
+        writeAttrs(buffer, file, flags, followLinks);
         send(buffer);
     }
 
-    protected void sendPath(int id, Path f) throws IOException {
-        sendPath(id, f, true);
-    }
-
-    protected void sendPath(int id, Path f, boolean sendAttrs) throws IOException {
+    protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putByte((byte) SSH_FXP_NAME);
         buffer.putInt(id);
@@ -934,13 +1420,19 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         if (normalizedPath.length() == 0) {
             normalizedPath = "/";
         }
-        buffer.putString(normalizedPath);
+        buffer.putString(normalizedPath, StandardCharsets.UTF_8);
         f = resolveFile(normalizedPath);
         if (f.getFileName() == null) {
             f = resolveFile(".");
         }
-        buffer.putString(getLongName(f, sendAttrs)); // Format specified in the specs
-        buffer.putInt(0);
+        if (version == SFTP_V3) {
+            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();
+        }
         send(buffer);
     }
 
@@ -965,9 +1457,11 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         int nb = 0;
         while (files.hasNext() && buffer.wpos() < MAX_PACKET_LENGTH) {
             Path f = files.next();
-            buffer.putString(f.getFileName().toString());
-            buffer.putString(getLongName(f)); // Format specified in the specs
-            writeAttrs(buffer, f, false);
+            buffer.putString(f.getFileName().toString(), StandardCharsets.UTF_8);
+            if (version == SFTP_V3) {
+                buffer.putString(getLongName(f), StandardCharsets.UTF_8); // Format specified in the specs
+            }
+            writeAttrs(buffer, f, SSH_FILEXFER_ATTR_ALL, false);
             nb++;
         }
         int oldpos = buffer.wpos();
@@ -982,21 +1476,22 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     }
 
     private String getLongName(Path f, boolean sendAttrs) throws IOException {
-        Map<Attribute, Object> attributes;
+        Map<String, Object> attributes;
         if (sendAttrs) {
             attributes = getAttributes(f, false);
         } else {
-            attributes = new HashMap<>();
-            attributes.put(Attribute.Owner, "owner");
-            attributes.put(Attribute.Group, "group");
-            attributes.put(Attribute.Size, (long) 0);
-            attributes.put(Attribute.IsDirectory, false);
-            attributes.put(Attribute.IsSymbolicLink, false);
-            attributes.put(Attribute.IsRegularFile, false);
-            attributes.put(Attribute.Permissions, EnumSet.noneOf(Permission.class));
-            attributes.put(Attribute.LastModifiedTime, (long) 0);
-        }
-        String username = (String) attributes.get(Attribute.Owner);
+            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 {
@@ -1004,7 +1499,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 username = username + " ";
             }
         }
-        String group = (String) attributes.get(Attribute.Group);
+        String group;
+        if (attributes.containsKey("group")) {
+            group = attributes.get("group").toString();
+        } else {
+            group = "group";
+        }
         if (group.length() > 8) {
             group = group.substring(0, 8);
         } else {
@@ -1013,27 +1513,25 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             }
         }
 
-        long length = (Long) attributes.get(Attribute.Size);
+        Long length = (Long) attributes.get("size");
+        if (length == null) {
+            length = 0l;
+        }
         String lengthString = String.format("%1$8s", length);
 
-        boolean isDirectory = (Boolean) attributes.get(Attribute.IsDirectory);
-        boolean isLink = (Boolean) attributes.get(Attribute.IsSymbolicLink);
-        int perms = getPermissions(attributes);
+        Boolean isDirectory = (Boolean) attributes.get("isDirectory");
+        Boolean isLink = (Boolean) attributes.get("isSymbolicLink");
+        Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+        if (perms == null) {
+            perms = new HashSet<>();
+        }
 
         StringBuilder sb = new StringBuilder();
-        sb.append(isDirectory ? "d" : isLink ? "l" : "-");
-        sb.append((perms & S_IRUSR) != 0 ? "r" : "-");
-        sb.append((perms & S_IWUSR) != 0 ? "w" : "-");
-        sb.append((perms & S_IXUSR) != 0 ? "x" : "-");
-        sb.append((perms & S_IRGRP) != 0 ? "r" : "-");
-        sb.append((perms & S_IWGRP) != 0 ? "w" : "-");
-        sb.append((perms & S_IXGRP) != 0 ? "x" : "-");
-        sb.append((perms & S_IROTH) != 0 ? "r" : "-");
-        sb.append((perms & S_IWOTH) != 0 ? "w" : "-");
-        sb.append((perms & S_IXOTH) != 0 ? "x" : "-");
+        sb.append((isDirectory != null && isDirectory) ? "d" : (isLink != null && isLink) ? "l" : "-");
+        sb.append(PosixFilePermissions.toString(perms));
         sb.append("  ");
-        sb.append(attributes.containsKey(Attribute.NLink)
-                ? attributes.get(Attribute.NLink) : "1");
+        sb.append(attributes.containsKey("nlink")
+                ? attributes.get("nlink") : "1");
         sb.append(" ");
         sb.append(username);
         sb.append(" ");
@@ -1041,73 +1539,50 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         sb.append(" ");
         sb.append(lengthString);
         sb.append(" ");
-        sb.append(getUnixDate((Long) attributes.get(Attribute.LastModifiedTime)));
+        sb.append(getUnixDate((FileTime) attributes.get("lastModifiedTime")));
         sb.append(" ");
         sb.append(f.getFileName().toString());
 
         return sb.toString();
     }
 
-    protected Map<Attribute, Object> getPermissions(int perms) {
-        Map<Attribute, Object> attrs = new HashMap<>();
-        if ((perms & S_IFMT) == S_IFREG) {
-            attrs.put(Attribute.IsRegularFile, Boolean.TRUE);
-        }
-        if ((perms & S_IFMT) == S_IFDIR) {
-            attrs.put(Attribute.IsDirectory, Boolean.TRUE);
-        }
-        if ((perms & S_IFMT) == S_IFLNK) {
-            attrs.put(Attribute.IsSymbolicLink, Boolean.TRUE);
-        }
-        EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
-        if ((perms & S_IRUSR) != 0) {
-            p.add(Permission.UserRead);
-        }
-        if ((perms & S_IWUSR) != 0) {
-            p.add(Permission.UserWrite);
-        }
-        if ((perms & S_IXUSR) != 0) {
-            p.add(Permission.UserExecute);
-        }
-        if ((perms & S_IRGRP) != 0) {
-            p.add(Permission.GroupRead);
-        }
-        if ((perms & S_IWGRP) != 0) {
-            p.add(Permission.GroupWrite);
-        }
-        if ((perms & S_IXGRP) != 0) {
-            p.add(Permission.GroupExecute);
-        }
-        if ((perms & S_IROTH) != 0) {
-            p.add(Permission.OthersRead);
-        }
-        if ((perms & S_IWOTH) != 0) {
-            p.add(Permission.OthersWrite);
-        }
-        if ((perms & S_IXOTH) != 0) {
-            p.add(Permission.OthersExecute);
-        }
-        attrs.put(Attribute.Permissions, p);
-        return attrs;
-    }
-
-    protected int getPermissions(Map<Attribute, Object> attributes) {
-        boolean isReg = (Boolean) attributes.get(Attribute.IsRegularFile);
-        boolean isDir = (Boolean) attributes.get(Attribute.IsDirectory);
-        boolean isLnk = (Boolean) attributes.get(Attribute.IsSymbolicLink);
+    protected int attributesToPermissions(Map<String, Object> attributes) {
+        boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+        boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+        boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
         int pf = 0;
-        EnumSet<Permission> perms = (EnumSet<Permission>) attributes.get(Attribute.Permissions);
-        for (Permission p : perms) {
-            switch (p) {
-                case UserRead:      pf |= S_IRUSR; break;
-                case UserWrite:     pf |= S_IWUSR; break;
-                case UserExecute:   pf |= S_IXUSR; break;
-                case GroupRead:     pf |= S_IRGRP; break;
-                case GroupWrite:    pf |= S_IWGRP; break;
-                case GroupExecute:  pf |= S_IXGRP; break;
-                case OthersRead:    pf |= S_IROTH; break;
-                case OthersWrite:   pf |= S_IWOTH; break;
-                case OthersExecute: pf |= S_IXOTH; break;
+        Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+        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;
+                }
             }
         }
         pf |= isReg ? S_IFREG : 0;
@@ -1116,41 +1591,93 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         return pf;
     }
 
-    protected void writeAttrs(Buffer buffer, Path file, boolean followLinks) throws IOException {
-        if (!Files.exists(file)) {
+    protected void writeAttrs(Buffer buffer, Path file, int flags, boolean followLinks) throws IOException {
+        LinkOption[] options = followLinks ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
+        if (!Files.exists(file, options)) {
             throw new FileNotFoundException(file.toString());
         }
-        Map<Attribute, Object> attributes = getAttributes(file, followLinks);
-        boolean isReg = getBool((Boolean) attributes.get(Attribute.IsRegularFile));
-        boolean isDir = getBool((Boolean) attributes.get(Attribute.IsDirectory));
-        boolean isLnk = getBool((Boolean) attributes.get(Attribute.IsSymbolicLink));
-        int flags = 0;
-        if ((isReg || isLnk) && attributes.containsKey(Attribute.Size)) {
-            flags |= SSH_FILEXFER_ATTR_SIZE;
-        }
-        if (attributes.containsKey(Attribute.Uid) && attributes.containsKey(Attribute.Gid)) {
-            flags |= SSH_FILEXFER_ATTR_UIDGID;
-        }
-        if (attributes.containsKey(Attribute.Permissions)) {
-            flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
-        }
-        if (attributes.containsKey(Attribute.LastAccessTime) && attributes.containsKey(Attribute.LastModifiedTime)) {
-            flags |= SSH_FILEXFER_ATTR_ACMODTIME;
-        }
-        buffer.putInt(flags);
-        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-            buffer.putLong((Long) attributes.get(Attribute.Size));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-            buffer.putInt((Integer) attributes.get(Attribute.Uid));
-            buffer.putInt((Integer) attributes.get(Attribute.Gid));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-            buffer.putInt(getPermissions(attributes));
-        }
-        if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-            buffer.putInt(((Long) attributes.get(Attribute.LastAccessTime)) / 1000);
-            buffer.putInt(((Long) attributes.get(Attribute.LastModifiedTime)) / 1000);
+        Map<String, Object> attributes = getAttributes(file, flags, followLinks);
+        writeAttrs(buffer, attributes);
+    }
+
+    protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
+        if (version == SFTP_V3) {
+            boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+            boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+            boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
+            int flags =
+                    ((isReg || isLnk) && attributes.containsKey("size") ? SSH_FILEXFER_ATTR_SIZE : 0) |
+                    (attributes.containsKey("uid") && attributes.containsKey("gid") ? SSH_FILEXFER_ATTR_UIDGID : 0) |
+                    (attributes.containsKey("permissions") ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+                    (attributes.containsKey("lastModifiedTime") && attributes.containsKey("lastAccessTime") ? SSH_FILEXFER_ATTR_ACMODTIME : 0);
+            buffer.putInt(flags);
+            if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+                buffer.putLong((Long) attributes.get("size"));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+                buffer.putInt((Integer) attributes.get("uid"));
+                buffer.putInt((Integer) attributes.get("gid"));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+                buffer.putInt(attributesToPermissions(attributes));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+                buffer.putInt(((FileTime) attributes.get("lastAccessTime")).to(TimeUnit.SECONDS));
+                buffer.putInt(((FileTime) attributes.get("lastModifiedTime")).to(TimeUnit.SECONDS));
+            }
+        } else if (version >= SFTP_V4) {
+            boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+            boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+            boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
+            int flags =
+                    ((isReg || isLnk) && attributes.containsKey("size") ? SSH_FILEXFER_ATTR_SIZE : 0) |
+                            (attributes.containsKey("owner") && attributes.containsKey("group") ? SSH_FILEXFER_ATTR_OWNERGROUP : 0) |
+                            (attributes.containsKey("permissions") ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+                            (attributes.containsKey("lastModifiedTime") ? SSH_FILEXFER_ATTR_MODIFYTIME : 0) |
+                            (attributes.containsKey("creationTime") ? SSH_FILEXFER_ATTR_CREATETIME : 0) |
+                            (attributes.containsKey("lastAccessTime") ? 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((Long) attributes.get("size"));
+            }
+            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(attributes));
+            }
+            if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+                buffer.putLong(((FileTime) attributes.get("lastAccessTime")).to(TimeUnit.SECONDS));
+                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+                    long nanos = ((FileTime) attributes.get("lastAccessTime")).to(TimeUnit.NANOSECONDS);
+                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+                    buffer.putInt((int) nanos);
+                }
+            }
+            if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+                buffer.putLong(((FileTime) attributes.get("creationTime")).to(TimeUnit.SECONDS));
+                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+                    long nanos = ((FileTime) attributes.get("creationTime")).to(TimeUnit.NANOSECONDS);
+                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+                    buffer.putInt((int) nanos);
+                }
+            }
+            if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+                buffer.putLong(((FileTime) attributes.get("lastModifiedTime")).to(TimeUnit.SECONDS));
+                if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+                    long nanos = ((FileTime) attributes.get("lastModifiedTime")).to(TimeUnit.NANOSECONDS);
+                    nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+                    buffer.putInt((int) nanos);
+                }
+            }
+            // TODO: acls
+            // TODO: bits
+            // TODO: extended
         }
     }
 
@@ -1158,98 +1685,51 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         return bool != null && bool;
     }
 
-    protected Map<Attribute, Object> getAttributes(Path file, boolean followLinks) throws IOException {
-        String[] attrs = new String[] { "unix:*", "posix:*", "*" };
-        Map<String, Object> a = null;
-        for (String attr : attrs) {
-            try {
-                a = Files.readAttributes(
-                        file, attr,
-                        followLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
-                break;
-            } catch (UnsupportedOperationException e) {
-                // Ignore
-            }
-        }
-        if (a == null) {
-            throw new IllegalStateException();
-        }
-        Map<Attribute, Object> map = new HashMap<>();
-        map.put(Attribute.Size, a.get("size"));
-        if (a.containsKey("uid")) {
-            map.put(Attribute.Uid, a.get("uid"));
-        }
-        if (a.containsKey("owner")) {
-            map.put(Attribute.Owner, ((UserPrincipal) a.get("owner")).getName());
-        } else {
-            map.put(Attribute.Owner, session.getUsername());
-        }
-        if (a.containsKey("gid")) {
-            map.put(Attribute.Gid, a.get("gid"));
-        }
-        if (a.containsKey("group")) {
-            map.put(Attribute.Group, ((GroupPrincipal) a.get("group")).getName());
-        } else {
-            map.put(Attribute.Group, session.getUsername());
-        }
-        if (a.containsKey("nlink")) {
-            map.put(Attribute.NLink, a.get("nlink"));
-        }
-        map.put(Attribute.IsDirectory, a.get("isDirectory"));
-        map.put(Attribute.IsRegularFile, a.get("isRegularFile"));
-        map.put(Attribute.IsSymbolicLink, a.get("isSymbolicLink"));
-        map.put(Attribute.CreationTime, ((FileTime) a.get("creationTime")).toMillis());
-        map.put(Attribute.LastModifiedTime, ((FileTime) a.get("lastModifiedTime")).toMillis());
-        map.put(Attribute.LastAccessTime, ((FileTime) a.get("lastAccessTime")).toMillis());
-        if (a.containsKey("permissions")) {
-            map.put(Attribute.Permissions, fromPerms((Set<PosixFilePermission>) a.get("permissions")));
+    protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
+        return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
+    }
+
+    protected Map<String, Object

<TRUNCATED>

[13/15] mina-sshd git commit: [SSHD-378] Switch to nio FileSystem api for commands (scp and sftp subsystem)

Posted by gn...@apache.org.
[SSHD-378] Switch to nio FileSystem api for commands (scp and sftp subsystem)


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

Branch: refs/heads/master
Commit: 345604ba5804ce5ef03242ee9e83757b536e1274
Parents: cc4d787
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Dec 1 08:25:25 2014 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 .../java.nio.file.spi.FileSystemProvider        |   1 +
 .../sshd/client/scp/DefaultScpClient.java       |  82 ++-
 .../client/sftp/SftpFileSystemProvider.java     |   8 +-
 .../sshd/common/file/FileSystemAware.java       |   6 +-
 .../sshd/common/file/FileSystemFactory.java     |  11 +-
 .../apache/sshd/common/file/FileSystemView.java |  53 --
 .../org/apache/sshd/common/file/SshFile.java    | 234 ------
 .../file/nativefs/NameEqualsFileFilter.java     |  61 --
 .../file/nativefs/NativeFileSystemFactory.java  |  29 +-
 .../file/nativefs/NativeFileSystemView.java     | 431 -----------
 .../common/file/nativefs/NativeSshFile.java     | 701 ------------------
 .../common/file/nativefs/NativeSshFileNio.java  | 260 -------
 .../sshd/common/file/root/RootedFileSystem.java |  76 ++
 .../file/root/RootedFileSystemProvider.java     | 310 ++++++++
 .../sshd/common/file/root/RootedPath.java       |  48 ++
 .../virtualfs/VirtualFileSystemFactory.java     |  13 +-
 .../org/apache/sshd/common/scp/ScpHelper.java   | 306 +++++---
 .../sshd/server/channel/ChannelSession.java     |   2 +-
 .../apache/sshd/server/command/ScpCommand.java  |  20 +-
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 707 ++++++++++++-------
 .../org/apache/sshd/SftpFileSystemTest.java     |  16 +-
 .../src/test/java/org/apache/sshd/SftpTest.java |   2 +-
 .../file/nativefs/NativeFileSystemViewTest.java | 115 ---
 .../common/file/nativefs/NativeSshFileTest.java |  50 --
 .../file/virtualfs/VirtualFileSystemTest.java   | 107 ---
 25 files changed, 1172 insertions(+), 2477 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/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 08d4b4a..7691b7c 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
@@ -18,3 +18,4 @@
 ##
 
 org.apache.sshd.client.sftp.SftpFileSystemProvider
+org.apache.sshd.common.file.root.RootedFileSystemProvider

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index 7527102..c0b23a0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -20,10 +20,12 @@ package org.apache.sshd.client.scp;
 
 import java.io.IOException;
 import java.io.InterruptedIOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 import org.apache.sshd.ClientSession;
@@ -31,8 +33,6 @@ import org.apache.sshd.client.ScpClient;
 import org.apache.sshd.client.channel.ChannelExec;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
 import org.apache.sshd.common.scp.ScpHelper;
 
 /**
@@ -81,33 +81,41 @@ public class DefaultScpClient implements ScpClient {
         sb.append(remote);
 
         FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
-        FileSystemView fs = factory.createFileSystemView(clientSession);
-        SshFile target = fs.getFile(local);
-        if (options.contains(Option.TargetIsDirectory)) {
-            if (!target.doesExist()) {
-                throw new SshException("Target directory " + target.toString() + " does not exists");
-            }
-            if (!target.isDirectory()) {
-                throw new SshException("Target directory " + target.toString() + " is not a directory");
+        FileSystem fs = factory.createFileSystem(clientSession);
+        try {
+            Path target = fs.getPath(local);
+            if (options.contains(Option.TargetIsDirectory)) {
+                if (!Files.exists(target)) {
+                    throw new SshException("Target directory " + target.toString() + " does not exists");
+                }
+                if (!Files.isDirectory(target)) {
+                    throw new SshException("Target directory " + target.toString() + " is not a directory");
+                }
             }
-        }
 
-        ChannelExec channel = clientSession.createExecChannel(sb.toString());
-        try {
-            channel.open().await();
-        } catch (InterruptedException e) {
-            throw (IOException) new InterruptedIOException().initCause(e);
-        }
+            ChannelExec channel = clientSession.createExecChannel(sb.toString());
+            try {
+                channel.open().await();
+            } catch (InterruptedException e) {
+                throw (IOException) new InterruptedIOException().initCause(e);
+            }
 
-        ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
+            ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
 
-        helper.receive(target,
-                       options.contains(Option.Recursive),
-                       options.contains(Option.TargetIsDirectory),
-                       options.contains(Option.PreserveAttributes),
-                       ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE);
+            helper.receive(target,
+                           options.contains(Option.Recursive),
+                           options.contains(Option.TargetIsDirectory),
+                           options.contains(Option.PreserveAttributes),
+                           ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE);
 
-        channel.close(false);
+            channel.close(false);
+        } finally {
+            try {
+                fs.close();
+            } catch (UnsupportedOperationException e) {
+                // Ignore
+            }
+        }
     }
 
     public void upload(String local, String remote, Option... options) throws IOException {
@@ -151,19 +159,25 @@ public class DefaultScpClient implements ScpClient {
         }
 
         FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
-        FileSystemView fs = factory.createFileSystemView(clientSession);
-        ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
-
-        helper.send(Arrays.asList(local),
-                    options.contains(Option.Recursive),
-                    options.contains(Option.PreserveAttributes),
-                    ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
-
+        FileSystem fs = factory.createFileSystem(clientSession);
+        try {
+            ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
+            helper.send(Arrays.asList(local),
+                        options.contains(Option.Recursive),
+                        options.contains(Option.PreserveAttributes),
+                        ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
+        } finally {
+            try {
+                fs.close();
+            } catch (UnsupportedOperationException e) {
+                // Ignore
+            }
+        }
         channel.close(false);
     }
 
     private List<Option> options(Option... options) {
-        List<Option> opts = new ArrayList<Option>();
+        List<Option> opts = new ArrayList<>();
         if (options != null) {
             opts.addAll(Arrays.asList(options));
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/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
index 07c0dc2..ca60647 100644
--- 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
@@ -240,9 +240,11 @@ public class SftpFileSystemProvider extends FileSystemProvider {
 
             @Override
             public void close() throws IOException {
-                sftp.close(handle);
-                sftp.close();
-                pos = -1;
+                if (pos >= 0) {
+                    sftp.close(handle);
+                    sftp.close();
+                    pos = -1;
+                }
             }
         };
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemAware.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemAware.java b/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemAware.java
index ae0d66b..b3a7bf4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemAware.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemAware.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sshd.common.file;
 
+import java.nio.file.FileSystem;
+
 /**
  * Interface that can be implemented by a command to be able to access the
  * file system in which this command will be used.
@@ -26,7 +28,7 @@ public interface FileSystemAware {
     /**
      * Set the file system in which this shell will be executed.
      *
-     * @param view
+     * @param fileSystem
      */
-    void setFileSystemView(FileSystemView view);
+    void setFileSystem(FileSystem fileSystem);
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemFactory.java
index 6acc99c..6692c44 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemFactory.java
@@ -20,22 +20,23 @@
 package org.apache.sshd.common.file;
 
 import java.io.IOException;
+import java.nio.file.FileSystem;
 
 import org.apache.sshd.common.Session;
 
 /**
- * Factory for file system implementations - it returns the file system view for user.
+ * Factory for file system implementations - it returns the file system for user.
  *
  * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  */
 public interface FileSystemFactory {
 
     /**
-     * Create user specific file system view.
+     * Create user specific file system.
      * @param session The session created for the user
-     * @return The current {@link FileSystemView} for the provided session
-     * @throws IOException when the filesystem view can not be created
+     * @return The current {@link FileSystem} for the provided session
+     * @throws java.io.IOException when the filesystem can not be created
      */
-    FileSystemView createFileSystemView(Session session) throws IOException;
+    FileSystem createFileSystem(Session session) throws IOException;
 
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemView.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemView.java b/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemView.java
deleted file mode 100644
index e2de5d5..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/FileSystemView.java
+++ /dev/null
@@ -1,53 +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.file;
-
-/**
- * This is an abstraction over the user file system view.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface FileSystemView {
-
-    /**
-     * Get file object.
-     * @param file The path to the file to get
-     * @return The {@link SshFile} for the provided path
-     */
-    SshFile getFile(String file);
-
-    /**
-     * Get file object.
-     * @param baseDir The reference towards which the file should be resolved
-     * @param file The path to the file to get
-     * @return The {@link SshFile} for the provided path
-     */
-    SshFile getFile(SshFile baseDir, String file);
-
-    /**
-     * Return a view of this file system which has a single root
-     * and uses '/' as a file separator.
-     * If the file system has multiple roots, they are mapped
-     * to directories inside that single root.
-     * @return a normalized file system view
-     */
-    FileSystemView getNormalizedView();
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
deleted file mode 100644
index a7a2372..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
+++ /dev/null
@@ -1,234 +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.file;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This is the file abstraction used by the server.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface SshFile {
-
-    //
-    // File attributes
-    //
-    enum Attribute {
-        Size,               // long
-        Uid,                // int
-        Owner,              // String
-        Gid,                // int
-        Group,              // String
-        IsDirectory,        // boolean
-        IsRegularFile,      // boolean
-        IsSymbolicLink,     // boolean
-        Permissions,        // EnumSet<Permission>
-        CreationTime,       // long
-        LastModifiedTime,   // long
-        LastAccessTime,     // long
-        NLink               // int
-    }
-
-    //
-    // File permissions
-    //
-    enum Permission {
-        UserRead,
-        UserWrite,
-        UserExecute,
-        GroupRead,
-        GroupWrite,
-        GroupExecute,
-        OthersRead,
-        OthersWrite,
-        OthersExecute
-    }
-
-
-    /**
-     * Get the full path from the base directory of the FileSystemView.
-     * @return a path where the path separator is '/' (even if the operating system
-     *     uses another character as path separator).
-     */
-    String getAbsolutePath();
-
-    /**
-     * Get the file name of the file
-     * @return the last part of the file path (the part after the last '/').
-     */
-    String getName();
-
-    Map<Attribute,Object> getAttributes(boolean followLinks) throws IOException;
-
-    void setAttributes(Map<Attribute, Object> attributes) throws IOException;
-
-    Object getAttribute(Attribute attribute, boolean followLinks) throws IOException;
-
-    void setAttribute(Attribute attribute, Object value) throws IOException;
-
-    String readSymbolicLink() throws IOException;
-
-    void createSymbolicLink(SshFile destination) throws IOException;
-
-
-    /**
-     * Get the owner name of the file
-     * @return the name of the owner.
-     */
-    String getOwner();
-
-    /**
-     * Is it a directory?
-     * @return true if the {@link SshFile} is a directory
-     */
-    boolean isDirectory();
-
-    /**
-     * Is it a file?
-     * @return true if the {@link SshFile} is a file, false if it is a directory
-     */
-    boolean isFile();
-
-    /**
-     * Does this file exists?
-     * @return true if the {@link SshFile} exists
-     */
-    boolean doesExist();
-
-    /**
-     * Has read permission?
-     * @return true if the {@link SshFile} is readable by the user
-     */
-    boolean isReadable();
-
-    /**
-     * Has write permission?
-     * @return true if the {@link SshFile} is writable by the user
-     */
-    boolean isWritable();
-
-    /**
-     * Has exec permission?
-     * @return true if the {@link SshFile} is executable by the user
-     */
-    boolean isExecutable();
-
-    /**
-     * Has delete permission?
-     * @return true if the {@link SshFile} is removable by the user
-     */
-    boolean isRemovable();
-
-    /**
-     * Get the immediate parent. Returns the root directory if the current file is the root.
-     * @return
-     */
-    SshFile getParentFile();
-
-    /**
-     * Get last modified time in UTC.
-     * @return The timestamp of the last modified time for the {@link SshFile}
-     */
-    long getLastModified();
-
-    /**
-     * Set the last modified time stamp of a file
-     * @param time The last modified time, in milliseconds since the epoch. See {@link java.io.File#setLastModified(long)}.
-     */
-    boolean setLastModified(long time);
-    
-    /**
-     * Get file size.
-     * @return The size of the {@link SshFile} in bytes
-     */
-    long getSize();
-
-    /**
-     * Create directory.
-     * @return true if the operation was successful
-     */
-    boolean mkdir();
-
-    /**
-     * Delete file.
-     * @return true if the operation was successful
-     */
-    boolean delete();
-
-    /**
-     * Create the file.
-     * @return true if the file has been created and false if it already exist
-     * @throws java.io.IOException if something wrong happen
-     */
-    boolean create() throws IOException;
-
-    /**
-     * Truncate the file to length 0.
-     * @throws java.io.IOException if something wrong happen
-     */
-    void truncate() throws IOException;
-
-    /**
-     * Move file.
-     * @param destination The target {@link SshFile} to move the current {@link SshFile} to
-     * @return true if the operation was successful
-     */
-    boolean move(SshFile destination);
-
-    /**
-     * List file objects. If not a directory or does not exist, null will be
-     * returned. Files must be returned in alphabetical order.
-     * List must be immutable.
-     * @return The {@link java.util.List} of {@link SshFile}s
-     */
-    List<SshFile> listSshFiles();
-
-    /**
-     * Create output stream for writing. 
-     * @param offset The number of bytes at where to start writing.
-     *      If the file is not random accessible,
-     *      any offset other than zero will throw an exception.
-     * @return An {@link java.io.OutputStream} used to write to the {@link SshFile}
-     * @throws java.io.IOException 
-     */
-    OutputStream createOutputStream(long offset) throws IOException;
-
-    /**
-     * Create input stream for reading. 
-     * @param offset The number of bytes of where to start reading. 
-     *          If the file is not random accessible,
-     *          any offset other than zero will throw an exception.
-     * @return An {@link java.io.InputStream} used to read the {@link SshFile}
-     * @throws java.io.IOException 
-     */
-    InputStream createInputStream(long offset) throws IOException;
-
-    /**
-     * Handle post-handle-close functionality.
-     * @throws IOException
-     */
-    void handleClose() throws IOException;
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NameEqualsFileFilter.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NameEqualsFileFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NameEqualsFileFilter.java
deleted file mode 100644
index 65693fb..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NameEqualsFileFilter.java
+++ /dev/null
@@ -1,61 +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.file.nativefs;
-
-import java.io.File;
-import java.io.FileFilter;
-
-/**
- * <strong>Internal class, do not use directly.</strong>
- * 
- * FileFilter used for simple file name matching
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class NameEqualsFileFilter implements FileFilter {
-
-    private String nameToMatch;
-
-    private boolean caseInsensitive = false;
-
-    /**
-     * Constructor
-     * 
-     * @param nameToMatch
-     *            The exact file name to match
-     * @param caseInsensitive
-     *            Wether that match should be case insensitive
-     */
-    public NameEqualsFileFilter(final String nameToMatch,
-            final boolean caseInsensitive) {
-        this.nameToMatch = nameToMatch;
-        this.caseInsensitive = caseInsensitive;
-    }
-
-    public boolean accept(final File file) {
-
-        if (caseInsensitive) {
-            return file.getName().equalsIgnoreCase(nameToMatch);
-        } else {
-            return file.getName().equals(nameToMatch);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java
index 50e7f73..1561dbc 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java
@@ -20,10 +20,11 @@
 package org.apache.sshd.common.file.nativefs;
 
 import java.io.File;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
 
 import org.apache.sshd.common.Session;
 import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.FileSystemView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,8 +39,6 @@ public class NativeFileSystemFactory implements FileSystemFactory {
 
     private boolean createHome;
 
-    private boolean caseInsensitive;
-
     /**
      * Should the home directories be created automatically
      * @return true if the file system will create the home directory if not available
@@ -58,27 +57,9 @@ public class NativeFileSystemFactory implements FileSystemFactory {
     }
 
     /**
-     * Is this file system case insensitive.
-     * Enabling might cause problems when working against case-sensitive file systems, like on Linux
-     * @return true if this file system is case insensitive
-     */
-    public boolean isCaseInsensitive() {
-        return caseInsensitive;
-    }
-
-    /**
-     * Should this file system be case insensitive.
-     * Enabling might cause problems when working against case-sensitive file systems, like on Linux
-     * @param caseInsensitive true if this file system should be case insensitive
-     */
-    public void setCaseInsensitive(boolean caseInsensitive) {
-        this.caseInsensitive = caseInsensitive;
-    }
-
-    /**
      * Create the appropriate user file system view.
      */
-    public FileSystemView createFileSystemView(Session session) {
+    public FileSystem createFileSystem(Session session) {
         String userName = session.getUsername();
         // create home if does not exist
         if (createHome) {
@@ -95,7 +76,7 @@ public class NativeFileSystemFactory implements FileSystemFactory {
             }
         }
 
-        FileSystemView fsView = new NativeFileSystemView(userName, caseInsensitive);
-        return fsView;
+        return FileSystems.getDefault();
     }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java
deleted file mode 100644
index 9337349..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java
+++ /dev/null
@@ -1,431 +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.file.nativefs;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.sshd.common.file.nativefs.NativeSshFile.normalizeSeparateChar;
-
-/**
- * <strong>Internal class, do not use directly.</strong>
- * 
- * File system view based on native file system. Here the root directory will be
- * user virtual root (/).
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class NativeFileSystemView implements FileSystemView {
-
-    public enum UnsupportedAttributePolicy {
-        Ignore,
-        Warn,
-        ThrowException
-    }
-
-    private final Logger LOG = LoggerFactory.getLogger(NativeFileSystemView.class);
-
-
-    private Map<String, String> roots;
-
-    // the first and the last character will always be '/'
-    // It is always with respect to one of the roots.
-    private String current;
-
-    private String userName;
-
-    private char separator;
-
-    private boolean caseInsensitive = false;
-
-    private UnsupportedAttributePolicy unsupportedAttributePolicy = UnsupportedAttributePolicy.Warn;
-
-    /**
-     * Constructor - internal do not use directly, use {@link NativeFileSystemFactory} instead
-     */
-    public NativeFileSystemView(String userName) {
-        this(userName, false);
-    }
-
-    /**
-     * Constructor - internal do not use directly, use {@link NativeFileSystemFactory} instead
-     */
-    public NativeFileSystemView(String userName, boolean caseInsensitive) {
-        this(userName, getAllRoots(), System.getProperty("user.dir"), File.separatorChar, caseInsensitive);
-    }
-
-    /**
-     * Constructor - internal do not use directly, use {@link NativeFileSystemFactory} instead
-     */
-    public NativeFileSystemView(String userName, Map<String, String> roots, String current) {
-        this(userName, roots, current, File.separatorChar, false);
-    }
-
-    /**
-     * Constructor - internal do not use directly, use {@link NativeFileSystemFactory} instead
-     *
-     * @param userName the user name
-     * @param roots known root mapping, key is the virtual root name, value is the physical file
-     * @param current the virtual current dir
-     */
-    public NativeFileSystemView(String userName, Map<String, String> roots, String current, char separator, boolean caseInsensitive) {
-        if (userName == null) {
-            throw new IllegalArgumentException("user can not be null");
-        }
-        // Normalize roots
-        Map<String, String> verRoots = new LinkedHashMap<String, String>();
-        for (String r : roots.keySet()) {
-            String virtual = appendSlash(normalizeSeparateChar(r));
-            String physical = appendSlash(normalizeSeparateChar(roots.get(r)));
-            verRoots.put(virtual, physical);
-        }
-        // add last '/' if necessary
-        current = appendSlash(normalizeSeparateChar(current));
-        // Verify the current dir is relative to a known root
-        String root = null;
-        for (String r : verRoots.keySet()) {
-            if (current.startsWith(r)) {
-                root = r;
-                break;
-            }
-        }
-        if (root == null) {
-            throw new IllegalArgumentException("Current dir " + current + " does not start from a known root: " + new ArrayList<String>(verRoots.keySet()));
-        }
-
-        this.separator = separator;
-        this.caseInsensitive = caseInsensitive;
-        this.roots = verRoots;
-        this.current = current;
-        this.userName = userName;
-        LOG.debug("Native filesystem view created for user \"{}\" with current dir \"{}\"", userName, this.current);
-    }
-
-    private String appendSlash(String path) {
-        return path.endsWith("/") ? path : path + "/";
-    }
-
-    private static Map<String, String> getAllRoots() {
-        Map<String, String> roots = new LinkedHashMap<String, String>();
-        if (isWindows) {
-            for (File file : File.listRoots()) {
-                if (file.exists()) {
-                    String root = file.toString();
-                    String name = root.substring(0, root.length() - 1);
-                    roots.put(name, root);
-                }
-            }
-        } else {
-            roots.put("/", "/");
-        }
-        return roots;
-    }
-
-    public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
-        return unsupportedAttributePolicy;
-    }
-
-    public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy unsupportedAttributePolicy) {
-        this.unsupportedAttributePolicy = unsupportedAttributePolicy;
-    }
-
-    public String getUserName() {
-        return userName;
-    }
-
-    public char getSeparator() {
-        return separator;
-    }
-
-    /**
-     * Get file object.
-     */
-    public SshFile getFile(String file) {
-        return getFile(current, file);
-    }
-
-    public SshFile getFile(SshFile baseDir, String file) {
-        return getFile(baseDir.getAbsolutePath(), file);
-    }
-
-    protected SshFile getFile(String dir, String file) {
-        dir = appendSlash(normalizeSeparateChar(dir));
-        file = normalizeSeparateChar(file);
-        // Compute root + non rooted absolute file
-        String root = null;
-        if (roots.size() > 1 && file.startsWith("/")) {
-            file = file.substring(1);
-        }
-        for (String r : roots.keySet()) {
-            if (!file.isEmpty() && r.equals(file + "/")) {
-                file += "/";
-            }
-            if (file.startsWith(r)) {
-                root = r;
-                file = "/" + file.substring(r.length());
-                break;
-            }
-        }
-        if (root == null) {
-            // file is relative to dir
-            file = dir + file;
-            for (String r : roots.keySet()) {
-                if (file.startsWith(r)) {
-                    root = r;
-                    file = "/" + file.substring(r.length());
-                    break;
-                }
-            }
-        }
-        if (root == null) {
-            throw new IllegalStateException("Could not find root dir for file(" + dir + ", " + file + ")");
-        }
-        // Physical root
-        String physicalRoot = roots.get(root);
-        // get actual file object
-        String physicalName = NativeSshFile.getPhysicalName(physicalRoot, "/", file, caseInsensitive);
-        File fileObj = new File(physicalName);
-
-        // strip the root directory and return
-        String userFileName = root + physicalName.substring(physicalRoot.length());
-        return createNativeSshFile(userFileName, fileObj, userName);
-    }
-
-    static boolean isJava7;
-    static boolean isWindows;
-    static {
-        // Check java 7
-        boolean j7 = false;
-        try {
-            ClassLoader.getSystemClassLoader().loadClass("java.nio.file.Files");
-            j7 = true;
-        } catch (Throwable t) {
-            // Ignore
-        }
-        isJava7 = j7;
-        // Check windows
-        boolean win = false;
-        try {
-            win = System.getProperty("os.name").toLowerCase().contains("win");
-        } catch (Throwable t) {
-            // Ignore
-        }
-        isWindows = win;
-    }
-
-    public NativeSshFile createNativeSshFile(String name, File file, String userName) {
-        name = deNormalizeSeparateChar(name);
-        if (isJava7) {
-            return new NativeSshFileNio(this, name, file, userName);
-        } else {
-		    return new NativeSshFile(this, name, file, userName);
-        }
-	}
-
-    /**
-     * Normalize separate character. Separate character should be '/' always.
-     */
-    public final String deNormalizeSeparateChar(final String pathName) {
-        return pathName.replace('/', separator);
-    }
-
-    public FileSystemView getNormalizedView() {
-        if (roots.size() == 1 && roots.containsKey("/") && separator == '/') {
-            return this;
-        }
-        return new NativeFileSystemView(userName, roots, current, '/', caseInsensitive) {
-            public SshFile getFile(String file) {
-                return getFile(reroot(current), file);
-            }
-
-            public SshFile getFile(SshFile baseDir, String file) {
-                return getFile(baseDir.getAbsolutePath(), file);
-            }
-
-            public FileSystemView getNormalizedView() {
-                return this;
-            }
-
-            protected String reroot(String file) {
-                file = appendSlash(file);
-                for (String r : roots.keySet()) {
-                    if (file.startsWith(r)) {
-                        return "/" + normalizeRoot(r) + file.substring(r.length());
-                    }
-                }
-                throw new IllegalArgumentException();
-            }
-
-            protected SshFile getFile(String dir, String file) {
-                dir = appendSlash(normalizeSeparateChar(dir));
-                file = normalizeSeparateChar(file);
-                // Compute root + non rooted absolute file
-                if (!file.startsWith("/")) {
-                    file = dir + file;
-                }
-                // get actual file object
-                String userFileName = NativeSshFile.getPhysicalName("/", "/", file, caseInsensitive);
-                if (userFileName.equals("/")) {
-                    return new RootFile();
-                }
-                int idx = userFileName.indexOf("/", 1);
-                if (idx < 0) {
-                    String root = userFileName + "/";
-                    String physRoot = null;
-                    for (String r : roots.keySet()) {
-                        if (normalizeRoot(r).equals(root)) {
-                            physRoot = roots.get(r);
-                            break;
-                        }
-                    }
-                    if (physRoot == null) {
-                        throw new IllegalArgumentException("Unknown root " + userFileName);
-                    }
-                    File fileObj = new File(physRoot);
-                    userFileName = normalizeSeparateChar(userFileName);
-                    return createNativeSshFile(userFileName, fileObj, userName);
-                } else {
-                    String root = userFileName.substring(1, idx) + "/";
-                    String physRoot = null;
-                    for (String r : roots.keySet()) {
-                        if (normalizeRoot(r).equals(root)) {
-                            physRoot = roots.get(r);
-                            break;
-                        }
-                    }
-                    if (physRoot == null) {
-                        throw new IllegalArgumentException("Unknown root " + userFileName);
-                    }
-                    File fileObj = new File(physRoot + userFileName.substring(idx + 1));
-                    userFileName = normalizeSeparateChar(userFileName);
-                    return createNativeSshFile(userFileName, fileObj, userName);
-                }
-            }
-        };
-    }
-
-    protected static String normalizeRoot(String root) {
-        return root.replace(":", "");
-    }
-
-    class RootFile implements SshFile {
-        public String getAbsolutePath() {
-            return "/";
-        }
-        public String getName() {
-            return "/";
-        }
-        public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
-            return null;
-        }
-        public void setAttributes(Map<Attribute, Object> attributes) throws IOException {
-            throw new UnsupportedOperationException();
-        }
-        public Object getAttribute(Attribute attribute, boolean followLinks) throws IOException {
-            return null;
-        }
-        public void setAttribute(Attribute attribute, Object value) throws IOException {
-            throw new UnsupportedOperationException();
-        }
-        public String readSymbolicLink() throws IOException {
-            return null;
-        }
-        public void createSymbolicLink(SshFile destination) throws IOException {
-        }
-        public String getOwner() {
-            return null;
-        }
-        public boolean isDirectory() {
-            return true;
-        }
-        public boolean isFile() {
-            return false;
-        }
-        public boolean doesExist() {
-            return true;
-        }
-        public boolean isReadable() {
-            return true;
-        }
-        public boolean isWritable() {
-            return false;
-        }
-        public boolean isExecutable() {
-            return false;
-        }
-        public boolean isRemovable() {
-            return false;
-        }
-        public SshFile getParentFile() {
-            return null;
-        }
-        public long getLastModified() {
-            return 0;
-        }
-        public boolean setLastModified(long time) {
-            return false;
-        }
-        public long getSize() {
-            return 0;
-        }
-        public boolean mkdir() {
-            return false;
-        }
-        public boolean delete() {
-            return false;
-        }
-        public boolean create() throws IOException {
-            return false;
-        }
-        public void truncate() throws IOException {
-        }
-        public boolean move(SshFile destination) {
-            return false;
-        }
-        public List<SshFile> listSshFiles() {
-            List<SshFile> list = new ArrayList<SshFile>();
-            for (String root : roots.keySet()) {
-                String display = normalizeRoot(root);
-                display = "/" + display.substring(display.length() - 1);
-                list.add(createNativeSshFile(display, new File(roots.get(root)), userName));
-            }
-            return list;
-        }
-        public OutputStream createOutputStream(long offset) throws IOException {
-            return null;
-        }
-        public InputStream createInputStream(long offset) throws IOException {
-            return null;
-        }
-        public void handleClose() throws IOException {
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
deleted file mode 100644
index 8b336ef..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
+++ /dev/null
@@ -1,701 +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.file.nativefs;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.lang.reflect.Method;
-import java.nio.channels.FileChannel;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.StringTokenizer;
-
-import org.apache.sshd.common.file.SshFile;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <strong>Internal class, do not use directly.</strong>
- * 
- * This class wraps native file object.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class NativeSshFile implements SshFile {
-
-    protected static final Logger LOG = LoggerFactory.getLogger(NativeSshFile.class);
-
-    // the file name with respect to the user root.
-    // The path separator character will be '/'.
-    protected String fileName;
-
-    protected File file;
-
-    protected String userName;
-
-    protected final NativeFileSystemView nativeFileSystemView;
-
-    /**
-     * Constructor, internal do not use directly.
-     * @param nativeFileSystemView 
-     */
-    protected NativeSshFile(final NativeFileSystemView nativeFileSystemView, final String fileName, final File file,
-            final String userName) {
-        this.nativeFileSystemView = nativeFileSystemView;
-        if (fileName == null) {
-            throw new IllegalArgumentException("fileName can not be null");
-        }
-        if (file == null) {
-            throw new IllegalArgumentException("file can not be null");
-        }
-
-        if (fileName.length() == 0) {
-            throw new IllegalArgumentException("fileName can not be empty");
-        }
-
-        this.fileName = fileName;
-        this.file = file;
-        this.userName = userName;
-    }
-
-    public File getNativeFile() {
-        return file;
-    }
-
-    /**
-     * Get full name.
-     */
-    public String getAbsolutePath() {
-
-        char separator = nativeFileSystemView.getSeparator();
-
-        // strip the last '/' if necessary
-        String fullName = fileName;
-        int filelen = fullName.length();
-        if (fileName.indexOf(separator) != filelen - 1 && (fullName.charAt(filelen - 1) == separator)) {
-            fullName = fullName.substring(0, filelen - 1);
-        }
-
-        return fullName;
-    }
-
-    /**
-     * Get short name.
-     */
-    public String getName() {
-
-        char separator = nativeFileSystemView.getSeparator();
-
-        // root - the short name will be '/'
-        if (fileName.indexOf(separator) == fileName.length() - 1) {
-            return fileName;
-        }
-
-        // strip the last '/'
-        String shortName = fileName;
-        int filelen = fileName.length();
-        if (shortName.charAt(filelen - 1) == separator) {
-            shortName = shortName.substring(0, filelen - 1);
-        }
-
-        // return from the last '/'
-        int slashIndex = shortName.lastIndexOf(separator);
-        if (slashIndex != -1) {
-            shortName = shortName.substring(slashIndex + 1);
-        }
-        return shortName;
-    }
-
-    /**
-     * Get owner name
-     */
-    public String getOwner() {
-        return userName;
-    }
-
-    /**
-     * Is it a directory?
-     */
-    public boolean isDirectory() {
-        return file.isDirectory();
-    }
-
-    /**
-     * Is it a file?
-     */
-    public boolean isFile() {
-        return file.isFile();
-    }
-
-    /**
-     * Does this file exists?
-     */
-    public boolean doesExist() {
-        return file.exists();
-    }
-
-    /**
-     * Get file size.
-     */
-    public long getSize() {
-        return file.length();
-    }
-
-    /**
-     * Get last modified time.
-     */
-    public long getLastModified() {
-        return file.lastModified();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean setLastModified(long time) {
-        return file.setLastModified(time);
-    }
-
-    /**
-     * Check read permission.
-     */
-    public boolean isReadable() {
-        return file.canRead();
-    }
-
-    /**
-     * Check file write permission.
-     */
-    public boolean isWritable() {
-        LOG.debug("Checking if file exists");
-        if (file.exists()) {
-            LOG.debug("Checking can write: " + file.canWrite());
-            return file.canWrite();
-        }
-
-        LOG.debug("Authorized");
-        return true;
-    }
-
-    /**
-     * File.canExecute() method is only available on JDK 1.6
-     */
-    private static final Method CAN_EXECUTE_METHOD;
-    static {
-        Method method = null;
-        try {
-           method = File.class.getMethod("canExecute");
-        } catch (Throwable t) {
-        }
-        CAN_EXECUTE_METHOD = method;
-    }
-
-    /**
-     * Check file exec permission.
-     */
-    public boolean isExecutable() {
-        if (CAN_EXECUTE_METHOD != null) {
-            try {
-                return (Boolean) CAN_EXECUTE_METHOD.invoke(file);
-            } catch (Throwable t) {
-            }
-        }
-        // Default directories to being executable
-        // as on unix systems to allow listing their contents.
-        return file.isDirectory();
-    }
-
-    /**
-     * Has delete permission.
-     */
-    public boolean isRemovable() {
-
-        char separator = nativeFileSystemView.getSeparator();
-
-        // root cannot be deleted
-        if (fileName.indexOf(separator) == fileName.length() - 1) {
-            return false;
-        }
-
-        /* Added 12/08/2008: in the case that the permission is not explicitly denied for this file
-         * we will check if the parent file has write permission as most systems consider that a file can
-         * be deleted when their parent directory is writable.
-        */
-        String fullName = getAbsolutePath();
-
-        // we check FTPServer's write permission for this file.
-//        if (user.authorize(new WriteRequest(fullName)) == null) {
-//            return false;
-//        }
-
-        // In order to maintain consistency, when possible we delete the last '/' character in the String
-        int indexOfSlash = fullName.lastIndexOf(separator);
-        String parentFullName;
-        if (indexOfSlash == 0) {
-            parentFullName = "/";
-        } else {
-            if (fullName.indexOf(separator) == indexOfSlash) {
-                parentFullName = fullName.substring(0, indexOfSlash + 1);
-            } else {
-                parentFullName = fullName.substring(0, indexOfSlash);
-            }
-        }
-
-        // we check if the parent FileObject is writable.
-        NativeSshFile parentObject = nativeFileSystemView.createNativeSshFile(parentFullName, file
-                .getAbsoluteFile().getParentFile(), userName);
-        return parentObject.isWritable();
-    }
-
-    public SshFile getParentFile() {
-        char separator = nativeFileSystemView.getSeparator();
-
-        String path = getAbsolutePath();
-        int indexOfSlash = path.lastIndexOf(separator);
-        String parentFullName;
-        if (indexOfSlash == 0) {
-            parentFullName = "/";
-        } else {
-            if (path.indexOf(separator) == indexOfSlash) {
-                parentFullName = path.substring(0, indexOfSlash + 1);
-            } else {
-                parentFullName = path.substring(0, indexOfSlash);
-            }
-        }
-
-        // we check if the parent FileObject is writable.
-        return nativeFileSystemView.createNativeSshFile(parentFullName, file
-                .getAbsoluteFile().getParentFile(), userName);
-    }
-
-    /**
-     * Delete file.
-     */
-    public boolean delete() {
-        boolean retVal = false;
-        if (isRemovable()) {
-            retVal = file.delete();
-        }
-        return retVal;
-    }
-
-    /**
-     * Create a new file
-     */
-    public boolean create() throws IOException {
-        return file.createNewFile();
-    }
-
-    /**
-     * Truncate file to length 0.
-     */
-    public void truncate() throws IOException {
-        new FileWriter(file).close();
-    }
-
-    /**
-     * Move file object.
-     */
-    public boolean move(final SshFile dest) {
-        boolean retVal = false;
-        if (dest.isWritable() && isReadable()) {
-            File destFile = ((NativeSshFile) dest).file;
-
-            if (destFile.exists()) {
-                // renameTo behaves differently on different platforms
-                // this check verifies that if the destination already exists,
-                // we fail
-                retVal = false;
-            } else {
-                retVal = file.renameTo(destFile);
-            }
-        }
-        return retVal;
-    }
-
-    /**
-     * Create directory.
-     */
-    public boolean mkdir() {
-        boolean retVal = false;
-        if (isWritable()) {
-            retVal = file.mkdir();
-        }
-        return retVal;
-    }
-
-    /**
-     * List files. If not a directory or does not exist, null will be returned.
-     */
-    public List<SshFile> listSshFiles() {
-
-        // is a directory
-        if (!file.isDirectory()) {
-            return null;
-        }
-
-        // directory - return all the files
-        File[] files = file.listFiles();
-        if (files == null) {
-            return null;
-        }
-
-        // make sure the files are returned in order
-        Arrays.sort(files, new Comparator<File>() {
-            public int compare(File f1, File f2) {
-                return f1.getName().compareTo(f2.getName());
-            }
-        });
-
-        char separator = nativeFileSystemView.getSeparator();
-
-        // get the virtual name of the base directory
-        String virtualFileStr = getAbsolutePath();
-        if (virtualFileStr.charAt(virtualFileStr.length() - 1) != separator) {
-            virtualFileStr += separator;
-        }
-
-        // now return all the files under the directory
-        SshFile[] virtualFiles = new SshFile[files.length];
-        for (int i = 0; i < files.length; ++i) {
-            File fileObj = files[i];
-            String fileName = virtualFileStr + fileObj.getName();
-            virtualFiles[i] = nativeFileSystemView.createNativeSshFile(fileName, fileObj, userName);
-        }
-
-        return Collections.unmodifiableList(Arrays.asList(virtualFiles));
-    }
-
-    /**
-     * Create output stream for writing.
-     */
-    public OutputStream createOutputStream(final long offset)
-            throws IOException {
-
-        // permission check
-        if (!isWritable()) {
-            throw new IOException("No write permission : " + file.getName());
-        }
-
-        // move to the appropriate offset and create output stream
-        final boolean canRead = file.canRead();
-        if (!canRead) {
-            file.setReadable(true, true);
-        }
-        final RandomAccessFile raf = new RandomAccessFile(file, "rw");
-        try {
-            raf.seek(offset);
-
-            // The IBM jre needs to have both the stream and the random access file
-            // objects closed to actually close the file
-            return new FileOutputStream(raf.getFD()) {
-                public void close() throws IOException {
-                    super.close();
-                    raf.close();
-                    if (!canRead) {
-                        file.setReadable(false, true);
-                    }
-                }
-            };
-        } catch (IOException e) {
-            raf.close();
-            throw e;
-        }
-    }
-
-    /**
-     * Create input stream for reading.
-     */
-    public InputStream createInputStream(final long offset) throws IOException {
-
-        // permission check
-        if (!isReadable()) {
-            throw new IOException("No read permission : " + file.getName());
-        }
-
-        // move to the appropriate offset and create input stream
-        final FileInputStream fis = new FileInputStream(file);
-        try {
-            fis.getChannel().position(offset);
-            return fis;
-        } catch (IOException e) {
-            fis.close();
-            throw e;
-        }
-    }
-
-    public void handleClose() {
-        // Noop
-    }
-
-    /**
-     * Normalize separate character. Separate character should be '/' always.
-     */
-    public final static String normalizeSeparateChar(final String pathName) {
-        String normalizedPathName = pathName.replace('\\', '/');
-        return normalizedPathName;
-    }
-
-    /**
-     * Get the physical canonical file name. It works like
-     * File.getCanonicalPath().
-     * 
-     * @param rootDir
-     *            The root directory.
-     * @param currDir
-     *            The current directory. It will always be with respect to the
-     *            root directory.
-     * @param fileName
-     *            The input file name.
-     * @return The return string will always begin with the root directory. It
-     *         will never be null.
-     */
-    public final static String getPhysicalName(final String rootDir,
-            final String currDir, final String fileName,
-            final boolean caseInsensitive) {
-
-        // get the starting directory
-        String normalizedRootDir = normalizeSeparateChar(rootDir);
-        if (normalizedRootDir.charAt(normalizedRootDir.length() - 1) != '/') {
-            normalizedRootDir += '/';
-        }
-
-        String normalizedFileName = normalizeSeparateChar(fileName);
-        String resArg;
-        String normalizedCurrDir = currDir;
-        if (normalizedFileName.charAt(0) != '/') {
-            if (normalizedCurrDir == null) {
-                normalizedCurrDir = "/";
-            }
-            if (normalizedCurrDir.length() == 0) {
-                normalizedCurrDir = "/";
-            }
-
-            normalizedCurrDir = normalizeSeparateChar(normalizedCurrDir);
-
-            if (normalizedCurrDir.charAt(0) != '/') {
-                normalizedCurrDir = '/' + normalizedCurrDir;
-            }
-            if (normalizedCurrDir.charAt(normalizedCurrDir.length() - 1) != '/') {
-                normalizedCurrDir += '/';
-            }
-
-            resArg = normalizedRootDir + normalizedCurrDir.substring(1);
-        } else {
-            resArg = normalizedRootDir;
-        }
-
-        // strip last '/'
-        if (resArg.charAt(resArg.length() - 1) == '/') {
-            resArg = resArg.substring(0, resArg.length() - 1);
-        }
-
-        // replace ., ~ and ..
-        // in this loop resArg will never end with '/'
-        StringTokenizer st = new StringTokenizer(normalizedFileName, "/");
-        while (st.hasMoreTokens()) {
-            String tok = st.nextToken();
-
-            // . => current directory
-            if (tok.equals(".")) {
-                continue;
-            }
-
-            // .. => parent directory (if not root)
-            if (tok.equals("..")) {
-                if (resArg.startsWith(normalizedRootDir)) {
-                    int slashIndex = resArg.lastIndexOf('/');
-                    if (slashIndex != -1) {
-                        resArg = resArg.substring(0, slashIndex);
-                    }
-                }
-                continue;
-            }
-
-            // ~ => home directory (in this case the root directory)
-            if (tok.equals("~")) {
-                resArg = normalizedRootDir.substring(0, normalizedRootDir
-                        .length() - 1);
-                continue;
-            }
-
-            if (caseInsensitive) {
-                File[] matches = new File(resArg)
-                        .listFiles(new NameEqualsFileFilter(tok, true));
-
-                if (matches != null && matches.length > 0) {
-                    tok = matches[0].getName();
-                }
-            }
-
-            resArg = resArg + '/' + tok;
-        }
-
-        // add last slash if necessary
-        if ((resArg.length()) + 1 == normalizedRootDir.length()) {
-            resArg += '/';
-        }
-
-        // final check
-        if (!resArg.regionMatches(0, normalizedRootDir, 0, normalizedRootDir
-                .length())) {
-            resArg = normalizedRootDir;
-        }
-
-        return resArg;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj != null && obj instanceof NativeSshFile) {
-            File thisCanonicalFile;
-            File otherCanonicalFile;
-            try {
-                thisCanonicalFile = this.file.getCanonicalFile();
-                otherCanonicalFile = ((NativeSshFile) obj).file
-                        .getCanonicalFile();
-            } catch (IOException e) {
-                throw new RuntimeException("Failed to get the canonical path", e);
-            }
-
-            return thisCanonicalFile.equals(otherCanonicalFile);
-        }
-        return false;
-    }
-    
-    /**
-     * Returns the according physical file. Needed for logging, monitoring, event handling, etc.
-     * 
-     * @return The according physical file.
-     */
-    public File getPhysicalFile() {
-    	return file;
-    }
-
-    @Override
-    public String toString() {
-        return fileName;
-    }
-
-    public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
-        Map<Attribute, Object> map = new HashMap<Attribute, Object>();
-        map.put(Attribute.Size, getSize());
-        map.put(Attribute.IsDirectory, isDirectory());
-        map.put(Attribute.IsRegularFile, isFile());
-        map.put(Attribute.IsSymbolicLink, false);
-        map.put(Attribute.LastModifiedTime, getLastModified());
-        map.put(Attribute.LastAccessTime, getLastModified());
-        map.put(Attribute.Owner, userName);
-        map.put(Attribute.Group, userName);
-        EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
-        if (isReadable()) {
-            p.add(Permission.UserRead);
-            p.add(Permission.GroupRead);
-            p.add(Permission.OthersRead);
-        }
-        if (isWritable()) {
-            p.add(Permission.UserWrite);
-            p.add(Permission.GroupWrite);
-            p.add(Permission.OthersWrite);
-        }
-        if (isExecutable()) {
-            p.add(Permission.UserExecute);
-            p.add(Permission.GroupExecute);
-            p.add(Permission.OthersExecute);
-        }
-        map.put(Attribute.Permissions, p);
-        return map;
-    }
-
-    public void setAttributes(Map<Attribute, Object> attributes) throws IOException {
-        Set<Attribute> unsupported = new HashSet<Attribute>();
-        for (Attribute attribute : attributes.keySet()) {
-            Object value = attributes.get(attribute);
-            switch (attribute) {
-            case Size: {
-                long newSize = (Long) value;
-                FileChannel outChan = new FileOutputStream(file, true).getChannel();
-                outChan.truncate(newSize);
-                outChan.close();
-                continue;
-            }
-            case LastModifiedTime:
-                setLastModified((Long) value);
-                break;
-            default:
-                unsupported.add(attribute);
-                break;
-            }
-        }
-        handleUnsupportedAttributes(unsupported);
-    }
-
-    protected void handleUnsupportedAttributes(Collection<Attribute> attributes) {
-        if (!attributes.isEmpty()) {
-            StringBuilder sb = new StringBuilder();
-            for (Attribute attr : attributes) {
-                if (sb.length() > 0) {
-                    sb.append(", ");
-                }
-                sb.append(attr.name());
-            }
-            switch (nativeFileSystemView.getUnsupportedAttributePolicy()) {
-            case Ignore:
-                break;
-            case Warn:
-                LOG.warn("Unsupported attributes: " + sb.toString());
-                break;
-            case ThrowException:
-                throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
-            }
-        }
-    }
-
-    public Object getAttribute(Attribute attribute, boolean followLinks) throws IOException {
-        return getAttributes(followLinks).get(attribute);
-    }
-
-    public void setAttribute(Attribute attribute, Object value) throws IOException {
-        Map<Attribute, Object> map = new HashMap<Attribute, Object>();
-        map.put(attribute, value);
-        setAttributes(map);
-    }
-
-    public String readSymbolicLink() throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void createSymbolicLink(SshFile destination) throws IOException {
-        throw new UnsupportedOperationException();
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
deleted file mode 100644
index b0596d1..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
+++ /dev/null
@@ -1,260 +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.file.nativefs;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.sshd.common.file.SshFile;
-
-/**
- * <strong>Internal class, do not use directly.</strong>
- * 
- * This class wraps native file object.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class NativeSshFileNio extends NativeSshFile {
-
-    @Override
-    public boolean doesExist() {
-        return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
-    }
-
-    /**
-     * Constructor, internal do not use directly.
-     * @param nativeFileSystemView
-     */
-    public NativeSshFileNio(NativeFileSystemView nativeFileSystemView, String fileName, File file, String userName) {
-        super(nativeFileSystemView, fileName, file, userName);
-    }
-
-    public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
-        String[] attrs = new String[] { "unix:*", "posix:*", "*" };
-        Map<String, Object> a = null;
-        for (String attr : attrs) {
-            try {
-                a = Files.readAttributes(
-                        file.toPath(), attr,
-                        followLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
-                break;
-            } catch (UnsupportedOperationException e) {
-                // Ignore
-            }
-        }
-        if (a == null) {
-            throw new IllegalStateException();
-        }
-        Map<Attribute, Object> map = new HashMap<Attribute, Object>();
-        map.put(Attribute.Size, a.get("size"));
-        if (a.containsKey("uid")) {
-            map.put(Attribute.Uid, a.get("uid"));
-        }
-        if (a.containsKey("owner")) {
-            map.put(Attribute.Owner, ((UserPrincipal) a.get("owner")).getName());
-        } else {
-            map.put(Attribute.Owner, userName);
-        }
-        if (a.containsKey("gid")) {
-            map.put(Attribute.Gid, a.get("gid"));
-        }
-        if (a.containsKey("group")) {
-            map.put(Attribute.Group, ((GroupPrincipal) a.get("group")).getName());
-        } else {
-            map.put(Attribute.Group, userName);
-        }
-        if (a.containsKey("nlink")) {
-            map.put(Attribute.NLink, a.get("nlink"));
-        }
-        map.put(Attribute.IsDirectory, a.get("isDirectory"));
-        map.put(Attribute.IsRegularFile, a.get("isRegularFile"));
-        map.put(Attribute.IsSymbolicLink, a.get("isSymbolicLink"));
-        map.put(Attribute.CreationTime, ((FileTime) a.get("creationTime")).toMillis());
-        map.put(Attribute.LastModifiedTime, ((FileTime) a.get("lastModifiedTime")).toMillis());
-        map.put(Attribute.LastAccessTime, ((FileTime) a.get("lastAccessTime")).toMillis());
-        if (a.containsKey("permissions")) {
-            map.put(Attribute.Permissions, fromPerms((Set<PosixFilePermission>) a.get("permissions")));
-        } else {
-            EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
-            if (isReadable()) {
-                p.add(Permission.UserRead);
-                p.add(Permission.GroupRead);
-                p.add(Permission.OthersRead);
-            }
-            if (isWritable()) {
-                p.add(Permission.UserWrite);
-                p.add(Permission.GroupWrite);
-                p.add(Permission.OthersWrite);
-            }
-            if (isExecutable()) {
-                p.add(Permission.UserExecute);
-                p.add(Permission.GroupExecute);
-                p.add(Permission.OthersExecute);
-            }
-            map.put(Attribute.Permissions, p);
-        }
-        return map;
-    }
-
-    public void setAttributes(Map<Attribute, Object> attributes) throws IOException {
-        Set<Attribute> unsupported = new HashSet<Attribute>();
-        for (Attribute attribute : attributes.keySet()) {
-            String name = null;
-            Object value = attributes.get(attribute);
-            switch (attribute) {
-                case Size:             {
-                    long newSize = (Long) value;
-                    FileChannel outChan = new FileOutputStream(file, true).getChannel();
-                    outChan.truncate(newSize);
-                    outChan.close();
-                    continue;
-                }
-                case Uid:              name = "unix:uid"; break;
-                case Gid:              name = "unix:gid"; break;
-                case Owner:            name = "posix:owner"; value = toUser((String) value); break;
-                case Group:            name = "posix:group"; value = toGroup((String) value); break;
-                case Permissions:      name = "posix:permissions"; value = toPerms((EnumSet<Permission>) value); break;
-                case CreationTime:     name = "basic:creationTime"; value = FileTime.fromMillis((Long) value); break;
-                case LastModifiedTime: name = "basic:lastModifiedTime"; value = FileTime.fromMillis((Long) value); break;
-                case LastAccessTime:   name = "basic:lastAccessTime"; value = FileTime.fromMillis((Long) value); break;
-            }
-            if (name != null && value != null) {
-                try {
-                    Files.setAttribute(file.toPath(), name, value, LinkOption.NOFOLLOW_LINKS);
-                } catch (UnsupportedOperationException e) {
-                    unsupported.add(attribute);
-                }
-            }
-        }
-        handleUnsupportedAttributes(unsupported);
-    }
-
-    public String readSymbolicLink() throws IOException {
-        Path path = file.toPath();
-        Path link = Files.readSymbolicLink(path);
-        return link.toString();
-    }
-
-    public void createSymbolicLink(SshFile destination) throws IOException {
-        Path link = file.toPath();
-        Path target = Paths.get(destination.toString());
-        Files.createSymbolicLink(link, target);
-    }
-
-    private EnumSet<Permission> fromPerms(Set<PosixFilePermission> perms) {
-        EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
-        for (PosixFilePermission perm : perms) {
-            switch (perm) {
-                case OWNER_READ:     p.add(Permission.UserRead); break;
-                case OWNER_WRITE:    p.add(Permission.UserWrite); break;
-                case OWNER_EXECUTE:  p.add(Permission.UserExecute); break;
-                case GROUP_READ:     p.add(Permission.GroupRead); break;
-                case GROUP_WRITE:    p.add(Permission.GroupWrite); break;
-                case GROUP_EXECUTE:  p.add(Permission.GroupExecute); break;
-                case OTHERS_READ:    p.add(Permission.OthersRead); break;
-                case OTHERS_WRITE:   p.add(Permission.OthersWrite); break;
-                case OTHERS_EXECUTE: p.add(Permission.OthersExecute); break;
-            }
-        }
-        return p;
-    }
-
-    private GroupPrincipal toGroup(String name) throws IOException {
-        UserPrincipalLookupService lookupService = file.toPath().getFileSystem().getUserPrincipalLookupService();
-        return lookupService.lookupPrincipalByGroupName(name);
-    }
-
-    private UserPrincipal toUser(String name) throws IOException {
-        UserPrincipalLookupService lookupService = file.toPath().getFileSystem().getUserPrincipalLookupService();
-        return lookupService.lookupPrincipalByName(name);
-    }
-
-    private Set<PosixFilePermission> toPerms(EnumSet<Permission> perms) {
-        Set<PosixFilePermission> set = new HashSet<PosixFilePermission>();
-        for (Permission p : perms) {
-            switch (p) {
-                case UserRead:      set.add(PosixFilePermission.OWNER_READ); break;
-                case UserWrite:     set.add(PosixFilePermission.OWNER_WRITE); break;
-                case UserExecute:   set.add(PosixFilePermission.OWNER_EXECUTE); break;
-                case GroupRead:     set.add(PosixFilePermission.GROUP_READ); break;
-                case GroupWrite:    set.add(PosixFilePermission.GROUP_WRITE); break;
-                case GroupExecute:  set.add(PosixFilePermission.GROUP_EXECUTE); break;
-                case OthersRead:    set.add(PosixFilePermission.OTHERS_READ); break;
-                case OthersWrite:   set.add(PosixFilePermission.OTHERS_WRITE); break;
-                case OthersExecute: set.add(PosixFilePermission.OTHERS_EXECUTE); break;
-            }
-        }
-        return set;
-    }
-
-    @Override
-    public OutputStream createOutputStream(long offset) throws IOException {
-        Path path = file.toPath();
-        final SeekableByteChannel sbc = Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
-        if (offset > 0) {
-            sbc.position(offset);
-        }
-        return 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 {
-                if (b == null) {
-                    throw new NullPointerException();
-                } else if ((off < 0) || (off > b.length) || (len < 0) ||
-                        ((off + len) > b.length) || ((off + len) < 0)) {
-                    throw new IndexOutOfBoundsException();
-                } else if (len == 0) {
-                    return;
-                }
-                sbc.write(ByteBuffer.wrap(b, off, len));
-            }
-
-            @Override
-            public void close() throws IOException {
-                sbc.close();
-            }
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystem.java
new file mode 100644
index 0000000..293d054
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystem.java
@@ -0,0 +1,76 @@
+/*
+ * 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.file.root;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.file.util.BaseFileSystem;
+import org.apache.sshd.common.file.util.ImmutableList;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RootedFileSystem extends BaseFileSystem<RootedPath> {
+
+    private Path root;
+
+    public RootedFileSystem(RootedFileSystemProvider fileSystemProvider, Path root, Map<String, ?> env) {
+        super(fileSystemProvider);
+        this.root = root;
+    }
+
+    public Path getRoot() {
+        return root;
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+
+    @Override
+    public boolean isOpen() {
+        return getRoot().getFileSystem().isOpen();
+    }
+
+    @Override
+    public boolean isReadOnly() {
+        return getRoot().getFileSystem().isReadOnly();
+    }
+
+    @Override
+    public Set<String> supportedFileAttributeViews() {
+        return root.getFileSystem().supportedFileAttributeViews();
+    }
+
+    @Override
+    public UserPrincipalLookupService getUserPrincipalLookupService() {
+        return getRoot().getFileSystem().getUserPrincipalLookupService();
+    }
+
+    @Override
+    protected RootedPath create(String root, ImmutableList<String> names) {
+        return new RootedPath(this, root, names);
+    }
+
+}


[07/15] mina-sshd git commit: [SSHD-377] Create a nio FileSystem implementation for sftp

Posted by gn...@apache.org.
[SSHD-377] Create a nio FileSystem implementation for sftp


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

Branch: refs/heads/master
Commit: cc4d787733388f192a2f776ae4fcde2a471f858d
Parents: e9ee318
Author: Guillaume Nodet <gn...@apache.org>
Authored: Fri Nov 28 15:12:06 2014 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 .../java.nio.file.spi.FileSystemProvider        |  20 +
 .../java/org/apache/sshd/ClientSession.java     |   3 +
 .../java/org/apache/sshd/client/SftpClient.java |   5 +-
 .../sshd/client/session/ClientSessionImpl.java  |   7 +
 .../sshd/client/sftp/DefaultSftpClient.java     |   4 +
 .../apache/sshd/client/sftp/SftpFileSystem.java | 314 +++++++++
 .../client/sftp/SftpFileSystemProvider.java     | 665 +++++++++++++++++++
 .../org/apache/sshd/client/sftp/SftpPath.java   |  49 ++
 .../common/file/nativefs/NativeSshFileNio.java  |   4 +-
 .../sshd/common/file/util/BaseFileSystem.java   | 239 +++++++
 .../apache/sshd/common/file/util/BasePath.java  | 367 ++++++++++
 .../sshd/common/file/util/ImmutableList.java    |  61 ++
 .../apache/sshd/server/sftp/SftpSubsystem.java  |   5 +-
 .../org/apache/sshd/SftpFileSystemTest.java     | 112 ++++
 .../sshd/common/file/util/BasePathTest.java     | 560 ++++++++++++++++
 15 files changed, 2411 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/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
new file mode 100644
index 0000000..08d4b4a
--- /dev/null
+++ b/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
@@ -0,0 +1,20 @@
+##
+## 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.
+##
+
+org.apache.sshd.client.sftp.SftpFileSystemProvider

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
index bee81f7..867dadb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
@@ -19,6 +19,7 @@
 package org.apache.sshd;
 
 import java.io.IOException;
+import java.nio.file.FileSystem;
 import java.security.KeyPair;
 import java.util.Map;
 
@@ -160,6 +161,8 @@ public interface ClientSession extends Session {
      */
     SftpClient createSftpClient() throws IOException;
 
+    FileSystem createSftpFileSystem() throws IOException;
+
     /**
      * Start forwarding the given local address on the client to the given address on the server.
      */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
index 35b4260..18a9ba4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
@@ -21,12 +21,13 @@ package org.apache.sshd.client;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.util.EnumSet;
 
 /**
  * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  */
-public interface SftpClient {
+public interface SftpClient extends AutoCloseable {
 
     //
     // Permission flags
@@ -135,6 +136,8 @@ public interface SftpClient {
      */
     void close() throws IOException;
 
+    boolean isClosing();
+
     //
     // Low level API
     //

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/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 7d9a8c9..a1872b8 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
@@ -20,6 +20,7 @@ package org.apache.sshd.client.session;
 
 import java.io.IOException;
 import java.net.SocketAddress;
+import java.nio.file.FileSystem;
 import java.security.KeyPair;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -47,6 +48,8 @@ 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.sftp.DefaultSftpClient;
+import org.apache.sshd.client.sftp.SftpFileSystem;
+import org.apache.sshd.client.sftp.SftpFileSystemProvider;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Service;
 import org.apache.sshd.common.ServiceFactory;
@@ -278,6 +281,10 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
         return new DefaultSftpClient(this);
     }
 
+    public FileSystem createSftpFileSystem() throws IOException {
+        return new SftpFileSystem(new SftpFileSystemProvider((org.apache.sshd.SshClient) factoryManager), this);
+    }
+
     public SshdSocketAddress startLocalPortForwarding(SshdSocketAddress local, SshdSocketAddress remote) throws IOException {
         return getConnectionService().getTcpipForwarder().startLocalPortForwarding(local, remote);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/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
index cbed805..526d92d 100644
--- 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
@@ -132,6 +132,10 @@ public class DefaultSftpClient implements SftpClient {
         init();
     }
 
+    public boolean isClosing() {
+        return closing;
+    }
+
     public void close() throws IOException {
         this.channel.close(false);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/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
new file mode 100644
index 0000000..d6de9df
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
@@ -0,0 +1,314 @@
+/*
+ * 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.Collections;
+import java.util.EnumSet;
+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.ClientSession;
+import org.apache.sshd.client.SftpClient;
+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;
+
+    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("."));
+        }
+    }
+
+    @Override
+    protected SftpPath create(String root, ImmutableList<String> names) {
+        return new SftpPath(this, root, names);
+    }
+
+    public ClientSession getSession() {
+        return session;
+    }
+
+    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);
+                }
+            }
+            wrappers.set(wrapper);
+        } else {
+            wrapper.increment();
+        }
+        return wrapper;
+    }
+
+    @Override
+    public void close() throws IOException {
+        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();
+    }
+
+    public SftpPath getDefaultDir() {
+        return defaultDir;
+    }
+
+    private class Wrapper implements SftpClient {
+
+        private final SftpClient delegate;
+        private final AtomicInteger count = new AtomicInteger(1);
+
+        private Wrapper(SftpClient delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public boolean isClosing() {
+            return false;
+        }
+
+        @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 Handle open(String path, EnumSet<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) throws IOException {
+            delegate.rename(oldPath, newPath);
+        }
+
+        @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 Handle 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 delegate.read(path);
+        }
+
+        @Override
+        public InputStream read(String path, EnumSet<OpenMode> mode) throws IOException {
+            return delegate.read(path, mode);
+        }
+
+        @Override
+        public OutputStream write(String path) throws IOException {
+            return delegate.write(path);
+        }
+
+        @Override
+        public OutputStream write(String path, EnumSet<OpenMode> mode) throws IOException {
+            return delegate.write(path, mode);
+        }
+
+    }
+
+    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/cc4d7877/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
new file mode 100644
index 0000000..07c0dc2
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
@@ -0,0 +1,665 @@
+/*
+ * 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.InterruptedIOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+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.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.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.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshBuilder;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.client.SftpClient;
+import org.apache.sshd.client.SftpException;
+
+public class SftpFileSystemProvider extends FileSystemProvider {
+
+    public static final int SSH_FX_NO_SUCH_FILE =         2;
+    public static final int SSH_FX_FILE_ALREADY_EXISTS = 11;
+
+    final SshClient client;
+    final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
+
+    public SftpFileSystemProvider() {
+        this(null);
+    }
+
+    public SftpFileSystemProvider(SshClient client) {
+        if (client == null) {
+            // TODO: make this configurable using system properties
+            client = SshBuilder.client().build();
+        }
+        this.client = client;
+        this.client.start();
+    }
+
+    @Override
+    public String getScheme() {
+        return "sftp";
+    }
+
+    @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 = uri.getHost();
+            String userInfo = uri.getUserInfo();
+            if (host == null) {
+                throw new IllegalArgumentException("Host not provided");
+            }
+            if (userInfo == null) {
+                throw new IllegalArgumentException("UserInfo not provided");
+            }
+            String[] ui = userInfo.split(":");
+            ClientSession session;
+            try {
+                session = client.connect(ui[0], host, uri.getPort() > 0 ? uri.getPort() : 22)
+                        .await().getSession();
+            } catch (InterruptedException e) {
+                throw new InterruptedIOException();
+            }
+            session.addPasswordIdentity(ui[1]);
+            session.auth().verify();
+            fileSystem = new SftpFileSystem(this, session);
+            fileSystems.put(authority, fileSystem);
+            return fileSystem;
+        }
+    }
+
+    @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) {
+        return getFileSystem(uri).getPath(uri.getPath());
+    }
+
+    @Override
+    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        final SftpPath p = toSftpPath(path);
+        final EnumSet<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 {
+                throw new IllegalArgumentException("Unsupported open option " + option);
+            }
+        }
+        if (modes.isEmpty()) {
+            modes.add(SftpClient.OpenMode.Read);
+        }
+        return new SeekableByteChannel() {
+            final SftpClient sftp = p.getFileSystem().getClient();
+            final SftpClient.Handle handle = sftp.open(p.toString(), modes);
+            long pos = 0;
+            @Override
+            public int read(ByteBuffer dst) throws IOException {
+                if (dst.hasArray()) {
+                    int read = sftp.read(handle, pos, dst.array(), dst.arrayOffset() + dst.position(), dst.remaining());
+                    if (read > 0) {
+                        dst.position(dst.position() + read);
+                        pos += read;
+                    }
+                    return read;
+                } else {
+                    int remaining = Math.min(8192, dst.remaining());
+                    byte[] buf = new byte[remaining];
+                    int read = sftp.read(handle, pos, buf, 0, remaining);
+                    if (read > 0) {
+                        dst.put(buf, 0, read);
+                        pos += read;
+                    }
+                    return read;
+                }
+            }
+
+            @Override
+            public int write(ByteBuffer src) throws IOException {
+                if (src.hasArray()) {
+                    int rem = src.remaining();
+                    sftp.write(handle, pos, src.array(), src.arrayOffset() + src.position(), rem);
+                    src.position(src.position() + rem);
+                    pos += rem;
+                    return rem;
+                } else {
+                    byte[] buf = new byte[Math.min(8192, src.remaining())];
+                    src.get(buf);
+                    sftp.write(handle, pos, buf, 0, buf.length);
+                    pos += buf.length;
+                    return buf.length;
+                }
+            }
+
+            @Override
+            public long position() throws IOException {
+                if (pos < 0) {
+                    throw new ClosedChannelException();
+                }
+                return pos;
+            }
+
+            @Override
+            public SeekableByteChannel position(long newPosition) throws IOException {
+                if (newPosition < 0) {
+                    throw new IllegalArgumentException();
+                }
+                pos = newPosition;
+                return this;
+            }
+
+            @Override
+            public long size() throws IOException {
+                return sftp.stat(handle).size;
+            }
+
+            @Override
+            public SeekableByteChannel truncate(long size) throws IOException {
+                sftp.setStat(handle, new SftpClient.Attributes().size(size));
+                return this;
+            }
+
+            @Override
+            public boolean isOpen() {
+                return pos >= 0;
+            }
+
+            @Override
+            public void close() throws IOException {
+                sftp.close(handle);
+                sftp.close();
+                pos = -1;
+            }
+        };
+    }
+
+    @Override
+    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+        final SftpPath p = toSftpPath(dir);
+        return new DirectoryStream<Path>() {
+            final SftpClient sftp = p.getFileSystem().getClient();
+            final Iterable<SftpClient.DirEntry> iter = sftp.readDir(p.toString());
+            @Override
+            public Iterator<Path> iterator() {
+                return new Iterator<Path>() {
+                    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();
+                    }
+                };
+            }
+
+            @Override
+            public void close() throws IOException {
+                sftp.close();
+            }
+        };
+    }
+
+    @Override
+    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+        SftpPath p = toSftpPath(dir);
+        try (SftpClient sftp = p.getFileSystem().getClient()) {
+            // TODO: handle attributes
+            try {
+                sftp.mkdir(dir.toString());
+            } catch (SftpException e) {
+                if (e.getStatus() == SSH_FX_FILE_ALREADY_EXISTS) {
+                    throw new FileAlreadyExistsException(p.toString());
+                }
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public void delete(Path path) throws IOException {
+        SftpPath p = toSftpPath(path);
+        checkAccess(p, AccessMode.WRITE);
+        try (SftpClient sftp = p.getFileSystem().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 {
+        // TODO
+    }
+
+    @Override
+    public void move(Path source, Path target, CopyOption... options) throws IOException {
+        // TODO
+
+    }
+
+    @Override
+    public boolean isSameFile(Path path1, Path path2) throws IOException {
+        SftpPath p1 = toSftpPath(path1);
+        SftpPath p2 = toSftpPath(path2);
+        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 UnsupportedOperationException();
+    }
+
+    @Override
+    public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
+        SftpPath l = toSftpPath(link);
+        SftpPath t = toSftpPath(target);
+        if (l.getFileSystem() != t.getFileSystem()) {
+            throw new ProviderMismatchException();
+        }
+        try (SftpClient client = l.getFileSystem().getClient()) {
+            client.symLink(l.toString(), t.toString());
+        }
+    }
+
+    @Override
+    public Path readSymbolicLink(Path link) throws IOException {
+        SftpPath l = toSftpPath(link);
+        try (SftpClient client = l.getFileSystem().getClient()) {
+            return l.getFileSystem().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;
+        for (AccessMode mode : modes) {
+            switch (mode) {
+            case READ:
+                break;
+            case WRITE:
+                w = true;
+                break;
+            case EXECUTE:
+                x = true;
+                break;
+            default:
+                throw new UnsupportedOperationException();
+            }
+        }
+        BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
+        if (attrs == null && !(p.isAbsolute() && p.getNameCount() == 0)) {
+            throw new NoSuchFileException(toString());
+        }
+        if (x || w && p.getFileSystem().isReadOnly()) {
+            throw new AccessDeniedException(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";
+                }
+
+                @Override
+                public PosixFileAttributes readAttributes() throws IOException {
+                    SftpPath p = toSftpPath(path);
+                    final SftpClient.Attributes attributes;
+                    try (SftpClient client = p.getFileSystem().getClient()) {
+                        try {
+                            if (followLinks(options)) {
+                                attributes = client.stat(p.toString());
+                            } else {
+                                attributes = client.lstat(p.toString());
+                            }
+                        } catch (SftpException e) {
+                            if (e.getStatus() == SSH_FX_NO_SUCH_FILE) {
+                                throw new NoSuchFileException(p.toString());
+                            }
+                            throw e;
+                        }
+                    }
+                    return new PosixFileAttributes() {
+                        @Override
+                        public UserPrincipal owner() {
+                            // TODO
+                            return null;
+                        }
+
+                        @Override
+                        public GroupPrincipal group() {
+                            // TODO
+                            return null;
+                        }
+
+                        @Override
+                        public Set<PosixFilePermission> permissions() {
+                            // TODO
+                            return null;
+                        }
+
+                        @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() {
+                            // TODO
+                            return null;
+                        }
+
+                        @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();
+        }
+    }
+
+    @Override
+    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+        if (type.isAssignableFrom(PosixFileAttributes.class)) {
+            return (A) getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes();
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    @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);
+        if (!p.getFileSystem().supportedFileAttributeViews().contains(view)) {
+            throw new UnsupportedOperationException();
+        }
+        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, v.size());
+                break;
+            case "isRegularFile":
+                map.put(attr, v.isRegularFile());
+                break;
+            case "isDirectory":
+                map.put(attr, v.isDirectory());
+                break;
+            case "isSymbolicLink":
+                map.put(attr, v.isSymbolicLink());
+                break;
+            case "isOther":
+                map.put(attr, 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;
+            }
+        }
+        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);
+        if (!p.getFileSystem().supportedFileAttributeViews().contains(view)) {
+            throw new UnsupportedOperationException();
+        }
+        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 "size":
+            attributes.size = (long) value;
+            break;
+        case "owner":
+        case "permissions":
+        case "group":
+            // TODO: handle those
+            throw new IllegalArgumentException(attr);
+        case "creationTime":
+        case "isRegularFile":
+        case "isDirectory":
+        case "isSymbolicLink":
+        case "isOther":
+        case "fileKey":
+            throw new IllegalArgumentException(attr);
+        }
+        try (SftpClient client = p.getFileSystem().getClient()) {
+            client.setStat(p.toString(), attributes);
+        }
+    }
+
+    private SftpPath toSftpPath(Path path) {
+        if (path == null) {
+            throw new NullPointerException();
+        }
+        if (!(path instanceof SftpPath)) {
+            throw new ProviderMismatchException();
+        }
+        return (SftpPath) path;
+    }
+
+    static boolean followLinks(LinkOption... paramVarArgs)
+    {
+        boolean bool = true;
+        for (LinkOption localLinkOption : paramVarArgs) {
+            if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
+                bool = false;
+            }
+        }
+        return bool;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/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
new file mode 100644
index 0000000..8e70c62
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
@@ -0,0 +1,49 @@
+/*
+ * 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.file.LinkOption;
+
+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);
+    }
+
+    public URI toUri() {
+        // TODO
+        return null;
+    }
+
+    public SftpPath toRealPath(LinkOption... options) throws IOException {
+//        try (SftpClient client = fileSystem.getClient()) {
+//            client.realP
+//        }
+        // TODO: handle links
+        SftpPath absolute = toAbsolutePath();
+        fileSystem.provider().checkAccess(absolute);
+        return absolute;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
index 9753ed8..b0596d1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
@@ -174,8 +174,8 @@ public class NativeSshFileNio extends NativeSshFile {
 
     public void createSymbolicLink(SshFile destination) throws IOException {
         Path link = file.toPath();
-        Path target = Paths.get(destination.getAbsolutePath());
-        Files.createSymbolicLink(target, link);
+        Path target = Paths.get(destination.toString());
+        Files.createSymbolicLink(link, target);
     }
 
     private EnumSet<Permission> fromPerms(Set<PosixFilePermission> perms) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/main/java/org/apache/sshd/common/file/util/BaseFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/util/BaseFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/common/file/util/BaseFileSystem.java
new file mode 100644
index 0000000..68869ca
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/util/BaseFileSystem.java
@@ -0,0 +1,239 @@
+/*
+ * 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.file.util;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.WatchService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+public abstract class BaseFileSystem<T extends Path> extends FileSystem {
+
+    private final FileSystemProvider fileSystemProvider;
+
+    public BaseFileSystem(FileSystemProvider fileSystemProvider) {
+        this.fileSystemProvider = fileSystemProvider;
+    }
+
+    public T getDefaultDir() {
+        return getPath("/");
+    }
+
+    @Override
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    @Override
+    public FileSystemProvider provider() {
+        return fileSystemProvider;
+    }
+
+    @Override
+    public String getSeparator() {
+        return "/";
+    }
+
+    @Override
+    public Iterable<Path> getRootDirectories() {
+        return Collections.<Path>singleton(create("/"));
+    }
+
+    @Override
+    public Iterable<FileStore> getFileStores() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public T getPath(String first, String... more) {
+        StringBuilder sb = new StringBuilder();
+        appendDedupSep(sb, first);
+        for (String segment : more) {
+            if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '/') {
+                sb.append('/');
+            }
+            appendDedupSep(sb, segment);
+        }
+        if (sb.length() > 1 && sb.charAt(sb.length() - 1) == '/') {
+            sb.setLength(sb.length() - 1);
+        }
+        String path = sb.toString();
+        String root = null;
+        if (path.startsWith("/")) {
+            root = "/";
+            path = path.substring(1);
+        }
+        String[] names;
+        if (path.length() > 0) {
+            names = path.split("/");
+        } else {
+            names = new String[0];
+        }
+        return create(root, names);
+    }
+
+    private void appendDedupSep(StringBuilder sb, String s) {
+        for (int i = 0; i < s.length(); i++) {
+            char ch = s.charAt(i);
+            if (ch != '/' || sb.length() == 0 || sb.charAt(sb.length() - 1) != '/') {
+                sb.append(ch);
+            }
+        }
+    }
+
+    @Override
+    public PathMatcher getPathMatcher(String syntaxAndPattern) {
+        int colonIndex = syntaxAndPattern.indexOf(':');
+        if (colonIndex <= 0 || colonIndex == syntaxAndPattern.length() - 1) {
+            throw new IllegalArgumentException("syntaxAndPattern must have form \"syntax:pattern\" but was \"" + syntaxAndPattern + "\"");
+        }
+
+        String syntax = syntaxAndPattern.substring(0, colonIndex);
+        String pattern = syntaxAndPattern.substring(colonIndex + 1);
+        String expr;
+        switch (syntax) {
+        case "glob":
+            expr = globToRegex(pattern);
+            break;
+        case "regex":
+            expr = pattern;
+            break;
+        default:
+            throw new UnsupportedOperationException("Unsupported syntax \'" + syntax + "\'");
+        }
+        final Pattern regex = Pattern.compile(expr);
+        return new PathMatcher() {
+            @Override
+            public boolean matches(Path path) {
+                return regex.matcher(path.toString()).matches();
+            }
+        };
+    }
+
+    private String globToRegex(String pattern) {
+        StringBuilder sb = new StringBuilder(pattern.length());
+        int inGroup = 0;
+        int inClass = 0;
+        int firstIndexInClass = -1;
+        char[] arr = pattern.toCharArray();
+        for (int i = 0; i < arr.length; i++) {
+            char ch = arr[i];
+            switch (ch) {
+            case '\\':
+                if (++i >= arr.length) {
+                    sb.append('\\');
+                } else {
+                    char next = arr[i];
+                    switch (next) {
+                    case ',':
+                        // escape not needed
+                        break;
+                    case 'Q':
+                    case 'E':
+                        // extra escape needed
+                        sb.append('\\');
+                    default:
+                        sb.append('\\');
+                    }
+                    sb.append(next);
+                }
+                break;
+            case '*':
+                if (inClass == 0)
+                    sb.append(".*");
+                else
+                    sb.append('*');
+                break;
+            case '?':
+                if (inClass == 0)
+                    sb.append('.');
+                else
+                    sb.append('?');
+                break;
+            case '[':
+                inClass++;
+                firstIndexInClass = i+1;
+                sb.append('[');
+                break;
+            case ']':
+                inClass--;
+                sb.append(']');
+                break;
+            case '.':
+            case '(':
+            case ')':
+            case '+':
+            case '|':
+            case '^':
+            case '$':
+            case '@':
+            case '%':
+                if (inClass == 0 || (firstIndexInClass == i && ch == '^'))
+                    sb.append('\\');
+                sb.append(ch);
+                break;
+            case '!':
+                if (firstIndexInClass == i)
+                    sb.append('^');
+                else
+                    sb.append('!');
+                break;
+            case '{':
+                inGroup++;
+                sb.append('(');
+                break;
+            case '}':
+                inGroup--;
+                sb.append(')');
+                break;
+            case ',':
+                if (inGroup > 0)
+                    sb.append('|');
+                else
+                    sb.append(',');
+                break;
+            default:
+                sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public WatchService newWatchService() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    protected T create(String root, String... names) {
+        return create(root, new ImmutableList<>(names));
+    }
+
+    protected T create(String root, Collection<String> names) {
+        return create(root, new ImmutableList<>(names.toArray(new String[names.size()])));
+    }
+
+    protected abstract T create(String root, ImmutableList<String> names);
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/main/java/org/apache/sshd/common/file/util/BasePath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/util/BasePath.java b/sshd-core/src/main/java/org/apache/sshd/common/file/util/BasePath.java
new file mode 100644
index 0000000..a5fde82
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/util/BasePath.java
@@ -0,0 +1,367 @@
+/*
+ * 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.file.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.AbstractList;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+public abstract class BasePath<T extends BasePath<T, FS>, FS extends BaseFileSystem<T>> implements Path {
+
+    protected final FS fileSystem;
+    protected final String root;
+    protected final ImmutableList<String> names;
+
+    public BasePath(FS fileSystem, String root, ImmutableList<String> names) {
+        this.fileSystem = fileSystem;
+        this.root = root;
+        this.names = names;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected T asT() {
+        return (T) this;
+    }
+    protected T create(String root, String... names) {
+        return create(root, new ImmutableList<>(names));
+    }
+
+    protected T create(String root, Collection<String> names) {
+        return create(root, new ImmutableList<>(names.toArray(new String[names.size()])));
+    }
+
+    protected T create(String root, ImmutableList<String> names) {
+        return fileSystem.create(root, names);
+    }
+
+    public FS getFileSystem() {
+        return fileSystem;
+    }
+
+    public boolean isAbsolute() {
+        return root != null;
+    }
+
+    public T getRoot() {
+        if (isAbsolute()) {
+            return create(root);
+        }
+        return null;
+    }
+
+    public T getFileName() {
+        if (!names.isEmpty()) {
+            return create(null, names.get(names.size() - 1));
+        }
+        return null;
+    }
+
+    public T getParent() {
+        if (names.isEmpty() || names.size() == 1 && root == null) {
+            return null;
+        }
+        return create(root, names.subList(0, names.size() - 1));
+    }
+
+    public int getNameCount() {
+        return names.size();
+    }
+
+    public T getName(int index) {
+        if (index < 0 || index >= names.size()) {
+            throw new IllegalArgumentException();
+        }
+        return create(null, names.subList(index, index + 1));
+    }
+
+    public T subpath(int beginIndex, int endIndex) {
+        if ((beginIndex < 0) || (beginIndex >= names.size()) || (endIndex > names.size()) || (beginIndex >= endIndex)) {
+            throw new IllegalArgumentException();
+        }
+        return create(null, names.subList(beginIndex, endIndex));
+    }
+
+    private static boolean startsWith(List<?> list, List<?> other) {
+        return list.size() >= other.size() && list.subList(0, other.size()).equals(other);
+    }
+
+    public boolean startsWith(Path other) {
+        T p1 = asT();
+        T p2 = checkPath(other);
+        return p1.getFileSystem().equals(p2.getFileSystem())
+                && Objects.equals(p1.root, p2.root)
+                && startsWith(p1.names, p2.names);
+    }
+
+    public boolean startsWith(String other) {
+        return startsWith(getFileSystem().getPath(other));
+    }
+
+    private static boolean endsWith(List<?> list, List<?> other) {
+        return other.size() <= list.size() && list.subList(list.size() - other.size(), list.size()).equals(other);
+    }
+
+    public boolean endsWith(Path other) {
+        T p1 = asT();
+        T p2 = checkPath(other);
+        if (p2.isAbsolute()) {
+            return p1.compareTo(p2) == 0;
+        }
+        return endsWith(p1.names, p2.names);
+    }
+
+    public boolean endsWith(String other) {
+        return endsWith(getFileSystem().getPath(other));
+    }
+
+    private boolean isNormal() {
+        if (getNameCount() == 0 || getNameCount() == 1 && !isAbsolute()) {
+            return true;
+        }
+        boolean foundNonParentName = isAbsolute(); // if there's a root, the path doesn't start with ..
+        boolean normal = true;
+        for (String name : names) {
+            if (name.equals("..")) {
+                if (foundNonParentName) {
+                    normal = false;
+                    break;
+                }
+            } else {
+                if (name.equals(".")) {
+                    normal = false;
+                    break;
+                }
+                foundNonParentName = true;
+            }
+        }
+        return normal;
+    }
+
+    public T normalize() {
+        if (isNormal()) {
+            return asT();
+        }
+
+        Deque<String> newNames = new ArrayDeque<>();
+        for (String name : names) {
+            if (name.equals("..")) {
+                String lastName = newNames.peekLast();
+                if (lastName != null && !lastName.equals("..")) {
+                    newNames.removeLast();
+                } else if (!isAbsolute()) {
+                    // if there's a root and we have an extra ".." that would go up above the root, ignore it
+                    newNames.add(name);
+                }
+            } else if (!name.equals(".")) {
+                newNames.add(name);
+            }
+        }
+
+        return newNames.equals(names) ? asT() : create(root, newNames);
+    }
+
+    public T resolve(Path other) {
+        T p1 = asT();
+        T p2 = checkPath(other);
+        if (p2.isAbsolute()) {
+            return p2;
+        }
+        if (p2.names.isEmpty()) {
+            return p1;
+        }
+        String[] names = new String[p1.names.size() + p2.names.size()];
+        int index = 0;
+        for (String p : p1.names) {
+            names[index++] = p;
+        }
+        for (String p : p2.names) {
+            names[index++] = p;
+        }
+        return create(p1.root, names);
+    }
+
+    public T resolve(String other) {
+        return resolve(getFileSystem().getPath(other));
+    }
+
+    public Path resolveSibling(Path other) {
+        if (other == null) {
+            throw new NullPointerException();
+        }
+        T parent = getParent();
+        return parent == null ? other : parent.resolve(other);
+    }
+
+    public Path resolveSibling(String other) {
+        return resolveSibling(getFileSystem().getPath(other));
+    }
+
+    public T relativize(Path other) {
+        T p1 = asT();
+        T p2 = checkPath(other);
+        if (!Objects.equals(p1.getRoot(), p2.getRoot())) {
+            throw new IllegalArgumentException("Paths have different roots: " + this + ", " + other);
+        }
+        if (p2.equals(p1)) {
+            return create(null);
+        }
+        if (p1.root == null && p1.names.isEmpty()) {
+            return p2;
+        }
+        // Common subsequence
+        int sharedSubsequenceLength = 0;
+        for (int i = 0; i < Math.min(p1.names.size(), p2.names.size()); i++) {
+            if (p1.names.get(i).equals(p2.names.get(i))) {
+                sharedSubsequenceLength++;
+            } else {
+                break;
+            }
+        }
+        int extraNamesInThis = Math.max(0, p1.names.size() - sharedSubsequenceLength);
+        List<String> extraNamesInOther = (p2.names.size() <= sharedSubsequenceLength)
+                ? Collections.<String>emptyList()
+                : p2.names.subList(sharedSubsequenceLength, p2.names.size());
+        List<String> parts = new ArrayList<>(extraNamesInThis + extraNamesInOther.size());
+        // add .. for each extra name in this path
+        parts.addAll(Collections.nCopies(extraNamesInThis, ".."));
+        // add each extra name in the other path
+        parts.addAll(extraNamesInOther);
+        return create(null, parts);
+    }
+
+    public T toAbsolutePath() {
+        if (isAbsolute()) {
+            return asT();
+        }
+        return fileSystem.getDefaultDir().resolve(this);
+    }
+
+    public File toFile() {
+        throw new UnsupportedOperationException();
+    }
+
+    public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Iterator<Path> iterator() {
+        return new AbstractList<Path>() {
+            @Override
+            public Path get(int index) {
+                return getName(index);
+            }
+
+            @Override
+            public int size() {
+                return getNameCount();
+            }
+        }.iterator();
+    }
+
+    public int compareTo(Path paramPath) {
+        T p1 = asT();
+        T p2 = checkPath(paramPath);
+        int c = compare(p1.root, p2.root);
+        if (c != 0) {
+            return c;
+        }
+        for (int i = 0; i < Math.min(p1.names.size(), p2.names.size()); i++) {
+            String n1 = p1.names.get(i);
+            String n2 = p2.names.get(i);
+            c = compare(n1, n2);
+            if (c != 0) {
+                return c;
+            }
+        }
+        return p1.names.size() - p2.names.size();
+    }
+
+    private int compare(String s1, String s2) {
+        if (s1 == null) {
+            return s2 == null ? 0 : -1;
+        } else {
+            return s2 == null ? +1 : s1.compareTo(s2);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private T checkPath(Path paramPath) {
+        if (paramPath == null) {
+            throw new NullPointerException();
+        }
+        if (paramPath.getClass() != getClass()) {
+            throw new ProviderMismatchException();
+        }
+        T t = (T) paramPath;
+        if (t.fileSystem.provider() != this.fileSystem.provider()) {
+            throw new ProviderMismatchException();
+        }
+        return t;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = getFileSystem().hashCode();
+        // use hash codes from toString() form of names
+        hash = 31 * hash + (root == null ? 0 : root.hashCode());
+        for (String name : names) {
+            hash = 31 * hash + name.hashCode();
+        }
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj instanceof Path
+                && compareTo((Path) obj) == 0;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (root != null) {
+            sb.append(root);
+        }
+        for (String name : names) {
+            if (sb.length() > 0 && sb.charAt(sb.length()  - 1) != '/') {
+                sb.append(fileSystem.getSeparator());
+            }
+            sb.append(name);
+        }
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/main/java/org/apache/sshd/common/file/util/ImmutableList.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/util/ImmutableList.java b/sshd-core/src/main/java/org/apache/sshd/common/file/util/ImmutableList.java
new file mode 100644
index 0000000..88d9f5c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/util/ImmutableList.java
@@ -0,0 +1,61 @@
+/*
+ * 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.file.util;
+
+import java.util.AbstractList;
+
+/**
+ * Simple immutable array list
+ * @param <T>
+ */
+public class ImmutableList<T> extends AbstractList<T> {
+
+    final T[] data;
+    final int from;
+    final int to;
+
+    public ImmutableList(T[] data) {
+        this(data, 0, data.length);
+    }
+
+    public ImmutableList(T[] data, int from, int to) {
+        this.data = data;
+        this.from = from;
+        this.to = to;
+    }
+
+    @Override
+    public T get(int index) {
+        return data[from + index];
+    }
+
+    @Override
+    public int size() {
+        return to - from;
+    }
+
+    @Override
+    public ImmutableList<T> subList(int fromIndex, int toIndex) {
+        if (fromIndex == from && toIndex == to) {
+            return this;
+        }
+        return new ImmutableList<>(data, from + fromIndex, from + toIndex);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/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
index 3ce1c78..643a1b7 100644
--- 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
@@ -165,6 +165,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     public static final int SSH_FX_CONNECTION_LOST =   7;
     public static final int SSH_FX_OP_UNSUPPORTED =    8;
 
+    public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; // Not in v3, but we need it
+
     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;
@@ -768,7 +770,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                     SshFile p = resolveFile(path);
                     if (p.doesExist()) {
                         if (p.isDirectory()) {
-                            sendStatus(id, SSH_FX_FAILURE, p.getAbsolutePath());
+                            sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.getAbsolutePath());
                         } else {
                             sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
                         }
@@ -885,6 +887,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
                 try {
                     SshFile link = resolveFile(linkpath);
+                    // TODO: resolving the file is wrong, we should keep it relative
                     SshFile target = resolveFile(targetpath);
                     link.createSymbolicLink(target);
                     sendStatus(id, SSH_FX_OK, "");

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cc4d7877/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
new file mode 100644
index 0000000..42d1dcc
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.sftp.SftpSubsystem;
+import org.apache.sshd.util.BaseTest;
+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.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SftpFileSystemTest extends BaseTest {
+
+    private SshServer sshd;
+    private int port;
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
+        sshd.setCommandFactory(new ScpCommandFactory());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.start();
+        port = sshd.getPort();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        sshd.stop(true);
+    }
+
+    @Test
+    public void testFileSystem() throws Exception {
+        Utils.deleteRecursive(new File("target/sftp"));
+
+        FileSystem fs = FileSystems.newFileSystem(URI.create("sftp://x:x@localhost:" + port + "/"), null);
+        Path root = fs.getRootDirectories().iterator().next();
+        try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+            for (Path child : ds) {
+                System.out.println(child);
+            }
+        }
+        Path file = fs.getPath("target/sftp/client/test.txt");
+        Files.createDirectories(file.getParent());
+        Files.write(file, "Hello world\n".getBytes());
+        String buf = new String(Files.readAllBytes(file));
+        assertEquals("Hello world\n", buf);
+
+        Map<String, Object> attrs = Files.readAttributes(file, "*");
+        System.out.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());
+
+        Path link = fs.getPath("target/sftp/client/test2.txt");
+        Files.createSymbolicLink(link, file);
+        assertTrue(Files.isSymbolicLink(link));
+        assertEquals(file.toAbsolutePath().toString(), Files.readSymbolicLink(link).toString());
+
+        attrs = Files.readAttributes(file, "*", LinkOption.NOFOLLOW_LINKS);
+        System.out.println(attrs);
+
+        buf = new String(Files.readAllBytes(file));
+        assertEquals("Hello world\n", buf);
+
+        Files.delete(file);
+
+        fs.close();
+    }
+
+}


[08/15] mina-sshd git commit: [SSHD-408] Implement sftp v4, v5 and v6

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
index 750ecc9..8ad1e0b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
@@ -19,14 +19,23 @@
 package org.apache.sshd;
 
 import java.io.File;
+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.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermissions;
 import java.util.Arrays;
 import java.util.Map;
 
@@ -44,6 +53,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class SftpFileSystemTest extends BaseTest {
 
@@ -71,19 +81,40 @@ public class SftpFileSystemTest extends BaseTest {
     public void testFileSystem() throws Exception {
         Utils.deleteRecursive(new File("target/sftp"));
 
-        FileSystem fs = FileSystems.newFileSystem(URI.create("sftp://x:x@localhost:" + port + "/"), null);
+        String uri = "sftp://x:x@localhost:" + port + "/";
+
+        FileSystem fs = FileSystems.newFileSystem(URI.create(uri), null);
         Path root = fs.getRootDirectories().iterator().next();
         try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
             for (Path child : ds) {
                 System.out.println(child);
             }
         }
+        Path current = fs.getPath(".").toRealPath();
         Path file = fs.getPath("target/sftp/client/test.txt");
         Files.createDirectories(file.getParent());
         Files.write(file, "Hello world\n".getBytes());
         String buf = new String(Files.readAllBytes(file));
         assertEquals("Hello world\n", buf);
 
+        Path file2 = fs.getPath("target/sftp/client/test2.txt");
+        Path file3 = fs.getPath("target/sftp/client/test3.txt");
+        try {
+            Files.move(file2, file3);
+            fail("Expected an IOException");
+        } catch (NoSuchFileException e) {
+            // expected
+        }
+        Files.write(file2, "h".getBytes());
+        try {
+            Files.move(file, file2);
+            fail("Expected an IOException");
+        } catch (FileAlreadyExistsException e) {
+            // expected
+        }
+        Files.move(file, file2, StandardCopyOption.REPLACE_EXISTING);
+        Files.move(file2, file);
+
         Map<String, Object> attrs = Files.readAttributes(file, "*");
         System.out.println(attrs);
 
@@ -105,12 +136,47 @@ public class SftpFileSystemTest extends BaseTest {
         buf = new String(Files.readAllBytes(file));
         assertEquals("Hello world\n", buf);
 
+        try (FileChannel channel = FileChannel.open(file)) {
+            try (FileLock lock = channel.lock()) {
+                System.out.println("Locked " + lock.toString());
+
+                try (FileChannel channel2 = FileChannel.open(file)) {
+                    try (FileLock lock2 = channel2.lock()) {
+                        System.out.println("Locked " + lock2.toString());
+                        fail("Expected an exception");
+                    } catch (OverlappingFileLockException e) {
+                        // expected
+                    }
+                }
+
+            }
+        }
+
         Files.delete(file);
 
         fs.close();
     }
 
     @Test
+    public void testAttributes() throws Exception {
+        Utils.deleteRecursive(new File("target/sftp"));
+
+        FileSystem fs = FileSystems.newFileSystem(URI.create("sftp://x:x@localhost:" + port + "/"), null);
+        Path file = fs.getPath("target/sftp/client/test.txt");
+        Files.createDirectories(file.getParent());
+        Files.write(file, "Hello world\n".getBytes());
+
+        Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+
+        Files.setAttribute(file, "basic:size", 2l);
+        Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
+        Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000l));
+        Files.setAttribute(file, "posix:group", file.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByGroupName("everyone"));
+
+        fs.close();
+    }
+
+    @Test
     public void testRootFileSystem() throws Exception {
         Path rootNative = Paths.get("target/root").toAbsolutePath();
         Utils.deleteRecursive(rootNative.toFile());

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
index b44f6af..7926544 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
@@ -31,7 +31,9 @@ import java.util.Vector;
 
 import com.jcraft.jsch.ChannelSftp;
 import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.SftpException;
 import org.apache.sshd.client.SftpClient;
+import org.apache.sshd.client.sftp.DefaultSftpClient;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.util.Buffer;
 import org.apache.sshd.common.util.OsUtils;
@@ -50,6 +52,7 @@ import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -431,8 +434,52 @@ public class SftpTest extends BaseTest {
         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 + "/foobar");
+        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
+        }
+    }
+
+    @Test
+    public void testRename() throws Exception {
+        SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession session = client.connect("x", "localhost", port).await().getSession();
+        session.addPasswordIdentity("x");
+        session.auth().verify();
+
+        Utils.deleteRecursive(new File("target/sftp"));
+        new File("target/sftp").mkdirs();
+        new File("target/sftp/client").delete();
+
+        SftpClient sftp = session.createSftpClient();
+        try (OutputStream os = sftp.write("target/sftp/test.txt")) {
+            os.write("Hello world!\n".getBytes());
+        }
+
+        try {
+            sftp.rename("target/sftp/test2.txt", "target/sftp/test3.txt");
+            fail("Expected an SftpException");
+        } catch (org.apache.sshd.client.SftpException e) {
+            assertEquals(DefaultSftpClient.SSH_FX_NO_SUCH_FILE, e.getStatus());
+        }
+
+        try (OutputStream os = sftp.write("target/sftp/test2.txt")) {
+            os.write("H".getBytes());
+        }
+
+        try {
+            sftp.rename("target/sftp/test.txt", "target/sftp/test2.txt");
+            fail("Expected an SftpException");
+        } catch (org.apache.sshd.client.SftpException e) {
+            assertEquals(DefaultSftpClient.SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
+        }
+        sftp.rename("target/sftp/test.txt", "target/sftp/test2.txt", SftpClient.CopyMode.Overwrite);
     }
 
     @Test
@@ -458,7 +505,7 @@ public class SftpTest extends BaseTest {
         assertTrue(target.exists());
         assertEquals("0123456789", readFile(unixPath));
 
-        c.symlink(linkUnixPath, unixPath);
+        c.symlink(unixPath, linkUnixPath);
 
         assertTrue(link.exists());
         assertEquals("0123456789", readFile(linkUnixPath));


[05/15] mina-sshd git commit: [SSHD-379] Require JDK 1.7

Posted by gn...@apache.org.
[SSHD-379] Require JDK 1.7


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

Branch: refs/heads/master
Commit: e9ee318a74860d32edb26ed0f4ce6ae9fa9e0df2
Parents: 80d8a51
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Dec 1 16:48:59 2014 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e9ee318a/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 063f47e..0fe9dcf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -216,8 +216,8 @@
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.2</version>
                 <configuration>
-                    <source>1.5</source>
-                    <target>1.5</target>
+                    <source>1.7</source>
+                    <target>1.7</target>
                 </configuration>
             </plugin>
             <plugin>


[15/15] mina-sshd git commit: [SSHD-410] Do not seek on files when unnecessary

Posted by gn...@apache.org.
[SSHD-410] Do not seek on files when unnecessary


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

Branch: refs/heads/master
Commit: 55d45e955f23ecfbb2382713cd77a04734c8785f
Parents: 90414db
Author: Guillaume Nodet <gn...@apache.org>
Authored: Sun Feb 22 23:56:22 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 27 +++++++++++++++++---
 1 file changed, 23 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/55d45e95/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
index f515445..28fba31 100644
--- 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
@@ -448,6 +448,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
 
     protected class FileHandle extends Handle {
         final FileChannel channel;
+        long pos;
         final List<FileLock> locks = new ArrayList<>();
 
         public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
@@ -504,16 +505,34 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 setAttributes(file, attrs);
             }
             this.channel = channel;
+            this.pos = 0;
         }
 
         public int read(byte[] data, long offset) throws IOException {
-            channel.position(offset);
-            return channel.read(ByteBuffer.wrap(data));
+            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 {
-            channel.position(offset);
-            channel.write(ByteBuffer.wrap(data));
+            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


[04/15] mina-sshd git commit: [SSHD-411] SSH_FX_OP_UNSUPPORTED should not be logged as error

Posted by gn...@apache.org.
[SSHD-411] SSH_FX_OP_UNSUPPORTED should not be logged as error


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

Branch: refs/heads/master
Commit: 90414db93c21e47ab153aa40bdbdf3cff5d706d7
Parents: 22581fb
Author: Guillaume Nodet <gn...@apache.org>
Authored: Sat Feb 21 21:55:12 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 .../src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/90414db9/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
index 700e61d..f515445 100644
--- 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
@@ -766,7 +766,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 break;
             }
             default: {
-                log.error("Received: {}", type);
+                log.warn("Received: {}", type);
                 sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
                 break;
             }
@@ -783,7 +783,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             doVersionSelect(buffer, id);
             break;
         default:
-            log.error("Received unsupported SSH_FXP_EXTENDED({})", extension);
+            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;
         }


[02/15] mina-sshd git commit: Upgrade to 1.0.0-SNAPSHOT and remove unsupported modules

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpSetstatRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpSetstatRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpSetstatRequest.java
deleted file mode 100644
index f72c869..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpSetstatRequest.java
+++ /dev/null
@@ -1,52 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_SETSTAT' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpSetstatRequest extends BaseRequest {
-
-	/**
-	 * Creates a SshFxpSetstatRequest instance.
-	 * 
-	 * @param id The request id.
-	 */
-	public SshFxpSetstatRequest(final int id) {
-		super(id);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_SETSTAT;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[]";
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpStatRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpStatRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpStatRequest.java
deleted file mode 100644
index f78018d..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpStatRequest.java
+++ /dev/null
@@ -1,71 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_STAT' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpStatRequest extends BaseRequest {
-
-	private final String path;
-    private final int flags;
-
-	/**
-	 * Create a SshFxpStatRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param path The requested file path.
-	 */
-	public SshFxpStatRequest(final int id, final String path, final int flags) {
-		super(id);
-		this.path = path;
-        this.flags = flags;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_STAT;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        return getName() + "[path=" + path + "]";
-	}
-
-	/**
-	 * Returns the path.
-	 * 
-	 * @return The path.
-	 */
-	public String getPath() {
-		return path;
-	}
-
-    public int getFlags() {
-        return flags;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java
deleted file mode 100644
index 22e3b8b..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java
+++ /dev/null
@@ -1,110 +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.sftp.request;
-
-import java.util.Arrays;
-
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for 'SSH_FXP_WRITE' request.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SshFxpWriteRequest extends BaseRequest {
-	private final String handleId;
-	private final long offset;
-	private final Handle handle;
-	private final byte[] data;
-
-	/**
-	 * Creates a SshFxpWriteRequest instance.
-	 * 
-	 * @param id       The request id.
-	 * @param handleId The according file handle id.
-	 * @param offset   The write offset.
-	 * @param data     The write data.
-	 * @param handle   The according file handle.
-	 */
-	public SshFxpWriteRequest(
-			final int id, final String handleId, final long offset, final byte[] data, final Handle handle) {
-		super(id);
-		this.handleId = handleId;
-		this.offset   = offset;
-		this.data     = Arrays.copyOf(data, data.length);
-		this.handle   = handle;
-	}
-
-	/**
-	 * Returns the write data.
-	 * 
-	 * @return The write data.
-	 */
-	public byte[] getData() {
-		return Arrays.copyOf(data, data.length);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-    public SftpConstants.Type getMessage() {
-        return SftpConstants.Type.SSH_FXP_WRITE;
-    }
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String toString() {
-        String ps;
-        if (handle != null && handle.getFile() != null) {
-            ps = handle.getFile().getAbsolutePath();
-        } else {
-            ps = "";
-        }
-        return getName() + "[handle=" + handleId + ", file=" + ps + ", offset=" + offset + ", length=" + data.length + "]";
-	}
-
-	/**
-	 * Returns the according handle.
-	 * 
-	 * @return The according handle.
-	 */
-	public Handle getHandle() {
-		return handle;
-	}
-
-	/**
-	 * Returns the handle id.
-	 * 
-	 * @return The handle id.
-	 */
-	public String getHandleId() {
-		return handleId;
-	}
-
-	/**
-	 * Returns the offset.
-	 * 
-	 * @return The offset.
-	 */
-	public long getOffset() {
-		return offset;
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java
deleted file mode 100644
index 7abcda3..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java
+++ /dev/null
@@ -1,56 +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.sftp.request;
-
-import org.apache.sshd.sftp.subsystem.SftpConstants;
-
-/**
- * Data container for unknown or unsupported requests.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class UnsupportedRequest extends BaseRequest {
-
-	private final int type;
-
-	/**
-	 * Create UnsupportedRequest instance.
-	 * 
-	 * @param id   The request id.
-	 * @param type The type code.
-	 */
-	public UnsupportedRequest(final int id, final int type) {
-		super(id);
-		this.type = type;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public String getName() {
-		return "Unsupported request: " + type;
-	}
-
-    /**
-     * {@inheritDoc}
-     */
-    public SftpConstants.Type getMessage() {
-        return null;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/BaseHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/BaseHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/BaseHandle.java
deleted file mode 100644
index e4df30b..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/BaseHandle.java
+++ /dev/null
@@ -1,48 +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.sftp.subsystem;
-
-import org.apache.sshd.common.file.SshFile;
-import org.apache.sshd.sftp.Handle;
-
-import java.io.IOException;
-
-public class BaseHandle implements Handle {
-
-    private final String id;
-    private final SshFile file;
-
-    public BaseHandle(String id, SshFile file) {
-        this.id = id;
-        this.file = file;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public SshFile getFile() {
-        return file;
-    }
-
-    public void close() throws IOException {
-        file.handleClose();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DefaultSftpletContainer.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DefaultSftpletContainer.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DefaultSftpletContainer.java
deleted file mode 100644
index 56734ee..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DefaultSftpletContainer.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.sftp.subsystem;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.sshd.common.Session;
-import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.sftp.Reply;
-import org.apache.sshd.sftp.Request;
-import org.apache.sshd.sftp.SftpSession;
-import org.apache.sshd.sftp.Sftplet;
-import org.apache.sshd.sftp.request.BaseRequest;
-
-
-/**
- * The default Sftplet implementation.
- * It's just calling other added Sftplet implementations.
- * 
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class DefaultSftpletContainer implements Sftplet {
-	private List<Sftplet> sftpLetList = new ArrayList<Sftplet>();
-
-    /**
-     * {@inheritDoc}
-     */
-    public void onConnect(final SftpSession session) {
-		for (Sftplet sftpLet : sftpLetList) {
-			sftpLet.onConnect(session);
-		}
-	}
-
-    /**
-     * {@inheritDoc}
-     */
-	public void onDisconnect(final SftpSession session) {
-		for (Sftplet sftpLet : sftpLetList) {
-			sftpLet.onDisconnect(session);
-		}
-	}
-
-    /**
-     * {@inheritDoc}
-     */
-	public Reply beforeCommand(final SftpSession session, final Request sftpRequest) {
-		Reply reply = null;
-		for (Sftplet sftpLet : sftpLetList) {
-			reply = sftpLet.beforeCommand(session, sftpRequest);
-		}
-		return reply;
-	}
-
-    /**
-     * {@inheritDoc}
-     */
-	public Reply afterCommand(final SftpSession session, final Request sftpRequest, final Reply sftpReply)
-			throws IOException {
-		Reply reply = sftpReply;
-		for (Sftplet sftpLet : sftpLetList) {
-			reply = sftpLet.afterCommand(session, sftpRequest, reply);
-		}
-		return reply;
-	}
-
-    /**
-     * {@inheritDoc}
-     */
-	public void add(final Sftplet sftpLet) {
-		this.sftpLetList.add(sftpLet);
-	}
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DirectoryHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DirectoryHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DirectoryHandle.java
deleted file mode 100644
index 06891cd..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/DirectoryHandle.java
+++ /dev/null
@@ -1,65 +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.sftp.subsystem;
-
-import org.apache.sshd.common.file.SshFile;
-
-import java.util.Iterator;
-import java.util.List;
-
-public class DirectoryHandle extends BaseHandle implements Iterator<SshFile> {
-
-    boolean done;
-    // the directory should be read once at "open directory"
-    List<SshFile> fileList = null;
-    int fileIndex;
-
-    public DirectoryHandle(String id, SshFile file) {
-        super(id, file);
-        fileList = file.listSshFiles();
-        fileIndex = 0;
-    }
-
-    public boolean isDone() {
-        return done;
-    }
-
-    public void setDone(boolean done) {
-        this.done = done;
-    }
-
-    public boolean hasNext() {
-        return fileIndex < fileList.size();
-    }
-
-    public SshFile next() {
-        SshFile f = fileList.get(fileIndex);
-        fileIndex++;
-        return f;
-    }
-
-    public void remove() {
-        throw new UnsupportedOperationException();
-    }
-
-    public void clearFileList() {
-        // allow the garbage collector to do the job
-        fileList = null;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/FileHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/FileHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/FileHandle.java
deleted file mode 100644
index 5ebd14c..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/FileHandle.java
+++ /dev/null
@@ -1,86 +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.sftp.subsystem;
-
-import org.apache.sshd.common.util.IoUtils;
-import org.apache.sshd.common.file.SshFile;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class FileHandle extends BaseHandle {
-
-    int flags;
-    OutputStream output;
-    long outputPos;
-    InputStream input;
-    long inputPos;
-    long length;
-
-    public FileHandle(String id, SshFile sshFile, int flags) {
-        super(id, sshFile);
-        this.flags = flags;
-    }
-
-    public int getFlags() {
-        return flags;
-    }
-
-    public int read(byte[] data, long offset) throws IOException {
-        if (input != null && offset >= length) {
-            return -1;
-        }
-        if (input != null && offset != inputPos) {
-            IoUtils.closeQuietly(input);
-            input = null;
-        }
-        if (input == null) {
-            input = getFile().createInputStream(offset);
-            length = getFile().getSize();
-            inputPos = offset;
-        }
-        if (offset >= length) {
-            return -1;
-        }
-        int read = input.read(data);
-        inputPos += read;
-        return read;
-    }
-
-    public void write(byte[] data, long offset) throws IOException {
-        if (output != null && offset != outputPos) {
-            IoUtils.closeQuietly(output);
-            output = null;
-        }
-        if (output == null) {
-            output = getFile().createOutputStream(offset);
-        }
-        output.write(data);
-        outputPos += data.length;
-    }
-
-    @Override
-    public void close() throws IOException {
-        IoUtils.closeQuietly(output, input);
-        output = null;
-        input = null;
-        super.close();
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/Serializer.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/Serializer.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/Serializer.java
deleted file mode 100644
index 2ad70b5..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/Serializer.java
+++ /dev/null
@@ -1,535 +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.sftp.subsystem;
-
-import org.apache.sshd.common.util.Buffer;
-import org.apache.sshd.common.file.SshFile;
-import org.apache.sshd.sftp.Handle;
-import org.apache.sshd.sftp.Reply;
-import org.apache.sshd.sftp.Request;
-import org.apache.sshd.sftp.SftpSession;
-import org.apache.sshd.sftp.reply.*;
-import org.apache.sshd.sftp.request.*;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import static org.apache.sshd.sftp.subsystem.SftpConstants.*;
-import static org.apache.sshd.sftp.subsystem.SftpConstants.SSH_FXP_STATUS;
-
-public class Serializer {
-
-    private final SftpSession session;
-
-    public Serializer(SftpSession session) {
-        this.session = session;
-    }
-
-    protected Request readRequest(final Buffer buffer) {
-        Request request;
-
-        int length = buffer.getInt();
-        byte type = buffer.getByte();
-        int id = buffer.getInt();
-
-        switch (type) {
-            case SSH_FXP_INIT: {
-                if (length != 5) {
-                    throw new IllegalArgumentException();
-                }
-                request = new SshFxpInitRequest(id);
-                break;
-            }
-            case SSH_FXP_OPEN: {
-                String path = buffer.getString();
-                int acc = (session.getVersion() > 4) ? buffer.getInt() : 0;
-                int flags = buffer.getInt();
-                request = new SshFxpOpenRequest(id, path, acc, flags);
-                break;
-            }
-            case SSH_FXP_CLOSE: {
-                String handleId = buffer.getString();
-                Handle handle = session.getHandle(handleId);
-                request = new SshFxpCloseRequest(id, handleId, handle);
-                break;
-            }
-            case SSH_FXP_READ: {
-                String handleId = buffer.getString();
-                long offset = buffer.getLong();
-                int len = buffer.getInt();
-                Handle handle = session.getHandle(handleId);
-                request = new SshFxpReadRequest(id, handleId, offset, len, handle);
-                break;
-            }
-            case SSH_FXP_WRITE: {
-                String handleId = buffer.getString();
-                long offset = buffer.getLong();
-                byte[] data = buffer.getBytes();
-                Handle handle = session.getHandle(handleId);
-                request = new SshFxpWriteRequest(id, handleId, offset, data, handle);
-                break;
-            }
-            case SSH_FXP_LSTAT: {
-                String path = buffer.getString();
-                int flags = 0;
-                if (session.getVersion() > 5) {
-                    flags = buffer.getInt();
-                }
-                request = new SshFxpLstatRequest(id, path, flags);
-                break;
-            }
-            case SSH_FXP_FSTAT: {
-                String handle = buffer.getString();
-                int flags = 0;
-                if (session.getVersion() > 5) {
-                    flags = buffer.getInt();
-                }
-                Handle p = session.getHandle(handle);
-                request = new SshFxpFstatRequest(id, handle, p);
-                break;
-            }
-            case SSH_FXP_SETSTAT: {
-                request = new SshFxpSetstatRequest(id);
-                break;
-            }
-            case SSH_FXP_FSETSTAT: {
-                request = new SshFxpFsetstatRequest(id);
-                break;
-            }
-            case SSH_FXP_OPENDIR: {
-                String path = buffer.getString();
-                request = new SshFxpOpendirRequest(id, path);
-                break;
-            }
-            case SSH_FXP_READDIR: {
-                String handle = buffer.getString();
-                Handle p = session.getHandle(handle);
-                request = new SshFxpReaddirRequest(id, handle, p);
-                break;
-            }
-            case SSH_FXP_REMOVE: {
-                String path = buffer.getString();
-                request = new SshFxpRemoveRequest(id, path);
-                break;
-            }
-            case SSH_FXP_MKDIR: {
-                String path = buffer.getString();
-                request = new SshFxpMkdirRequest(id, path);
-                break;
-            }
-            case SSH_FXP_RMDIR: {
-                String path = buffer.getString();
-                request = new SshFxpRmdirRequest(id, path);
-                break;
-            }
-            case SSH_FXP_REALPATH: {
-                String path = buffer.getString();
-                byte options = SSH_FXP_REALPATH_NO_CHECK;
-                List<String> compose = new ArrayList<String>();
-                if (session.getVersion() >= 6 && buffer.available() > 0) {
-                    options = buffer.getByte();
-                }
-                while (session.getVersion() >= 6 && buffer.available() > 0) {
-                    compose.add(buffer.getString());
-                }
-                request = new SshFxpRealpathRequest(id, path, options, compose);
-                break;
-            }
-            case SSH_FXP_STAT: {
-                String path = buffer.getString();
-                int flags = 0;
-                if (session.getVersion() > 5) {
-                    flags = buffer.getInt();
-                }
-                request = new SshFxpStatRequest(id, path, flags);
-                break;
-            }
-            case SSH_FXP_RENAME: {
-                final String oldPath = buffer.getString();
-                final String newPath = buffer.getString();
-                request = new SshFxpRenameRequest(id, oldPath, newPath);
-                break;
-            }
-            case SSH_FXP_READLINK: {
-                // TODO: implement
-                request = new UnsupportedRequest(id, Type.SSH_FXP_READLINK.toByte());
-                break;
-            }
-            case SSH_FXP_LINK: {
-                // TODO: implement
-                request = new UnsupportedRequest(id, Type.SSH_FXP_LINK.toByte());
-                break;
-            }
-            case SSH_FXP_BLOCK: {
-                // TODO: implement
-                request = new UnsupportedRequest(id, Type.SSH_FXP_BLOCK.toByte());
-                break;
-            }
-            case SSH_FXP_UNBLOCK: {
-                // TODO: implement
-                request = new UnsupportedRequest(id, Type.SSH_FXP_UNBLOCK.toByte());
-                break;
-            }
-            default: {
-                request = new UnsupportedRequest(id, type);
-                break;
-            }
-        }
-        return request;
-    }
-
-    /*
-    public Reply readReply(final Buffer buffer) {
-        Reply reply;
-
-        int length = buffer.getInt();
-        byte type = buffer.getByte();
-        int id = buffer.getInt();
-
-        switch (type) {
-            //
-            // Replies
-            //
-            case SSH_FXP_VERSION: {
-                // TODO
-                break;
-            }
-            case SSH_FXP_STATUS: {
-                // TODO
-                break;
-            }
-            case SSH_FXP_HANDLE: {
-                // TODO
-                break;
-            }
-            case SSH_FXP_DATA: {
-                // TODO
-                break;
-            }
-            case SSH_FXP_NAME: {
-                // TODO
-                break;
-            }
-            case SSH_FXP_ATTRS: {
-                // TODO
-                break;
-            }
-            default:
-                throw new IllegalStateException("Unsupported reply type: " + type);
-        }
-        return reply;
-    }
-
-    public Buffer writeRequest(final Request request) throws IOException {
-        if (request == null) {
-            throw new IllegalStateException("Can not serialize a null request");
-        } else if (request instanceof ...) {
-            // TODO
-        } else {
-            throw new IllegalStateException("Unsupported request: " + request.toString());
-        }
-    }
-    */
-
-    public Buffer writeReply(final Reply reply) throws IOException {
-        if (reply == null) {
-            throw new IllegalStateException("Can not serialize a null reply");
-        } else if (reply instanceof SshFxpAttrsReply) {
-            return writeAttrsReply((SshFxpAttrsReply) reply);
-        } else if (reply instanceof SshFxpDataReply) {
-            return writeDataReply((SshFxpDataReply) reply);
-        } else if (reply instanceof SshFxpHandleReply) {
-            return writeHandleReply((SshFxpHandleReply) reply);
-        } else if (reply instanceof SshFxpNameReply) {
-            return writeNameReply((SshFxpNameReply) reply);
-        } else if (reply instanceof SshFxpStatusReply) {
-            return writeStatus((SshFxpStatusReply) reply);
-        } else if (reply instanceof SshFxpVersionReply) {
-            return writeVersionReply((SshFxpVersionReply) reply);
-        } else {
-            throw new IllegalStateException("Unsupported reply: " + reply.toString());
-        }
-    }
-
-    private Buffer writeAttrsReply(SshFxpAttrsReply reply) throws IOException {
-        int id = reply.getId();
-        FileAttributes attrs = reply.getAttributes();
-
-        Buffer buffer = new Buffer();
-        buffer.putByte((byte) SSH_FXP_ATTRS);
-        buffer.putInt(id);
-        writeAttrs(buffer, attrs);
-        return buffer;
-    }
-
-    private Buffer writeVersionReply(SshFxpVersionReply reply) {
-        int version = reply.getVersion();
-
-        Buffer buffer = new Buffer();
-        buffer.putByte((byte) SSH_FXP_VERSION);
-        buffer.putInt(version);
-        return buffer;
-    }
-
-    private Buffer writeNameReply(SshFxpNameReply reply) {
-        int id = reply.getId();
-        Collection<SshFxpNameReply.ReplyFile> files = reply.getFiles();
-        Buffer buffer = new Buffer();
-        buffer.putByte((byte) SSH_FXP_NAME);
-        buffer.putInt(id);
-        buffer.putInt(files.size());
-        for (SshFxpNameReply.ReplyFile f : files) {
-            buffer.putString(f.getFileName());
-            if (session.getVersion() <= 3) {
-                buffer.putString(f.getLongName()); // Format specified in the specs
-            }
-            writeAttrs(buffer, f.getAttrs());
-        }
-        if (session.getVersion() >= 6 && reply.isEol()) {
-            buffer.putBoolean(true);
-        }
-        return buffer;
-    }
-
-    private Buffer writeHandleReply(SshFxpHandleReply reply) throws IOException {
-        int id = reply.getId();
-        String handle = reply.getHandle().getId();
-
-        Buffer buffer = new Buffer();
-        buffer.putByte((byte) SSH_FXP_HANDLE);
-        buffer.putInt(id);
-        buffer.putString(handle);
-        return buffer;
-    }
-
-    private Buffer writeDataReply(SshFxpDataReply reply) {
-        long id = reply.getId();
-        byte[] data = reply.getData();
-        int offset = reply.getOffset();
-        int length = reply.getLength();
-        boolean eof = reply.isEof();
-
-        Buffer buffer = new Buffer(length + 5);
-        buffer.putByte((byte) SSH_FXP_DATA);
-        buffer.putInt(id);
-        buffer.putBytes(data, offset, length);
-        if (session.getVersion() >= 6 && eof) {
-            buffer.putBoolean(eof);
-        }
-        return buffer;
-    }
-
-    private Buffer writeStatus(SshFxpStatusReply reply) throws IOException {
-        int id = reply.getId();
-        int substatus = reply.getSubstatus();
-        String msg = reply.getMsg();
-        String lang = reply.getLang();
-
-        Buffer buffer = new Buffer();
-        buffer.putByte((byte) SSH_FXP_STATUS);
-        buffer.putInt(id);
-        buffer.putInt(mapToVersion(substatus));
-        buffer.putString(msg);
-        buffer.putString(lang);
-        return buffer;
-    }
-
-    protected static int mapV4ToV3(int code) {
-        switch (code) {
-            case SSH_FX_INVALID_HANDLE:
-                return SSH_FX_FAILURE;
-            case SSH_FX_NO_SUCH_PATH:
-                return SSH_FX_NO_SUCH_FILE;
-            case SSH_FX_FILE_ALREADY_EXISTS:
-                return SSH_FX_FAILURE;
-            case SSH_FX_WRITE_PROTECT:
-                return SSH_FX_PERMISSION_DENIED;
-            case SSH_FX_NO_MEDIA:
-                return SSH_FX_FAILURE;
-            default:
-                return code;
-        }
-    }
-
-    protected static int mapV5ToV4(int code) {
-        switch (code) {
-            case SSH_FX_NO_SPACE_ON_FILESYSTEM:
-                return SSH_FX_FAILURE;
-            case SSH_FX_QUOTA_EXCEEDED:
-                return SSH_FX_FAILURE;
-            case SSH_FX_UNKNOWN_PRINCIPAL:
-                return SSH_FX_FAILURE;
-            case SSH_FX_LOCK_CONFLICT:
-                return SSH_FX_FAILURE;
-            default:
-                return code;
-        }
-    }
-
-    protected static int mapV6ToV5(int code) {
-        switch (code) {
-            case SSH_FX_DIR_NOT_EMPTY:
-                return SSH_FX_FAILURE;
-            case SSH_FX_NOT_A_DIRECTORY:
-                return SSH_FX_NO_SUCH_FILE;
-            case SSH_FX_INVALID_FILENAME:
-                return SSH_FX_NO_SUCH_FILE;
-            case SSH_FX_LINK_LOOP:
-                return SSH_FX_FAILURE;
-            case SSH_FX_CANNOT_DELETE:
-                return SSH_FX_PERMISSION_DENIED;
-            case SSH_FX_INVALID_PARAMETER:
-                return SSH_FX_FAILURE;
-            case SSH_FX_FILE_IS_A_DIRECTORY:
-                return SSH_FX_NO_SUCH_FILE;
-            case SSH_FX_BYTE_RANGE_LOCK_CONFLICT:
-                return SSH_FX_FAILURE;
-            case SSH_FX_BYTE_RANGE_LOCK_REFUSED:
-                return SSH_FX_FAILURE;
-            case SSH_FX_DELETE_PENDING:
-                return SSH_FX_FAILURE;
-            case SSH_FX_FILE_CORRUPT:
-                return SSH_FX_FAILURE;
-            case SSH_FX_OWNER_INVALID:
-                return SSH_FX_PERMISSION_DENIED;
-            case SSH_FX_GROUP_INVALID:
-                return SSH_FX_PERMISSION_DENIED;
-            case SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK:
-                return SSH_FX_FAILURE;
-            default:
-                return code;
-        }
-    }
-
-    protected static int mapToVersion(int code, int version) {
-        int mappedCode = code;
-        if (version < 6) {
-            mappedCode = mapV6ToV5(mappedCode);
-        }
-        if (version < 5) {
-            mappedCode = mapV5ToV4(mappedCode);
-        }
-        if (version < 4) {
-            mappedCode = mapV4ToV3(mappedCode);
-        }
-        return mappedCode;
-    }
-
-    protected int mapToVersion(int code) {
-        return mapToVersion(code, session.getVersion());
-    }
-
-    protected void writeAttrs(Buffer buffer, FileAttributes attrs) {
-        int version = session.getVersion();
-        int flags = attrs.getFlags();
-        buffer.putInt(flags);
-        if (session.getVersion() >= 4) {
-            buffer.putByte(attrs.getType());
-        }
-        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-            buffer.putLong(attrs.getSize());
-        }
-        if (version >= 6 && (flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) {
-            buffer.putLong(attrs.getAllocationSize());
-        }
-        if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
-            buffer.putString(attrs.getOwner());
-            buffer.putString(attrs.getGroup());
-        }
-        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
-            int perms = attrs.getPermissions();
-            if (session.getVersion() < 4) {
-                if (attrs.getType() == SSH_FILEXFER_TYPE_REGULAR) {
-                    perms |= 0100000;
-                } else if (attrs.getType() == SSH_FILEXFER_TYPE_DIRECTORY) {
-                    perms |= 0040000;
-                }
-            }
-            buffer.putInt(perms);
-        }
-        if (version <= 3 && (flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-            buffer.putInt(attrs.getAccessTime());
-            buffer.putInt(attrs.getAccessTime());
-        }
-    }
-
-    protected void writeAttrs(Buffer buffer, SshFile file, int flags) {
-        if (session.getVersion() >= 4) {
-            long size = file.getSize();
-            String username = session.getSession().getUsername();
-            long lastModif = file.getLastModified();
-            int p = 0;
-            if (file.isReadable()) {
-                p |= S_IRUSR;
-            }
-            if (file.isWritable()) {
-                p |= S_IWUSR;
-            }
-            if (file.isExecutable()) {
-                p |= S_IXUSR;
-            }
-            if (file.isFile()) {
-                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS);
-                buffer.putByte((byte) SSH_FILEXFER_TYPE_REGULAR);
-                buffer.putInt(p);
-            } else if (file.isDirectory()) {
-                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS);
-                buffer.putByte((byte) SSH_FILEXFER_TYPE_DIRECTORY);
-                buffer.putInt(p);
-            } else {
-                buffer.putInt(0);
-                buffer.putByte((byte) SSH_FILEXFER_TYPE_UNKNOWN);
-            }
-        } else {
-            int p = 0;
-            if (file.isFile()) {
-                p |= 0100000;
-            }
-            if (file.isDirectory()) {
-                p |= 0040000;
-            }
-            if (file.isReadable()) {
-                p |= 0000400;
-            }
-            if (file.isWritable()) {
-                p |= 0000200;
-            }
-            if (file.isExecutable()) {
-                p |= 0000100;
-            }
-            if (file.isFile()) {
-                buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
-                buffer.putLong(file.getSize());
-                buffer.putInt(p);
-                buffer.putInt(file.getLastModified() / 1000);
-                buffer.putInt(file.getLastModified() / 1000);
-            } else if (file.isDirectory()) {
-                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
-                buffer.putInt(p);
-                buffer.putInt(file.getLastModified() / 1000);
-                buffer.putInt(file.getLastModified() / 1000);
-            } else {
-                buffer.putInt(0);
-            }
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpConstants.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpConstants.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpConstants.java
deleted file mode 100644
index 0b4ae4a..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpConstants.java
+++ /dev/null
@@ -1,305 +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.sftp.subsystem;
-
-public final class SftpConstants {
-
-    private SftpConstants() { }
-
-    //
-    // Packet types
-    //
-
-    public enum Type {
-
-        SSH_FXP_INIT(1),
-        SSH_FXP_VERSION(2),
-        SSH_FXP_OPEN(3),
-        SSH_FXP_CLOSE(4),
-        SSH_FXP_READ(5),
-        SSH_FXP_WRITE(6),
-        SSH_FXP_LSTAT(7),
-        SSH_FXP_FSTAT(8),
-        SSH_FXP_SETSTAT(9),
-        SSH_FXP_FSETSTAT(10),
-        SSH_FXP_OPENDIR(11),
-        SSH_FXP_READDIR(12),
-        SSH_FXP_REMOVE(13),
-        SSH_FXP_MKDIR(14),
-        SSH_FXP_RMDIR(15),
-        SSH_FXP_REALPATH(16),
-        SSH_FXP_STAT(17),
-        SSH_FXP_RENAME(18),
-        SSH_FXP_READLINK(19),
-        SSH_FXP_LINK(21),
-        SSH_FXP_BLOCK(22),
-        SSH_FXP_UNBLOCK(23),
-
-        SSH_FXP_STATUS(101),
-        SSH_FXP_HANDLE(102),
-        SSH_FXP_DATA(103),
-        SSH_FXP_NAME(104),
-        SSH_FXP_ATTRS(105),
-
-        SSH_FXP_EXTENDED(200),
-        SSH_FXP_EXTENDED_REPLY(201);
-
-        private byte b;
-        private Type(int b) {
-            this.b = (byte) b;
-        }
-
-        public byte toByte() {
-            return b;
-        }
-
-        /*
-        static Type[] commands;
-        static {
-            commands = new Type[256];
-            for (Type c : Type.values()) {
-                if (commands[c.toByte()] == null) {
-                    commands[c.toByte()] = c;
-                }
-            }
-        }
-        public static Type fromByte(byte b) {
-            return commands[b];
-        }
-        */
-    }
-    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_LINK = 21;
-    public static final int SSH_FXP_BLOCK = 22;
-    public static final int SSH_FXP_UNBLOCK = 23;
-
-    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;
-
-
-    //
-    // Error codes
-    //
-    public enum ErrorCode {
-
-        SSH_FX_OK(0),
-        SSH_FX_EOF(1),
-        SSH_FX_NO_SUCH_FILE(2),
-        SSH_FX_PERMISSION_DENIED(3),
-        SSH_FX_FAILURE(4),
-        SSH_FX_BAD_MESSAGE(5),
-        SSH_FX_NO_CONNECTION(6),
-        SSH_FX_CONNECTION_LOST(7),
-        SSH_FX_OP_UNSUPPORTED(8),
-        SSH_FX_INVALID_HANDLE(9),
-        SSH_FX_NO_SUCH_PATH(10),
-        SSH_FX_FILE_ALREADY_EXISTS(11),
-        SSH_FX_WRITE_PROTECT(12),
-        SSH_FX_NO_MEDIA(13),
-        SSH_FX_NO_SPACE_ON_FILESYSTEM(14),
-        SSH_FX_QUOTA_EXCEEDED(15),
-        SSH_FX_UNKNOWN_PRINCIPAL(16),
-        SSH_FX_LOCK_CONFLICT(17),
-        SSH_FX_DIR_NOT_EMPTY(18),
-        SSH_FX_NOT_A_DIRECTORY(19),
-        SSH_FX_INVALID_FILENAME(20),
-        SSH_FX_LINK_LOOP(21),
-        SSH_FX_CANNOT_DELETE(22),
-        SSH_FX_INVALID_PARAMETER(23),
-        SSH_FX_FILE_IS_A_DIRECTORY(24),
-        SSH_FX_BYTE_RANGE_LOCK_CONFLICT(25),
-        SSH_FX_BYTE_RANGE_LOCK_REFUSED(26),
-        SSH_FX_DELETE_PENDING(27),
-        SSH_FX_FILE_CORRUPT(28),
-        SSH_FX_OWNER_INVALID(29),
-        SSH_FX_GROUP_INVALID(30),
-        SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK(31);
-
-        private byte b;
-        private ErrorCode(int b) {
-            this.b = (byte) b;
-        }
-
-        public byte toByte() {
-            return b;
-        }
-
-        static ErrorCode[] codes;
-        static {
-            codes = new ErrorCode[256];
-            for (ErrorCode c : ErrorCode.values()) {
-                if (codes[c.toByte()] == null) {
-                    codes[c.toByte()] = c;
-                }
-            }
-        }
-        public static ErrorCode fromByte(byte b) {
-            return codes[b];
-        }
-    }
-
-    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_PRINCIPAL = 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_PERMISSIONS = 0x00000004;
-    public static final int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; //v3 naming convention
-    public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008;
-    public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010;
-    public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020;
-    public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040;
-    public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080;
-    public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100;
-    public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200;
-    public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400;
-    public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800;
-    public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000;
-    public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000;
-    public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000;
-    public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000;
-    public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
-
-    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;
-    public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7;
-    public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8;
-    public static final int SSH_FILEXFER_TYPE_FIFO = 9;
-
-
-    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_BLOCK_READ = 0x00000040;
-    public static final int SSH_FXF_BLOCK_WRITE = 0x00000080;
-    public static final int SSH_FXF_BLOCK_DELETE = 0x00000100;
-    public static final int SSH_FXF_BLOCK_ADVISORY = 0x00000200;
-    public static final int SSH_FXF_NOFOLLOW = 0x00000400;
-    public static final int SSH_FXF_DELETE_ON_CLOSE = 0x00000800;
-    public static final int SSH_FXF_ACCESS_AUDIT_ALARM_INFO = 0x00001000;
-    public static final int SSH_FXF_ACCESS_BACKUP = 0x00002000;
-    public static final int SSH_FXF_BACKUP_STREAM = 0x00004000;
-    public static final int SSH_FXF_OVERRIDE_OWNER = 0x00008000;
-
-    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_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 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 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 final int S_ISUID = 0004000;
-    public static final int S_ISGID = 0002000;
-    public static final int S_ISVTX = 0001000;
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/80d8a51a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java
deleted file mode 100644
index 31e3f74..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java
+++ /dev/null
@@ -1,786 +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.sftp.subsystem;
-
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.Session;
-import org.apache.sshd.common.file.FileSystemAware;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
-import org.apache.sshd.common.util.Buffer;
-import org.apache.sshd.common.util.SelectorUtils;
-import org.apache.sshd.common.util.ThreadUtils;
-import org.apache.sshd.server.*;
-import org.apache.sshd.server.channel.ChannelDataReceiver;
-import org.apache.sshd.server.channel.ChannelSession;
-import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.sftp.*;
-import org.apache.sshd.sftp.reply.*;
-import org.apache.sshd.sftp.request.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import static org.apache.sshd.sftp.subsystem.SftpConstants.*;
-
-/**
- * SFTP subsystem
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpSubsystem implements Command, SessionAware, FileSystemAware, SftpSession, ChannelDataReceiver, ChannelSessionAware {
-
-    protected static final Logger LOG = LoggerFactory.getLogger(SftpSubsystem.class);
-
-    public static class Factory implements NamedFactory<Command> {
-
-        public Factory() {
-        }
-
-        public Command create() {
-            return new SftpSubsystem();
-        }
-
-        public String getName() {
-            return "sftp";
-        }
-    }
-
-    public static final int LOWER_SFTP_IMPL = 3; // Working implementation from v3
-    public static final int HIGHER_SFTP_IMPL = 6; //  .. up to
-    public static final String ALL_SFTP_IMPL = "3,4,5,6";
-    public static final int  MAX_PACKET_LENGTH = 1024 * 16;
-
-    /**
-     * 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";
-
-
-    private ExitCallback callback;
-    private InputStream in;
-    private OutputStream out;
-    private OutputStream err;
-    private Environment env;
-    private ServerSession session;
-    private ChannelSession channel;
-    private boolean closed = false;
-
-    private FileSystemView root;
-
-    private int version;
-    private Map<String, Handle> handles = new HashMap<String, Handle>();
-
-    private Sftplet sftpLet = new DefaultSftpletContainer();
-    private Serializer serializer = new Serializer(this);
-
-    private final ExecutorService executor;
-
-    public SftpSubsystem() {
-        executor = ThreadUtils.newSingleThreadExecutor("sftp[" + Integer.toHexString(hashCode()) + "]");
-    }
-
-    public void setSftpLet(final Sftplet sftpLet) {
-        this.sftpLet = sftpLet;
-    }
-
-    public int getVersion() {
-        return version;
-    }
-
-    public Session getSession() {
-        return session;
-    }
-
-    public Handle getHandle(String id) {
-        return handles.get(id);
-    }
-
-    public Handle createFileHandle(SshFile file, int flags) {
-        String id = UUID.randomUUID().toString();
-        Handle handle = new FileHandle(id,  file, flags);
-        handles.put(id, handle);
-        return handle;
-    }
-
-    public Handle createDirectoryHandle(SshFile file) {
-        String id = UUID.randomUUID().toString();
-        Handle handle = new DirectoryHandle(id, file);
-        handles.put(id, handle);
-        return handle;
-    }
-
-    public void setChannelSession(ChannelSession channel) {
-        this.channel = channel;
-        channel.setDataReceiver(this);
-    }
-
-    public void setSession(ServerSession session) {
-        this.session = session;
-        sftpLet.onConnect(this);
-    }
-
-    public void setFileSystemView(FileSystemView view) {
-        this.root = view;
-    }
-
-    public void setExitCallback(ExitCallback callback) {
-        this.callback = callback;
-    }
-
-    public void setInputStream(InputStream in) {
-        this.in = in;
-    }
-
-    public void setOutputStream(OutputStream out) {
-        this.out = out;
-    }
-
-    public void setErrorStream(OutputStream err) {
-        this.err = err;
-    }
-
-    public void start(Environment env) throws IOException {
-        this.env = env;
-    }
-
-    private Buffer buffer = new Buffer();
-
-    public int data(ChannelSession channel, byte[] buf, int start, int len) throws IOException {
-        Buffer incoming = new Buffer(buf,  start, len);
-        // If we already have partial data, we need to append it to the buffer and use it
-        if (buffer.available() > 0) {
-            buffer.putBuffer(incoming);
-            incoming = buffer;
-        }
-        // Process commands
-        int rpos = incoming.rpos();
-        while (receive(incoming));
-        int read = incoming.rpos() - rpos;
-        // Compact and add remaining data
-        buffer.compact();
-        if (buffer != incoming && incoming.available() > 0) {
-            buffer.putBuffer(incoming);
-        }
-        return read;
-    }
-
-    protected boolean receive(Buffer incoming) throws IOException {
-        int rpos = incoming.rpos();
-        int wpos = incoming.wpos();
-        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;
-    }
-
-    public void close() throws IOException {
-        executor.shutdownNow();
-        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);
-        sftpLet.onDisconnect(this);
-    }
-
-    public void process(Buffer buffer) throws IOException {
-        final Request request = serializer.readRequest(buffer);
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Received sftp request: " + request);
-        }
-        executor.execute(new Runnable() {
-            public void run() {
-                try {
-                    Reply reply = sftpLet.beforeCommand(SftpSubsystem.this, request);
-                    if (reply == null) {
-                        reply = doProcess(request);
-                    }
-                    reply = sftpLet.afterCommand(SftpSubsystem.this, request, reply);
-                    if (reply != null) {
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Sending sftp reply: " + reply);
-                        }
-                        Buffer buffer = serializer.writeReply(reply);
-                        send(buffer);
-                    }
-                } catch (Throwable t) {
-                    // TODO do something
-                    t.printStackTrace();
-                }
-            }
-        });
-    }
-
-    protected Reply doProcess(Request request) throws IOException {
-        try {
-            if (request instanceof SshFxpInitRequest) {
-                return doProcessInit((SshFxpInitRequest) request);
-            } else if (request instanceof SshFxpOpenRequest) {
-                return doProcessOpen((SshFxpOpenRequest) request);
-            } else if (request instanceof SshFxpCloseRequest) {
-                return doProcessClose((SshFxpCloseRequest) request);
-            } else if (request instanceof SshFxpReadRequest) {
-                return doProcessRead((SshFxpReadRequest) request);
-            } else if (request instanceof SshFxpWriteRequest) {
-                return doProcessWrite((SshFxpWriteRequest) request);
-            } else if ((request instanceof SshFxpLstatRequest)
-                    || (request instanceof SshFxpStatRequest)) {
-                return doProcessStat(request);
-            } else if (request instanceof SshFxpFstatRequest) {
-                return doProcessFstat((SshFxpFstatRequest) request);
-            } else if (request instanceof SshFxpOpendirRequest) {
-                return doProcessOpendir((SshFxpOpendirRequest) request);
-            } else if (request instanceof SshFxpReaddirRequest) {
-                return doProcessReaddir((SshFxpReaddirRequest) request);
-            } else if (request instanceof SshFxpRemoveRequest) {
-                return doProcessRemove((SshFxpRemoveRequest) request);
-            } else if (request instanceof SshFxpMkdirRequest) {
-                return doProcessMkdir((SshFxpMkdirRequest) request);
-            } else if (request instanceof SshFxpRmdirRequest) {
-                return doProcessRmdir((SshFxpRmdirRequest) request);
-            } else if (request instanceof SshFxpRealpathRequest) {
-                return doProcessRealpath((SshFxpRealpathRequest) request);
-            } else if (request instanceof SshFxpRenameRequest) {
-                return doProcessRename((SshFxpRenameRequest) request);
-            } else if ((request instanceof SshFxpSetstatRequest)
-                    || (request instanceof SshFxpFsetstatRequest)) {
-                return doProcessSetstat(request);
-
-            } else {
-                LOG.error("Received: {}", request);
-                int id = request.getId();
-                return new SshFxpStatusReply(id, SSH_FX_OP_UNSUPPORTED, "Command " + request + " is unsupported or not implemented");
-            }
-        } catch (IOException e) {
-            int id = request.getId();
-            return new SshFxpStatusReply(id, SSH_FX_FAILURE, e.getMessage());
-        }
-    }
-
-    private Reply doProcessSetstat(Request request) throws IOException {
-        // This is required for WinSCP / Cyberduck to upload properly
-        // Blindly reply "OK"
-        // TODO implement it
-        int id = request.getId();
-        return new SshFxpStatusReply(id, SSH_FX_OK, "");
-    }
-
-    private Reply doProcessRename(SshFxpRenameRequest request) throws IOException {
-        int id = request.getId();
-        String oldPath = request.getOldPath();
-        String newPath = request.getNewPath();
-        SshFile o = resolveFile(oldPath);
-        SshFile n = resolveFile(newPath);
-        if (!o.doesExist()) {
-            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, o.getAbsolutePath());
-        } else if (n.doesExist()) {
-            return new SshFxpStatusReply(id, SSH_FX_FILE_ALREADY_EXISTS, n.getAbsolutePath());
-        } else if (!o.move(n)) {
-            return new SshFxpStatusReply(id, SSH_FX_FAILURE, "Failed to rename file");
-        } else {
-            return new SshFxpStatusReply(id, SSH_FX_OK, "");
-        }
-    }
-
-    private Reply doProcessRealpath(SshFxpRealpathRequest request) throws IOException {
-        int id = request.getId();
-        String path = request.getPath();
-        if (path.trim().length() == 0) {
-            path = ".";
-        }
-        SshFile p = resolveFile(path);
-        for (String s : request.getCompose()) {
-            p = this.root.getFile(p, s);
-        }
-        String normalizedPath = SelectorUtils.normalizePath(p.getAbsolutePath(), "/");
-        if (normalizedPath.length() == 0) {
-            normalizedPath = "/";
-        }
-        p = resolveFile(normalizedPath);
-        if (p.getName().length() == 0) {
-            p = resolveFile(".");
-        }
-        boolean exists = (request.getOptions() != SSH_FXP_REALPATH_NO_CHECK) && p.doesExist();
-        if (!exists && request.getOptions() == SSH_FXP_REALPATH_STAT_ALWAYS) {
-            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
-        } else if (exists && (request.getOptions() == SSH_FXP_REALPATH_STAT_IF || request.getOptions() == SSH_FXP_REALPATH_STAT_ALWAYS)) {
-            SshFxpNameReply reply = new SshFxpNameReply(id);
-            int flags = SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE;
-            reply.addFile(p, normalizedPath, getLongName(p), new FileAttributes(p, flags));
-            return reply;
-        } else {
-            SshFxpNameReply reply = new SshFxpNameReply(id);
-            reply.addFile(p, normalizedPath, getLongName(p), new FileAttributes());
-            return reply;
-        }
-    }
-
-    private Reply doProcessRmdir(SshFxpRmdirRequest request) throws IOException {
-        int id = request.getId();
-        String path = request.getPath();
-        // attrs
-        SshFile p = resolveFile(path);
-        if (p.isDirectory()) {
-            if (p.doesExist()) {
-                if (p.listSshFiles().size() == 0) {
-                    if (p.delete()) {
-                        return new SshFxpStatusReply(id, SSH_FX_OK, "");
-                    } else {
-                        return new SshFxpStatusReply(id, SSH_FX_FAILURE, "Unable to delete directory " + path);
-                    }
-                } else {
-                    return new SshFxpStatusReply(id, SSH_FX_DIR_NOT_EMPTY, path);
-                }
-            } else {
-                return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_PATH, path);
-            }
-        } else {
-            return new SshFxpStatusReply(id, SSH_FX_NOT_A_DIRECTORY, p.getAbsolutePath());
-        }
-    }
-
-    private Reply doProcessMkdir(SshFxpMkdirRequest request) throws IOException {
-        int id = request.getId();
-        String path = request.getPath();
-        // attrs
-        SshFile p = resolveFile(path);
-        if (p.doesExist()) {
-            if (p.isDirectory()) {
-                return new SshFxpStatusReply(id, SSH_FX_FILE_ALREADY_EXISTS, p.getAbsolutePath());
-            } else {
-                return new SshFxpStatusReply(id, SSH_FX_NOT_A_DIRECTORY, p.getAbsolutePath());
-            }
-        } else if (!p.isWritable()) {
-            return new SshFxpStatusReply(id, SSH_FX_PERMISSION_DENIED, p.getAbsolutePath());
-        } else if (!p.mkdir()) {
-            return new SshFxpStatusReply(id, SSH_FX_FAILURE, "Error creating dir " + path);
-        } else {
-            return new SshFxpStatusReply(id, SSH_FX_OK, "");
-        }
-    }
-
-    private Reply doProcessRemove(SshFxpRemoveRequest request) throws IOException {
-        int id = request.getId();
-        String path = request.getPath();
-        SshFile p = resolveFile(path);
-        if (!p.doesExist()) {
-            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
-        } else if (p.isDirectory()) {
-            return new SshFxpStatusReply(id, SSH_FX_FILE_IS_A_DIRECTORY, p.getAbsolutePath());
-        } else if (!p.delete()) {
-            return new SshFxpStatusReply(id, SSH_FX_FAILURE, "Failed to delete file");
-        } else {
-            return new SshFxpStatusReply(id, SSH_FX_OK, "");
-        }
-    }
-
-    private Reply doProcessReaddir(SshFxpReaddirRequest request) throws IOException {
-        int id = request.getId();
-        String handle = request.getHandleId();
-        Handle p = getHandle(handle);
-        if (!(p instanceof DirectoryHandle)) {
-            return new SshFxpStatusReply(id, SSH_FX_INVALID_HANDLE, handle);
-        } else if (((DirectoryHandle) p).isDone()) {
-            return new SshFxpStatusReply(id, SSH_FX_EOF, "", "");
-        } else if (!p.getFile().doesExist()) {
-            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, p.getFile().getAbsolutePath());
-        } else if (!p.getFile().isDirectory()) {
-            return new SshFxpStatusReply(id, SSH_FX_NOT_A_DIRECTORY, p.getFile().getAbsolutePath());
-        } else if (!p.getFile().isReadable()) {
-            return new SshFxpStatusReply(id, SSH_FX_PERMISSION_DENIED, p.getFile().getAbsolutePath());
-        } 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.
-                Reply reply = sendName(id, dh);
-                if (!dh.hasNext()) {
-                    // if no more files to send
-                    dh.setDone(true);
-                    dh.clearFileList();
-                }
-                return reply;
-            } else {
-                // empty directory
-                dh.setDone(true);
-                dh.clearFileList();
-                return new SshFxpStatusReply(id, SSH_FX_EOF, "", "");
-            }
-        }
-    }
-
-    private Reply doProcessOpendir(SshFxpOpendirRequest request) throws IOException {
-        int id = request.getId();
-        String path = request.getPath();
-        SshFile p = resolveFile(path);
-        if (!p.doesExist()) {
-            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, path);
-        } else if (!p.isDirectory()) {
-            return new SshFxpStatusReply(id, SSH_FX_NOT_A_DIRECTORY, path);
-        } else if (!p.isReadable()) {
-            return new SshFxpStatusReply(id, SSH_FX_PERMISSION_DENIED, path);
-        } else {
-            Handle handle = createDirectoryHandle(p);
-            return new SshFxpHandleReply(id, handle);
-        }
-    }
-
-    private Reply doProcessFstat(SshFxpFstatRequest request) throws IOException {
-        int id = request.getId();
-        String handle = request.getHandleId();
-        Handle p = getHandle(handle);
-        if (p == null) {
-            return new SshFxpStatusReply(id, SSH_FX_INVALID_HANDLE, handle);
-        } else {
-            int flags = SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE;
-            return new SshFxpAttrsReply(id, new FileAttributes(p.getFile(), flags));
-        }
-    }
-
-    private Reply doProcessStat(Request sftpRequest) throws IOException {
-        int id = sftpRequest.getId();
-        String path;
-        if (sftpRequest instanceof SshFxpLstatRequest) {
-            SshFxpLstatRequest sshFxpLstatRequest = (SshFxpLstatRequest) sftpRequest;
-            path = sshFxpLstatRequest.getPath();
-        } else {
-            SshFxpStatRequest sshFxpStatRequest = (SshFxpStatRequest) sftpRequest;
-            path = sshFxpStatRequest.getPath();
-        }
-        SshFile p = resolveFile(path);
-        if (!p.doesExist()) {
-            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
-        }
-        int flags = SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE;
-        return new SshFxpAttrsReply(id, new FileAttributes(p, flags));
-    }
-
-    private Reply doProcessWrite(SshFxpWriteRequest request) throws IOException {
-        int id = request.getId();
-        String handle = request.getHandleId();
-        long offset = request.getOffset();
-        byte[] data = request.getData();
-        Handle p = getHandle(handle);
-        if (!(p instanceof FileHandle)) {
-            return new SshFxpStatusReply(id, SSH_FX_INVALID_HANDLE, handle);
-        } else {
-            FileHandle fh = (FileHandle) p;
-            fh.write(data, offset);
-            SshFile sshFile = fh.getFile();
-
-            sshFile.setLastModified(new Date().getTime());
-
-            return new SshFxpStatusReply(id, SSH_FX_OK, "");
-        }
-    }
-
-    private Reply doProcessRead(SshFxpReadRequest request) throws IOException {
-        int id = request.getId();
-        String handle = request.getHandleId();
-        long offset = request.getOffset();
-        int len = request.getLength();
-        Handle p = getHandle(handle);
-        if (!(p instanceof FileHandle)) {
-            return new SshFxpStatusReply(id, SSH_FX_INVALID_HANDLE, handle);
-        } else {
-            FileHandle fh = (FileHandle) p;
-            byte[] b = new byte[len];
-            len = fh.read(b, offset);
-            if (len >= 0) {
-                return new SshFxpDataReply(id, b, 0, len, len < b.length);
-            } else {
-                return new SshFxpStatusReply(id, SSH_FX_EOF, "");
-            }
-        }
-    }
-
-    private Reply doProcessClose(SshFxpCloseRequest sftpRequest) throws IOException {
-        int id = sftpRequest.getId();
-        SshFxpCloseRequest sshFxpCloseRequest = sftpRequest;
-        String handle = sshFxpCloseRequest.getHandleId();
-        Handle h = getHandle(handle);
-        if (h == null) {
-            return new SshFxpStatusReply(id, SSH_FX_INVALID_HANDLE, handle, "");
-        } else {
-            handles.remove(handle);
-            h.close();
-            return new SshFxpStatusReply(id, SSH_FX_OK, "", "");
-        }
-    }
-
-    private Reply doProcessOpen(SshFxpOpenRequest request) throws IOException {
-        int id = request.getId();
-        if (session.getFactoryManager().getProperties() != null) {
-            String maxHandlesString = session.getFactoryManager().getProperties().get(MAX_OPEN_HANDLES_PER_SESSION);
-            if (maxHandlesString != null) {
-                int maxHandleCount = Integer.parseInt(maxHandlesString);
-                if (handles.size() > maxHandleCount) {
-                    return new SshFxpStatusReply(id, SSH_FX_FAILURE, "Too many open handles");
-                }
-            }
-        }
-
-        int accValue = request.getAcc();
-        if (accValue == 0) {
-            String path = request.getPath();
-            int flags = request.getFlags();
-            // attrs
-            SshFile file = resolveFile(path);
-            if (file.doesExist()) {
-                if (((flags & SSH_FXF_CREAT) != 0) && ((flags & SSH_FXF_EXCL) != 0)) {
-                    return new SshFxpStatusReply(id, SSH_FX_FILE_ALREADY_EXISTS, path);
-                }
-            } else {
-                if ((flags & SSH_FXF_CREAT) != 0) {
-                    if (!file.isWritable()) {
-                        return new SshFxpStatusReply(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
-                    }
-                    file.create();
-                }
-            }
-            if ((flags & SSH_FXF_TRUNC) != 0) {
-                file.truncate();
-            }
-            return new SshFxpHandleReply(id, createFileHandle(file, flags));
-        } else {
-            String path = request.getPath();
-            int acc = accValue;
-            int flags = request.getFlags();
-            // attrs
-            SshFile file = resolveFile(path);
-            switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
-                case SSH_FXF_CREATE_NEW: {
-                    if (file.doesExist()) {
-                        return new SshFxpStatusReply(id, SSH_FX_FILE_ALREADY_EXISTS, path);
-                    } else if (!file.isWritable()) {
-                        return new SshFxpStatusReply(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
-                    }
-                    file.create();
-                    break;
-                }
-                case SSH_FXF_CREATE_TRUNCATE: {
-                    if (file.doesExist()) {
-                        return new SshFxpStatusReply(id, SSH_FX_FILE_ALREADY_EXISTS, path);
-                    } else if (!file.isWritable()) {
-                        return new SshFxpStatusReply(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
-                    }
-                    file.truncate();
-                    break;
-                }
-                case SSH_FXF_OPEN_EXISTING: {
-                    if (!file.doesExist()) {
-                        if (!file.getParentFile().doesExist()) {
-                            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_PATH, path);
-                        } else {
-                            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, path);
-                        }
-                    }
-                    break;
-                }
-                case SSH_FXF_OPEN_OR_CREATE: {
-                    if (!file.doesExist()) {
-                        file.create();
-                    }
-                    break;
-                }
-                case SSH_FXF_TRUNCATE_EXISTING: {
-                    if (!file.doesExist()) {
-                        if (!file.getParentFile().doesExist()) {
-                            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_PATH, path);
-                        } else {
-                            return new SshFxpStatusReply(id, SSH_FX_NO_SUCH_FILE, path);
-                        }
-                    }
-                    file.truncate();
-                    break;
-                }
-                default:
-                    throw new IllegalArgumentException("Unsupported open mode: " + flags);
-            }
-            return new SshFxpHandleReply(id, createFileHandle(file, flags));
-        }
-    }
-
-    private Reply doProcessInit(SshFxpInitRequest request) throws IOException {
-        int id = request.getId();
-        version = id;
-        if (version >= LOWER_SFTP_IMPL) {
-            version = Math.min(version, HIGHER_SFTP_IMPL);
-            return new SshFxpVersionReply(version);
-        } else {
-            // We only support version >= 3 (Version 1 and 2 are not common)
-            return new SshFxpStatusReply(id, SSH_FX_OP_UNSUPPORTED, "SFTP server only support versions " + ALL_SFTP_IMPL);
-        }
-    }
-
-    protected SshFxpNameReply sendName(int id, Iterator<SshFile> files) throws IOException {
-        SshFxpNameReply reply = new SshFxpNameReply(id);
-        int nb = 0;
-        while (files.hasNext() && nb < MAX_PACKET_LENGTH / 2) {
-            SshFile f = files.next();
-            String filename = f.getName();
-            if (version <= 3) {
-                nb += 55 + filename.length() * 2;
-            } else {
-                nb += filename.length();
-            }
-            nb += 10; // Attrs size
-            int flags = SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_ACCESSTIME;
-            reply.addFile(f, filename, getLongName(f), new FileAttributes(f, flags));
-        }
-        reply.setEol(!files.hasNext());
-        return reply;
-    }
-
-    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();
-    }
-
-    public void destroy() {
-        closed = true;
-    }
-
-    private SshFile resolveFile(String path) {
-        return this.root.getFile(path);
-    }
-
-
-    private String getLongName(SshFile f) {
-        String username = f.getOwner();
-        if (username.length() > 8) {
-            username = username.substring(0, 8);
-        } else {
-            for (int i = username.length(); i < 8; i++) {
-                username = username + " ";
-            }
-        }
-
-        long length = f.getSize();
-        String lengthString = String.format("%1$8s", length);
-
-        StringBuilder sb = new StringBuilder();
-        sb.append((f.isDirectory() ? "d" : "-"));
-        sb.append((f.isReadable() ? "r" : "-"));
-        sb.append((f.isWritable() ? "w" : "-"));
-        sb.append((f.isExecutable() ? "x" : "-"));
-        sb.append((f.isReadable() ? "r" : "-"));
-        sb.append((f.isWritable() ? "w" : "-"));
-        sb.append((f.isExecutable() ? "x" : "-"));
-        sb.append((f.isReadable() ? "r" : "-"));
-        sb.append((f.isWritable() ? "w" : "-"));
-        sb.append((f.isExecutable() ? "x" : "-"));
-        sb.append(" ");
-        sb.append("  1");
-        sb.append(" ");
-        sb.append(username);
-        sb.append(" ");
-        sb.append(username);
-        sb.append(" ");
-        sb.append(lengthString);
-        sb.append(" ");
-        sb.append(getUnixDate(f.getLastModified()));
-        sb.append(" ");
-        sb.append(f.getName());
-
-        return sb.toString();
-    }
-
-    private final static String[] MONTHS = {"Jan", "Feb", "Mar", "Apr", "May",
-            "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
-
-    /**
-     * Get unix style date string.
-     */
-    private final static String getUnixDate(long millis) {
-        if (millis < 0) {
-            return "------------";
-        }
-
-        StringBuffer sb = new StringBuffer(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();
-    }
-
-}


[10/15] mina-sshd git commit: [SSHD-408] Implement sftp v4, v5 and v6

Posted by gn...@apache.org.
[SSHD-408] Implement sftp v4, v5 and v6


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

Branch: refs/heads/master
Commit: 22581fb880efcbb1f7f86af68fd53a812df63c47
Parents: 345604b
Author: Guillaume Nodet <gn...@apache.org>
Authored: Wed Feb 11 00:06:45 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 .../java/org/apache/sshd/client/SftpClient.java |   82 +-
 .../sshd/client/sftp/DefaultSftpClient.java     |  449 +++-
 .../sshd/client/sftp/SftpFileChannel.java       |  345 +++
 .../apache/sshd/client/sftp/SftpFileSystem.java |   27 +-
 .../client/sftp/SftpFileSystemProvider.java     |  344 ++-
 .../org/apache/sshd/common/scp/ScpHelper.java   |   18 +-
 .../org/apache/sshd/common/util/Buffer.java     |   24 +-
 .../org/apache/sshd/common/util/IoUtils.java    |   17 +
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 2105 ++++++++++++------
 .../org/apache/sshd/SftpFileSystemTest.java     |   68 +-
 .../src/test/java/org/apache/sshd/SftpTest.java |   51 +-
 11 files changed, 2644 insertions(+), 886 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
index 18a9ba4..5c5aeb5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
@@ -22,7 +22,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.file.attribute.FileTime;
 import java.util.EnumSet;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author <a href="http://mina.apache.org">Apache MINA Project</a>
@@ -62,11 +64,20 @@ public interface SftpClient extends AutoCloseable {
         Exclusive
     }
 
+    enum CopyMode {
+        Atomic,
+        Overwrite
+    }
+
     enum Attribute {
         Size,
         UidGid,
         Perms,
-        AcModTime
+        AcModTime,
+        OwnerGroup,
+        AccessTime,
+        ModifyTime,
+        CreateTime,
     }
 
     public static class Handle {
@@ -79,16 +90,39 @@ public interface SftpClient extends AutoCloseable {
     public static class Attributes {
         public EnumSet<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;
         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 (group == null) {
+                group = "GROUP@";
+            }
+            return this;
+        }
+        public Attributes group(String group) {
+            flags.add(Attribute.OwnerGroup);
+            this.group = group;
+            if (owner == null) {
+                owner = "OWNER@";
+            }
+            return this;
+        }
         public Attributes owner(int uid, int gid) {
             flags.add(Attribute.UidGid);
             this.uid = uid;
@@ -100,12 +134,48 @@ public interface SftpClient extends AutoCloseable {
             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;
         }
@@ -131,6 +201,8 @@ public interface SftpClient extends AutoCloseable {
         }
     }
 
+    int getVersion();
+
     /**
      * Close the client.
      */
@@ -150,6 +222,8 @@ public interface SftpClient extends AutoCloseable {
 
     void rename(String oldPath, String newPath) throws IOException;
 
+    void rename(String oldPath, String newPath, CopyMode... options) throws IOException;
+
     int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException;
 
     void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException;
@@ -178,6 +252,12 @@ public interface SftpClient extends AutoCloseable {
 
     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
     //

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/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
index 526d92d..8ec2bb9 100644
--- 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
@@ -25,10 +25,13 @@ 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.EnumSet;
 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.ClientSession;
@@ -63,6 +66,9 @@ public class DefaultSftpClient implements SftpClient {
     public static final int SSH_FXP_RENAME =          18;
     public static final int SSH_FXP_READLINK =        19;
     public static final int SSH_FXP_SYMLINK =         20;
+    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;
@@ -71,21 +77,29 @@ public class DefaultSftpClient implements SftpClient {
     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_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_EXTENDED =    0x80000000;
+    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_FILE_ALREADY_EXISTS =         11;
+    public static final int SSH_FX_LOCK_CONFLICT =               17;
+
+    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; // v4
+    public static final int SSH_FILEXFER_ATTR_EXTENDED =        0x80000000;
 
     public static final int SSH_FXF_READ =   0x00000001;
     public static final int SSH_FXF_WRITE =  0x00000002;
@@ -94,17 +108,59 @@ public class DefaultSftpClient implements SftpClient {
     public static final int SSH_FXF_TRUNC =  0x00000010;
     public static final int SSH_FXF_EXCL =   0x00000020;
 
+    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 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 int SFTP_V3 = 3;
+    public static int SFTP_V4 = 4;
+    public static int SFTP_V5 = 5;
+    public static int SFTP_V6 = 6;
+
     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 Buffer();
     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("sftp");
-        this.messages = new HashMap<Integer, Buffer>();
+        this.messages = new HashMap<>();
         try {
             this.channel.setOut(new OutputStream() {
                 @Override
@@ -132,6 +188,11 @@ public class DefaultSftpClient implements SftpClient {
         init();
     }
 
+    @Override
+    public int getVersion() {
+        return version;
+    }
+
     public boolean isClosing() {
         return closing;
     }
@@ -258,9 +319,9 @@ public class DefaultSftpClient implements SftpClient {
         DataOutputStream dos = new DataOutputStream(channel.getInvertedIn());
         dos.writeInt(5);
         dos.writeByte(SSH_FXP_INIT);
-        dos.writeInt(3);
+        dos.writeInt(SFTP_V6);
         dos.flush();
-        Buffer buffer = null;
+        Buffer buffer;
         synchronized (messages) {
             while (messages.isEmpty()) {
                 try {
@@ -276,8 +337,14 @@ public class DefaultSftpClient implements SftpClient {
         int type = buffer.getByte();
         int id = buffer.getInt();
         if (type == SSH_FXP_VERSION) {
-            if (id != 3) {
-                throw new SshException("Unable to use SFTP v3, server replied with version " + id);
+            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();
@@ -353,7 +420,9 @@ public class DefaultSftpClient implements SftpClient {
                 throw new SshException("SFTP error: received " + len + " names instead of 1");
             }
             String name = buffer.getString();
-            String longName = buffer.getString();
+            if (version == SFTP_V3) {
+                String longName = buffer.getString();
+            }
             Attributes attrs = readAttributes(buffer);
             return name;
         } else {
@@ -364,70 +433,232 @@ public class DefaultSftpClient implements SftpClient {
     protected Attributes readAttributes(Buffer buffer) throws IOException {
         Attributes attrs = new Attributes();
         int flags = buffer.getInt();
-        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();
+        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();
+            }
+            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();
         }
         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 {
-        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;
+        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;
+                }
             }
-        }
-        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);
+            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;
+                }
+            }
+            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 IllegalStateException();
         }
     }
 
     public Handle open(String path, EnumSet<OpenMode> options) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putString(path);
-        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;
+        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;
+                }
             }
+            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);
         }
-        buffer.putInt(mode);
-        buffer.putInt(0);
+        writeAttributes(buffer, new Attributes());
         return checkHandle(receive(send(SSH_FXP_OPEN, buffer)));
     }
 
@@ -444,9 +675,29 @@ public class DefaultSftpClient implements SftpClient {
     }
 
     public void rename(String oldPath, String newPath) throws IOException {
+        rename(oldPath, newPath, new CopyMode[0]);
+    }
+
+    public void rename(String oldPath, String newPath, CopyMode... options) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putString(oldPath);
         buffer.putString(newPath);
+        if (version >= SFTP_V5) {
+            int opts = 0;
+            for (CopyMode opt : options) {
+                switch (opt) {
+                case Atomic:
+                    opts |= SSH_FXP_RENAME_ATOMIC;
+                    break;
+                case Overwrite:
+                    opts |= SSH_FXP_RENAME_OVERWRITE;
+                    break;
+                }
+            }
+            buffer.putInt(opts);
+        } else if (options.length > 0) {
+            throw new UnsupportedOperationException("copy options can not be used with this SFTP version");
+        }
         checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
     }
 
@@ -497,8 +748,11 @@ public class DefaultSftpClient implements SftpClient {
 
     public void mkdir(String path) throws IOException {
         Buffer buffer = new Buffer();
-        buffer.putString(path);
+        buffer.putString(path, StandardCharsets.UTF_8);
         buffer.putInt(0);
+        if (version != SFTP_V3) {
+            buffer.putByte((byte) 0);
+        }
         checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
     }
 
@@ -537,7 +791,7 @@ public class DefaultSftpClient implements SftpClient {
             DirEntry[] entries = new DirEntry[len];
             for (int i = 0; i < len; i++) {
                 String name = buffer.getString();
-                String longName = buffer.getString();
+                String longName = (version == SFTP_V3) ? buffer.getString() : null;
                 Attributes attrs = readAttributes(buffer);
                 entries[i] = new DirEntry(name, longName, attrs);
             }
@@ -556,18 +810,27 @@ public class DefaultSftpClient implements SftpClient {
     public Attributes stat(String path) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putString(path);
+        if (version >= SFTP_V4) {
+            buffer.putInt(0);
+        }
         return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
     }
 
     public Attributes lstat(String path) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putString(path);
+        if (version >= SFTP_V4) {
+            buffer.putInt(0);
+        }
         return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
     }
 
     public Attributes stat(Handle handle) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putString(handle.id);
+        if (version >= SFTP_V4) {
+            buffer.putInt(0);
+        }
         return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
     }
 
@@ -592,10 +855,44 @@ public class DefaultSftpClient implements SftpClient {
     }
 
     public void symLink(String linkPath, String targetPath) throws IOException {
+        link(linkPath, targetPath, true);
+    }
+
+    public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+        if (version < SFTP_V6) {
+            if (!symbolic) {
+                throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
+            }
+            Buffer buffer = new Buffer();
+            buffer.putString(targetPath);
+            buffer.putString(linkPath);
+            checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
+        } else {
+            Buffer buffer = new Buffer();
+            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 Buffer();
-        buffer.putString(linkPath);
-        buffer.putString(targetPath);
-        checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
+        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 Buffer();
+        buffer.putString(handle.id);
+        buffer.putLong(offset);
+        buffer.putLong(length);
+        checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
     }
 
     public Iterable<DirEntry> readDir(final String path) throws IOException {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/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
new file mode 100644
index 0000000..52a02d0
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
@@ -0,0 +1,345 @@
+/*
+ * 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.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.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.SftpClient;
+import org.apache.sshd.client.SftpException;
+
+public class SftpFileChannel extends FileChannel {
+
+    final SftpPath p;
+    final EnumSet<SftpClient.OpenMode> modes;
+    final SftpClient sftp;
+    final SftpClient.Handle handle;
+    final Object lock;
+    volatile long pos;
+    volatile Thread blockingThread;
+
+    public SftpFileChannel(SftpPath p, EnumSet<SftpClient.OpenMode> modes) throws IOException {
+        this.p = p;
+        this.modes = modes;
+        sftp = p.getFileSystem().getClient();
+        handle = sftp.open(p.toString(), modes);
+        lock = new Object();
+        pos = 0;
+    }
+
+    @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();
+        }
+        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);
+    }
+
+    protected long doRead(List<ByteBuffer> buffers, long position) throws IOException {
+        ensureOpen();
+        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();
+        }
+        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);
+    }
+
+    protected long doWrite(List<ByteBuffer> buffers, long position) throws IOException {
+        ensureOpen();
+        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();
+        return pos;
+    }
+
+    @Override
+    public FileChannel position(long newPosition) throws IOException {
+        if (newPosition < 0) {
+            throw new IllegalArgumentException();
+        }
+        ensureOpen();
+        synchronized (lock) {
+            pos = newPosition;
+            return this;
+        }
+    }
+
+    @Override
+    public long size() throws IOException {
+        return sftp.stat(handle).size;
+    }
+
+    @Override
+    public FileChannel truncate(long size) throws IOException {
+        sftp.setStat(handle, new SftpClient.Attributes().size(size));
+        return this;
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+    }
+
+    @Override
+    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
+        if (position < 0 || count < 0) {
+            throw new IllegalArgumentException();
+        }
+        ensureOpen();
+        synchronized (lock) {
+            boolean completed = false;
+            boolean eof = false;
+            long curPos = position;
+            try {
+                long totalRead = 0;
+                beginBlocking();
+
+                byte[] buffer = new byte[32768];
+                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();
+        }
+        ensureOpen();
+        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();
+    }
+
+    @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 {
+        try {
+            sftp.lock(handle, position, size, 0);
+        } catch (SftpException e) {
+            if (e.getStatus() == DefaultSftpClient.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();
+            }
+
+            @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 {
+                sftp.close(handle);
+            } finally {
+                sftp.close();
+            }
+        }
+    }
+
+    private void beginBlocking() {
+        begin();
+        blockingThread = Thread.currentThread();
+    }
+
+    private void endBlocking(boolean completed) throws AsynchronousCloseException {
+        blockingThread = null;
+        end(completed);
+    }
+
+    private void ensureOpen() throws IOException {
+        if (!isOpen()) {
+            throw new ClosedChannelException();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/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
index d6de9df..98d6679 100644
--- 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
@@ -118,6 +118,11 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         }
 
         @Override
+        public int getVersion() {
+            return delegate.getVersion();
+        }
+
+        @Override
         public boolean isClosing() {
             return false;
         }
@@ -157,6 +162,11 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         }
 
         @Override
+        public void rename(String oldPath, String newPath, 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);
         }
@@ -251,6 +261,20 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
             return delegate.write(path, 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 {
@@ -264,7 +288,6 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
             return new DefaultGroupPrincipal(group);
         }
-
     }
 
     protected static class DefaultUserPrincipal implements UserPrincipal {
@@ -278,7 +301,6 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
             this.name = name;
         }
 
-        @Override
         public String getName() {
             return name;
         }
@@ -300,7 +322,6 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         public String toString() {
             return name;
         }
-
     }
 
     protected static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/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
index ca60647..00d3d8a 100644
--- 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
@@ -19,10 +19,11 @@
 package org.apache.sshd.client.sftp;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InterruptedIOException;
+import java.io.OutputStream;
 import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.file.AccessDeniedException;
 import java.nio.file.AccessMode;
@@ -33,11 +34,13 @@ 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.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;
@@ -52,6 +55,7 @@ import java.nio.file.attribute.UserPrincipal;
 import java.nio.file.spi.FileSystemProvider;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -62,11 +66,12 @@ import org.apache.sshd.SshBuilder;
 import org.apache.sshd.SshClient;
 import org.apache.sshd.client.SftpClient;
 import org.apache.sshd.client.SftpException;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.IoUtils;
 
-public class SftpFileSystemProvider extends FileSystemProvider {
+import static org.apache.sshd.client.SftpClient.*;
 
-    public static final int SSH_FX_NO_SUCH_FILE =         2;
-    public static final int SSH_FX_FILE_ALREADY_EXISTS = 11;
+public class SftpFileSystemProvider extends FileSystemProvider {
 
     final SshClient client;
     final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
@@ -140,6 +145,11 @@ public class SftpFileSystemProvider extends FileSystemProvider {
 
     @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 {
         final SftpPath p = toSftpPath(path);
         final EnumSet<SftpClient.OpenMode> modes = EnumSet.noneOf(SftpClient.OpenMode.class);
         for (OpenOption option : options) {
@@ -162,91 +172,10 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         }
         if (modes.isEmpty()) {
             modes.add(SftpClient.OpenMode.Read);
+            modes.add(SftpClient.OpenMode.Write);
         }
-        return new SeekableByteChannel() {
-            final SftpClient sftp = p.getFileSystem().getClient();
-            final SftpClient.Handle handle = sftp.open(p.toString(), modes);
-            long pos = 0;
-            @Override
-            public int read(ByteBuffer dst) throws IOException {
-                if (dst.hasArray()) {
-                    int read = sftp.read(handle, pos, dst.array(), dst.arrayOffset() + dst.position(), dst.remaining());
-                    if (read > 0) {
-                        dst.position(dst.position() + read);
-                        pos += read;
-                    }
-                    return read;
-                } else {
-                    int remaining = Math.min(8192, dst.remaining());
-                    byte[] buf = new byte[remaining];
-                    int read = sftp.read(handle, pos, buf, 0, remaining);
-                    if (read > 0) {
-                        dst.put(buf, 0, read);
-                        pos += read;
-                    }
-                    return read;
-                }
-            }
-
-            @Override
-            public int write(ByteBuffer src) throws IOException {
-                if (src.hasArray()) {
-                    int rem = src.remaining();
-                    sftp.write(handle, pos, src.array(), src.arrayOffset() + src.position(), rem);
-                    src.position(src.position() + rem);
-                    pos += rem;
-                    return rem;
-                } else {
-                    byte[] buf = new byte[Math.min(8192, src.remaining())];
-                    src.get(buf);
-                    sftp.write(handle, pos, buf, 0, buf.length);
-                    pos += buf.length;
-                    return buf.length;
-                }
-            }
-
-            @Override
-            public long position() throws IOException {
-                if (pos < 0) {
-                    throw new ClosedChannelException();
-                }
-                return pos;
-            }
-
-            @Override
-            public SeekableByteChannel position(long newPosition) throws IOException {
-                if (newPosition < 0) {
-                    throw new IllegalArgumentException();
-                }
-                pos = newPosition;
-                return this;
-            }
-
-            @Override
-            public long size() throws IOException {
-                return sftp.stat(handle).size;
-            }
-
-            @Override
-            public SeekableByteChannel truncate(long size) throws IOException {
-                sftp.setStat(handle, new SftpClient.Attributes().size(size));
-                return this;
-            }
-
-            @Override
-            public boolean isOpen() {
-                return pos >= 0;
-            }
-
-            @Override
-            public void close() throws IOException {
-                if (pos >= 0) {
-                    sftp.close(handle);
-                    sftp.close();
-                    pos = -1;
-                }
-            }
-        };
+        // TODO: attrs
+        return new SftpFileChannel(p, modes);
     }
 
     @Override
@@ -288,15 +217,27 @@ public class SftpFileSystemProvider extends FileSystemProvider {
     public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
         SftpPath p = toSftpPath(dir);
         try (SftpClient sftp = p.getFileSystem().getClient()) {
-            // TODO: handle attributes
             try {
                 sftp.mkdir(dir.toString());
             } catch (SftpException e) {
-                if (e.getStatus() == SSH_FX_FILE_ALREADY_EXISTS) {
+                if (sftp.getVersion() == 3 && e.getStatus() == DefaultSftpClient.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 (e.getStatus() == DefaultSftpClient.SSH_FX_FILE_ALREADY_EXISTS) {
                     throw new FileAlreadyExistsException(p.toString());
                 }
                 throw e;
             }
+            for (FileAttribute<?> attr : attrs) {
+                setAttribute(p, attr.name(), attr.value());
+            }
         }
     }
 
@@ -316,19 +257,129 @@ public class SftpFileSystemProvider extends FileSystemProvider {
 
     @Override
     public void copy(Path source, Path target, CopyOption... options) throws IOException {
-        // TODO
+        SftpPath src = toSftpPath(source);
+        SftpPath dst = toSftpPath(target);
+        if (src.getFileSystem() != dst.getFileSystem()) {
+            throw new ProviderMismatchException();
+        }
+        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 = noFollowLinks ? new LinkOption[] { LinkOption.NOFOLLOW_LINKS }
+                                                 : new LinkOption[0];
+
+        // 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
+        if (replaceExisting) {
+            deleteIfExists(target);
+        } else if (Files.exists(target))
+            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 {
-        // TODO
+        SftpPath src = toSftpPath(source);
+        SftpPath dst = toSftpPath(target);
+        if (src.getFileSystem() != dst.getFileSystem()) {
+            throw new ProviderMismatchException();
+        }
+        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 = noFollowLinks ? new LinkOption[] { LinkOption.NOFOLLOW_LINKS }
+                : new LinkOption[0];
+
+        // 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
+        if (replaceExisting) {
+            deleteIfExists(target);
+        } else if (Files.exists(target))
+            throw new FileAlreadyExistsException(target.toString());
+
+        try (SftpClient sftp = src.getFileSystem().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();
+        }
         checkAccess(p1);
         checkAccess(p2);
         return p1.equals(p2);
@@ -414,7 +465,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
                                 attributes = client.lstat(p.toString());
                             }
                         } catch (SftpException e) {
-                            if (e.getStatus() == SSH_FX_NO_SUCH_FILE) {
+                            if (e.getStatus() == DefaultSftpClient.SSH_FX_NO_SUCH_FILE) {
                                 throw new NoSuchFileException(p.toString());
                             }
                             throw e;
@@ -423,20 +474,17 @@ public class SftpFileSystemProvider extends FileSystemProvider {
                     return new PosixFileAttributes() {
                         @Override
                         public UserPrincipal owner() {
-                            // TODO
-                            return null;
+                            return attributes.owner != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.owner) : null;
                         }
 
                         @Override
                         public GroupPrincipal group() {
-                            // TODO
-                            return null;
+                            return attributes.group != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.group) : null;
                         }
 
                         @Override
                         public Set<PosixFilePermission> permissions() {
-                            // TODO
-                            return null;
+                            return permissionsToAttributes(attributes.perms);
                         }
 
                         @Override
@@ -451,8 +499,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
 
                         @Override
                         public FileTime creationTime() {
-                            // TODO
-                            return null;
+                            return FileTime.from(attributes.ctime, TimeUnit.SECONDS);
                         }
 
                         @Override
@@ -617,20 +664,26 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         SftpClient.Attributes attributes = new SftpClient.Attributes();
         switch (attr) {
         case "lastModifiedTime":
-            attributes.mtime = (int) ((FileTime) value).to(TimeUnit.SECONDS);
+            attributes.mtime((int) ((FileTime) value).to(TimeUnit.SECONDS));
             break;
         case "lastAccessTime":
-            attributes.atime = (int) ((FileTime) value).to(TimeUnit.SECONDS);
+            attributes.atime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+            break;
+        case "creationTime":
+            attributes.ctime((int) ((FileTime) value).to(TimeUnit.SECONDS));
             break;
         case "size":
-            attributes.size = (long) value;
+            attributes.size((long) value);
             break;
-        case "owner":
         case "permissions":
+            attributes.perms(attributesToPermissions((Set<PosixFilePermission>) value));
+            break;
+        case "owner":
+            attributes.owner(((UserPrincipal) value).getName());
+            break;
         case "group":
-            // TODO: handle those
-            throw new IllegalArgumentException(attr);
-        case "creationTime":
+            attributes.group(((GroupPrincipal) value).getName());
+            break;
         case "isRegularFile":
         case "isDirectory":
         case "isSymbolicLink":
@@ -653,8 +706,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         return (SftpPath) path;
     }
 
-    static boolean followLinks(LinkOption... paramVarArgs)
-    {
+    static boolean followLinks(LinkOption... paramVarArgs) {
         boolean bool = true;
         for (LinkOption localLinkOption : paramVarArgs) {
             if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
@@ -664,4 +716,74 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         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(Set<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;
+                }
+            }
+        }
+        return pf;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 9827507..235e84b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.DirectoryScanner;
+import org.apache.sshd.common.util.IoUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -295,7 +296,7 @@ public class ScpHelper {
         };
         try (OutputStream os = Files.newOutputStream(file)) {
             ack();
-            copy(is, os, bufSize);
+            IoUtils.copy(is, os, bufSize);
         }
 
         if (preserve) {
@@ -439,7 +440,7 @@ public class ScpHelper {
 
         // TODO: use bufSize
         try (InputStream in = Files.newInputStream(path)) {
-            copy(in, out, bufSize);
+            IoUtils.copy(in, out, bufSize);
         }
         ack();
         readAck(false);
@@ -610,17 +611,4 @@ public class ScpHelper {
         return c;
     }
 
-    private static long copy(InputStream source, OutputStream sink, int bufferSize)
-            throws IOException
-    {
-        long nread = 0L;
-        byte[] buf = new byte[bufferSize];
-        int n;
-        while ((n = source.read(buf)) > 0) {
-            sink.write(buf, 0, n);
-            nread += n;
-        }
-        return nread;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java b/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java
index a214100..9697bb3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java
@@ -19,6 +19,8 @@
 package org.apache.sshd.common.util;
 
 import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
@@ -194,12 +196,16 @@ public final class Buffer implements Readable {
     }
 
     public String getString() {
+        return getString(StandardCharsets.UTF_8);
+    }
+
+    public String getString(Charset charset) {
         int len = getInt();
         if (len < 0) {
             throw new IllegalStateException("Bad item length: " + len);
         }
         ensureAvailable(len);
-        String s = new String(data, rpos, len);
+        String s = new String(data, rpos, len, charset);
         rpos += len;
         return s;
     }
@@ -389,6 +395,16 @@ public final class Buffer implements Readable {
     }
 
     /**
+     * Writes 16 bits
+     * @param i
+     */
+    public void putShort(int i) {
+        ensureCapacity(2);
+        data[wpos++] = (byte) (i >>  8);
+        data[wpos++] = (byte) (i      );
+    }
+
+    /**
      * Writes 32 bits
      * @param i
      */
@@ -432,7 +448,11 @@ public final class Buffer implements Readable {
     }
 
     public void putString(String string) {
-        putString(string.getBytes());
+        putString(string, Charset.defaultCharset());
+    }
+
+    public void putString(String string, Charset charset) {
+        putString(string.getBytes(charset));
     }
 
     public void putString(byte[] str) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
index da53e91..5a74cc3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
@@ -20,6 +20,8 @@ package org.apache.sshd.common.util;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 /**
  * TODO Add javadoc
@@ -28,6 +30,21 @@ import java.io.IOException;
  */
 public class IoUtils {
 
+    public static long copy(InputStream source, OutputStream sink) throws IOException {
+        return copy(source, sink, 8192);
+    }
+
+    public static long copy(InputStream source, OutputStream sink, int bufferSize) throws IOException {
+        long nread = 0L;
+        byte[] buf = new byte[bufferSize];
+        int n;
+        while ((n = source.read(buf)) > 0) {
+            sink.write(buf, 0, n);
+            nread += n;
+        }
+        return nread;
+    }
+
     public static void closeQuietly(Closeable... closeables) {
         for (Closeable c : closeables) {
             try {


[11/15] mina-sshd git commit: [SSHD-378] Switch to nio FileSystem api for commands (scp and sftp subsystem)

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/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
index 643a1b7..5313f6e 100644
--- 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
@@ -25,25 +25,39 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
 import java.util.Calendar;
 import java.util.Collection;
-import java.util.Date;
 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.Set;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.file.FileSystemAware;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
 import org.apache.sshd.common.util.Buffer;
-import org.apache.sshd.common.util.IoUtils;
 import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.ThreadUtils;
 import org.apache.sshd.server.Command;
@@ -117,6 +131,46 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
     }
 
+    //
+    // File attributes
+    //
+    enum Attribute {
+        Size,               // long
+        Uid,                // int
+        Owner,              // String
+        Gid,                // int
+        Group,              // String
+        IsDirectory,        // boolean
+        IsRegularFile,      // boolean
+        IsSymbolicLink,     // boolean
+        Permissions,        // EnumSet<Permission>
+        CreationTime,       // long
+        LastModifiedTime,   // long
+        LastAccessTime,     // long
+        NLink               // int
+    }
+
+    //
+    // File permissions
+    //
+    enum Permission {
+        UserRead,
+        UserWrite,
+        UserExecute,
+        GroupRead,
+        GroupWrite,
+        GroupExecute,
+        OthersRead,
+        OthersWrite,
+        OthersExecute
+    }
+
+    public enum UnsupportedAttributePolicy {
+        Ignore,
+        Warn,
+        ThrowException
+    }
+
     /**
      * Properties key for the maximum of available open handles per session.
      */
@@ -212,35 +266,41 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
 	private ExecutorService executors;
 	private boolean shutdownExecutor;
 	private Future<?> pendingFuture;
-    private FileSystemView root;
+
+    private FileSystem fileSystem = FileSystems.getDefault();
+    private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
+
     private int version;
-    private Map<String, Handle> handles = new HashMap<String, Handle>();
+    private Map<String, Handle> handles = new HashMap<>();
+
+    private UnsupportedAttributePolicy unsupportedAttributePolicy = UnsupportedAttributePolicy.Warn;
 
-    protected static abstract class Handle {
-        SshFile file;
+    protected static abstract class Handle implements java.io.Closeable {
+        Path file;
 
-        public Handle(SshFile file) {
+        public Handle(Path file) {
             this.file = file;
         }
 
-        public SshFile getFile() {
+        public Path getFile() {
             return file;
         }
 
         public void close() throws IOException {
-            file.handleClose();
         }
     }
 
-    protected static class DirectoryHandle extends Handle implements Iterator<SshFile> {
+    protected static class DirectoryHandle extends Handle implements Iterator<Path> {
         boolean done;
         // the directory should be read once at "open directory"
-        List<SshFile> fileList = null;
+        DirectoryStream<Path> ds;
+        Iterator<Path> fileList = null;
         int fileIndex;
 
-        public DirectoryHandle(SshFile file) {
+        public DirectoryHandle(Path file) throws IOException {
             super(file);
-            fileList = file.listSshFiles();
+            ds = Files.newDirectoryStream(file);
+            fileList = ds.iterator();
             fileIndex = 0;
         }
 
@@ -253,13 +313,11 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
 
         public boolean hasNext() {
-            return fileIndex < fileList.size();
+            return fileList.hasNext();
         }
 
-        public SshFile next() {
-            SshFile f = fileList.get(fileIndex);
-            fileIndex++;
-            return f;
+        public Path next() {
+            return fileList.next();
         }
 
         public void remove() {
@@ -270,70 +328,47 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             // allow the garbage collector to do the job
             fileList = null;
         }
+
+        @Override
+        public void close() throws IOException {
+            ds.close();
+        }
     }
 
     protected static class FileHandle extends Handle {
-        int flags;
-        OutputStream output;
-        long outputPos;
-        InputStream input;
-        long inputPos;
-        long length;
+        SeekableByteChannel channel;
 
-        public FileHandle(SshFile sshFile, int flags) {
-            super(sshFile);
-            this.flags = flags;
-        }
-
-        public int read(byte[] data, long offset) throws IOException {
-            if ((flags & SSH_FXF_READ) == 0) {
-                throw new IOException("File has not been opened for reading");
-            }
-            if (input != null && offset >= length) {
-                return -1;
+        public FileHandle(Path file, int flags) throws IOException {
+            super(file);
+            Set<OpenOption> options = new HashSet<>();
+            if ((flags & SSH_FXF_READ) != 0) {
+                options.add(StandardOpenOption.READ);
             }
-            if (input != null && offset != inputPos) {
-                IoUtils.closeQuietly(input);
-                input = null;
+            if ((flags & SSH_FXF_WRITE) != 0) {
+                options.add(StandardOpenOption.WRITE);
             }
-            if (input == null) {
-                input = file.createInputStream(offset);
-                length = file.getSize();
-                inputPos = offset;
+            if ((flags & SSH_FXF_APPEND) != 0) {
+                options.add(StandardOpenOption.APPEND);
             }
-            if (offset >= length) {
-                return -1;
+            if ((flags & SSH_FXF_TRUNC) != 0) {
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
             }
-            int read = input.read(data);
-            inputPos += read;
-            return read;
+            channel = Files.newByteChannel(file, options);
+        }
+
+        public int read(byte[] data, long offset) throws IOException {
+            channel.position(offset);
+            return channel.read(ByteBuffer.wrap(data));
         }
 
         public void write(byte[] data, long offset) throws IOException {
-            if ((flags & SSH_FXF_WRITE) == 0) {
-                throw new IOException("File has not been opened for writing");
-            }
-            if ((flags & SSH_FXF_APPEND) != 0) {
-                offset = (output != null) ? outputPos : file.getSize();
-            }
-            if (output != null && offset != outputPos) {
-                IoUtils.closeQuietly(output);
-                output = null;
-            }
-            if (output == null) {
-                outputPos = offset;
-                output = file.createOutputStream(offset);
-            }
-            output.write(data);
-            outputPos += data.length;
+            channel.position(offset);
+            channel.write(ByteBuffer.wrap(data));
         }
 
         @Override
         public void close() throws IOException {
-            IoUtils.closeQuietly(output, input);
-            output = null;
-            input = null;
-            super.close();
+            channel.close();
         }
     }
 
@@ -375,8 +410,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         this.session = session;
     }
 
-    public void setFileSystemView(FileSystemView view) {
-        this.root = view;
+    public void setFileSystem(FileSystem fileSystem) {
+        this.fileSystem = fileSystem;
     }
 
     public void setExitCallback(ExitCallback callback) {
@@ -450,8 +485,6 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                     }
                 }
             }
-            dis = null;
-
             callback.onExit(0);
         }
     }
@@ -495,16 +528,16 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
 
                 String path = buffer.getString();
                 int pflags = buffer.getInt();
-                Map<SshFile.Attribute, Object> attrs = readAttrs(buffer);
+                Map<Attribute, Object> attrs = readAttrs(buffer);
                 log.debug("Received SSH_FXP_OPEN (path={}, pflags={}, attrs={})", new Object[] { path, pflags, attrs });
                 try {
-                    SshFile file = resolveFile(path);
-                    if (file.doesExist()) {
-                        if ((pflags & SSH_FXF_READ) != 0 && !file.isReadable()) {
+                    Path file = resolveFile(path);
+                    if (Files.exists(file)) {
+                        if ((pflags & SSH_FXF_READ) != 0 && !Files.isReadable(file)) {
                             sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not read " + path);
                             return;
                         }
-                        if ((pflags & SSH_FXF_WRITE) != 0 && !file.isWritable()) {
+                        if ((pflags & SSH_FXF_WRITE) != 0 && !Files.isWritable(file)) {
                             sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not write " + path);
                             return;
                         }
@@ -514,28 +547,14 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                         }
                     } else {
                         if (((pflags & SSH_FXF_CREAT) != 0)) {
-                            if (!file.isWritable()) {
-                                sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
-                                return;
-                            }
-                            if (!file.create()) {
-                                sendStatus(id, SSH_FX_NO_SUCH_FILE, "No such file " + path);
-                                return;
-                            }
+                            Files.createFile(file);
                         } else {
                             sendStatus(id, SSH_FX_NO_SUCH_FILE, "No such file " + path);
                             return;
                         }
                     }
-                    if ((pflags & SSH_FXF_TRUNC) != 0) {
-                        if (!file.isWritable()) {
-                            sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not truncate " + path);
-                            return;
-                        }
-                        file.truncate();
-                    }
-                    if ((pflags & SSH_FXF_CREAT) != 0) {
-                        file.setAttributes(attrs);
+                    if (((pflags & SSH_FXF_CREAT) != 0)) {
+                        setAttributes(file, attrs);
                     }
                     String handle = UUID.randomUUID().toString();
                     handles.put(handle, new FileHandle(file, pflags));
@@ -602,10 +621,6 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                     } else {
                         FileHandle fh = (FileHandle) p;
                         fh.write(data, offset);
-                        SshFile sshFile = fh.getFile();
-
-                        sshFile.setLastModified(new Date().getTime());
-                        
                         sendStatus(id, SSH_FX_OK, "");
                     }
                 } catch (IOException e) {
@@ -617,7 +632,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String path = buffer.getString();
                 log.debug("Received SSH_FXP_LSTAT (path={})", path);
                 try {
-                    SshFile p = resolveFile(path);
+                    Path p = resolveFile(path);
                     sendAttrs(id, p, false);
                 } catch (FileNotFoundException e) {
                     sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
@@ -645,11 +660,11 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             }
             case SSH_FXP_SETSTAT: {
                 String path = buffer.getString();
-                Map<SshFile.Attribute, Object> attrs = readAttrs(buffer);
+                Map<Attribute, Object> attrs = readAttrs(buffer);
                 log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
                 try {
-                    SshFile p = resolveFile(path);
-                    p.setAttributes(attrs);
+                    Path p = resolveFile(path);
+                    setAttributes(p, attrs);
                     sendStatus(id, SSH_FX_OK, "");
                 } catch (FileNotFoundException e) {
                     sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
@@ -662,21 +677,19 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             }
             case SSH_FXP_FSETSTAT: {
                 String handle = buffer.getString();
-                Map<SshFile.Attribute, Object> attrs = readAttrs(buffer);
+                Map<Attribute, 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_FAILURE, handle);
                     } else {
-                        p.getFile().setAttributes(attrs);
+                        setAttributes(p.getFile(), attrs);
                         sendStatus(id, SSH_FX_OK, "");
                     }
                 } catch (FileNotFoundException e) {
                     sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
-                } catch (IOException e) {
-                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
-                } catch (UnsupportedOperationException e) {
+                } catch (IOException | UnsupportedOperationException e) {
                     sendStatus(id, SSH_FX_FAILURE, e.getMessage());
                 }
                 break;
@@ -685,12 +698,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String path = buffer.getString();
                 log.debug("Received SSH_FXP_OPENDIR (path={})", path);
                 try {
-                    SshFile p = resolveFile(path);
-                    if (!p.doesExist()) {
+                    Path p = resolveFile(path);
+                    if (!Files.exists(p)) {
                         sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
-                    } else if (!p.isDirectory()) {
+                    } else if (!Files.isDirectory(p)) {
                         sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
-                    } else if (!p.isReadable()) {
+                    } else if (!Files.isReadable(p)) {
                         sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
                     } else {
                         String handle = UUID.randomUUID().toString();
@@ -711,12 +724,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                         sendStatus(id, SSH_FX_FAILURE, handle);
                     } else if (((DirectoryHandle) p).isDone()) {
                         sendStatus(id, SSH_FX_EOF, "", "");
-                    } else if (!p.getFile().doesExist()) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().getAbsolutePath());
-                    } else if (!p.getFile().isDirectory()) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().getAbsolutePath());
-                    } else if (!p.getFile().isReadable()) {
-                        sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().getAbsolutePath());
+                    } else if (!Files.exists(p.getFile())) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().toString());
+                    } else if (!Files.isDirectory(p.getFile())) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().toString());
+                    } else if (!Files.isReadable(p.getFile())) {
+                        sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().toString());
                     } else {
                         DirectoryHandle dh = (DirectoryHandle) p;
                         if (dh.hasNext()) {
@@ -745,14 +758,13 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String path = buffer.getString();
                 log.debug("Received SSH_FXP_REMOVE (path={})", path);
                 try {
-                    SshFile p = resolveFile(path);
-                    if (!p.doesExist()) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
-                    } else if (p.isDirectory()) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
-                    } else if (!p.delete()) {
-                        sendStatus(id, SSH_FX_FAILURE, "Failed to delete file");
+                    Path p = resolveFile(path);
+                    if (!Files.exists(p)) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+                    } else if (Files.isDirectory(p)) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
                     } else {
+                        Files.delete(p);
                         sendStatus(id, SSH_FX_OK, "");
                     }
                 } catch (IOException e) {
@@ -762,26 +774,25 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             }
             case SSH_FXP_MKDIR: {
                 String path = buffer.getString();
-                Map<SshFile.Attribute, Object> attrs = readAttrs(buffer);
+                Map<Attribute, Object> attrs = readAttrs(buffer);
 
                 log.debug("Received SSH_FXP_MKDIR (path={})", path);
                 // attrs
                 try {
-                    SshFile p = resolveFile(path);
-                    if (p.doesExist()) {
-                        if (p.isDirectory()) {
-                            sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.getAbsolutePath());
+                    Path p = resolveFile(path);
+                    if (Files.exists(p)) {
+                        if (Files.isDirectory(p)) {
+                            sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
                         } else {
-                            sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
+                            sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
                         }
-                    } else if (!p.isWritable()) {
-                        sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getAbsolutePath());
-                    } else if (!p.mkdir()) {
-                        throw new IOException("Error creating dir " + path);
                     } else {
-                        p.setAttributes(attrs);
+                        Files.createDirectory(p);
+                        setAttributes(p, attrs);
                         sendStatus(id, SSH_FX_OK, "");
                     }
+                } catch (AccessDeniedException e) {
+                    sendStatus(id, SSH_FX_PERMISSION_DENIED, e.getMessage());
                 } catch (IOException e) {
                     sendStatus(id, SSH_FX_FAILURE, e.getMessage());
                 }
@@ -792,23 +803,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 log.debug("Received SSH_FXP_RMDIR (path={})", path);
                 // attrs
                 try {
-                    SshFile p = resolveFile(path);
-                    if (p.isDirectory()) {
-                        if (p.doesExist()) {
-                            if (p.listSshFiles().size() == 0) {
-                                if (p.delete()) {
-                                    sendStatus(id, SSH_FX_OK, "");
-                                } else {
-                                    sendStatus(id, SSH_FX_FAILURE, "Unable to delete directory " + path);
-                                }
-                            } else {
-                                sendStatus(id, SSH_FX_FAILURE, path);
-                            }
-                        } else {
-                            sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
-                        }
+                    Path p = resolveFile(path);
+                    if (Files.isDirectory(p)) {
+                        Files.delete(p);
+                        sendStatus(id, SSH_FX_OK, "");
                     } else {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
                     }
                 } catch (IOException e) {
                     sendStatus(id, SSH_FX_FAILURE, e.getMessage());
@@ -822,7 +822,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                     path = ".";
                 }
                 try {
-                    SshFile p = resolveFile(path);
+                    Path p = resolveFile(path).toAbsolutePath().normalize();
                     sendPath(id, p, false);
                 } catch (FileNotFoundException e) {
                     e.printStackTrace();
@@ -837,7 +837,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String path = buffer.getString();
                 log.debug("Received SSH_FXP_STAT (path={})", path);
                 try {
-                    SshFile p = resolveFile(path);
+                    Path p = resolveFile(path);
                     sendAttrs(id, p, true);
                 } catch (FileNotFoundException e) {
                     sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
@@ -851,15 +851,14 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String newPath = buffer.getString();
                 log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={})", oldPath, newPath);
                 try {
-                    SshFile o = resolveFile(oldPath);
-                    SshFile n = resolveFile(newPath);
-                    if (!o.doesExist()) {
-                        sendStatus(id, SSH_FX_NO_SUCH_FILE, o.getAbsolutePath());
-                    } else if (n.doesExist()) {
-                        sendStatus(id, SSH_FX_FAILURE, n.getAbsolutePath());
-                    } else if (!o.move(n)) {
-                        sendStatus(id, SSH_FX_FAILURE, "Failed to rename file");
+                    Path o = resolveFile(oldPath);
+                    Path n = resolveFile(newPath);
+                    if (!Files.exists(o)) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, o.toString());
+                    } else if (Files.exists(n)) {
+                        sendStatus(id, SSH_FX_FAILURE, n.toString());
                     } else {
+                        Files.move(o, n);
                         sendStatus(id, SSH_FX_OK, "");
                     }
                 } catch (IOException e) {
@@ -871,8 +870,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String path = buffer.getString();
                 log.debug("Received SSH_FXP_READLINK (path={})", path);
                 try {
-                    SshFile f = resolveFile(path);
-                    String l = f.readSymbolicLink();
+                    Path f = resolveFile(path);
+                    String l = Files.readSymbolicLink(f).toString();
                     sendLink(id, l);
                 } catch (UnsupportedOperationException e) {
                     sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
@@ -886,10 +885,9 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 String targetpath = buffer.getString();
                 log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
                 try {
-                    SshFile link = resolveFile(linkpath);
-                    // TODO: resolving the file is wrong, we should keep it relative
-                    SshFile target = resolveFile(targetpath);
-                    link.createSymbolicLink(target);
+                    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 " + type + " is unsupported or not implemented");
@@ -914,7 +912,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         send(buffer);
     }
 
-    protected void sendAttrs(int id, SshFile file, boolean followLinks) throws IOException {
+    protected void sendAttrs(int id, Path file, boolean followLinks) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putByte((byte) SSH_FXP_ATTRS);
         buffer.putInt(id);
@@ -922,23 +920,23 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         send(buffer);
     }
 
-    protected void sendPath(int id, SshFile f) throws IOException {
+    protected void sendPath(int id, Path f) throws IOException {
         sendPath(id, f, true);
     }
 
-    protected void sendPath(int id, SshFile f, boolean sendAttrs) throws IOException {
+    protected void sendPath(int id, Path f, boolean sendAttrs) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putByte((byte) SSH_FXP_NAME);
         buffer.putInt(id);
         buffer.putInt(1);
         //normalize the given path, use *nix style separator
-        String normalizedPath = SelectorUtils.normalizePath(f.getAbsolutePath(), "/");
+        String normalizedPath = SelectorUtils.normalizePath(f.toString(), "/");
         if (normalizedPath.length() == 0) {
             normalizedPath = "/";
         }
         buffer.putString(normalizedPath);
         f = resolveFile(normalizedPath);
-        if (f.getName().length() == 0) {
+        if (f.getFileName() == null) {
             f = resolveFile(".");
         }
         buffer.putString(getLongName(f, sendAttrs)); // Format specified in the specs
@@ -958,7 +956,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         send(buffer);
     }
 
-    protected void sendName(int id, Iterator<SshFile> files) throws IOException {
+    protected void sendName(int id, Iterator<Path> files) throws IOException {
         Buffer buffer = new Buffer();
         buffer.putByte((byte) SSH_FXP_NAME);
         buffer.putInt(id);
@@ -966,8 +964,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         buffer.putInt(0);
         int nb = 0;
         while (files.hasNext() && buffer.wpos() < MAX_PACKET_LENGTH) {
-            SshFile f = files.next();
-            buffer.putString(f.getName());
+            Path f = files.next();
+            buffer.putString(f.getFileName().toString());
             buffer.putString(getLongName(f)); // Format specified in the specs
             writeAttrs(buffer, f, false);
             nb++;
@@ -979,26 +977,26 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         send(buffer);
     }
 
-    private String getLongName(SshFile f) throws IOException {
+    private String getLongName(Path f) throws IOException {
         return getLongName(f, true);
     }
 
-    private String getLongName(SshFile f, boolean sendAttrs) throws IOException {
-        Map<SshFile.Attribute, Object> attributes;
+    private String getLongName(Path f, boolean sendAttrs) throws IOException {
+        Map<Attribute, Object> attributes;
         if (sendAttrs) {
-            attributes = f.getAttributes(false);
+            attributes = getAttributes(f, false);
         } else {
-            attributes = new HashMap<SshFile.Attribute, Object>();
-            attributes.put(SshFile.Attribute.Owner, "owner");
-            attributes.put(SshFile.Attribute.Group, "group");
-            attributes.put(SshFile.Attribute.Size, (long) 0);
-            attributes.put(SshFile.Attribute.IsDirectory, false);
-            attributes.put(SshFile.Attribute.IsSymbolicLink, false);
-            attributes.put(SshFile.Attribute.IsRegularFile, false);
-            attributes.put(SshFile.Attribute.Permissions, EnumSet.noneOf(SshFile.Permission.class));
-            attributes.put(SshFile.Attribute.LastModifiedTime, (long) 0);
-        }
-        String username = (String) attributes.get(SshFile.Attribute.Owner);
+            attributes = new HashMap<>();
+            attributes.put(Attribute.Owner, "owner");
+            attributes.put(Attribute.Group, "group");
+            attributes.put(Attribute.Size, (long) 0);
+            attributes.put(Attribute.IsDirectory, false);
+            attributes.put(Attribute.IsSymbolicLink, false);
+            attributes.put(Attribute.IsRegularFile, false);
+            attributes.put(Attribute.Permissions, EnumSet.noneOf(Permission.class));
+            attributes.put(Attribute.LastModifiedTime, (long) 0);
+        }
+        String username = (String) attributes.get(Attribute.Owner);
         if (username.length() > 8) {
             username = username.substring(0, 8);
         } else {
@@ -1006,7 +1004,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 username = username + " ";
             }
         }
-        String group = (String) attributes.get(SshFile.Attribute.Group);
+        String group = (String) attributes.get(Attribute.Group);
         if (group.length() > 8) {
             group = group.substring(0, 8);
         } else {
@@ -1015,11 +1013,11 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             }
         }
 
-        long length = (Long) attributes.get(SshFile.Attribute.Size);
+        long length = (Long) attributes.get(Attribute.Size);
         String lengthString = String.format("%1$8s", length);
 
-        boolean isDirectory = (Boolean) attributes.get(SshFile.Attribute.IsDirectory);
-        boolean isLink = (Boolean) attributes.get(SshFile.Attribute.IsSymbolicLink);
+        boolean isDirectory = (Boolean) attributes.get(Attribute.IsDirectory);
+        boolean isLink = (Boolean) attributes.get(Attribute.IsSymbolicLink);
         int perms = getPermissions(attributes);
 
         StringBuilder sb = new StringBuilder();
@@ -1034,8 +1032,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         sb.append((perms & S_IWOTH) != 0 ? "w" : "-");
         sb.append((perms & S_IXOTH) != 0 ? "x" : "-");
         sb.append("  ");
-        sb.append(attributes.containsKey(SshFile.Attribute.NLink)
-                ? attributes.get(SshFile.Attribute.NLink) : "1");
+        sb.append(attributes.containsKey(Attribute.NLink)
+                ? attributes.get(Attribute.NLink) : "1");
         sb.append(" ");
         sb.append(username);
         sb.append(" ");
@@ -1043,63 +1041,63 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         sb.append(" ");
         sb.append(lengthString);
         sb.append(" ");
-        sb.append(getUnixDate((Long) attributes.get(SshFile.Attribute.LastModifiedTime)));
+        sb.append(getUnixDate((Long) attributes.get(Attribute.LastModifiedTime)));
         sb.append(" ");
-        sb.append(f.getName());
+        sb.append(f.getFileName().toString());
 
         return sb.toString();
     }
 
-    protected Map<SshFile.Attribute, Object> getPermissions(int perms) {
-        Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
+    protected Map<Attribute, Object> getPermissions(int perms) {
+        Map<Attribute, Object> attrs = new HashMap<>();
         if ((perms & S_IFMT) == S_IFREG) {
-            attrs.put(SshFile.Attribute.IsRegularFile, Boolean.TRUE);
+            attrs.put(Attribute.IsRegularFile, Boolean.TRUE);
         }
         if ((perms & S_IFMT) == S_IFDIR) {
-            attrs.put(SshFile.Attribute.IsDirectory, Boolean.TRUE);
+            attrs.put(Attribute.IsDirectory, Boolean.TRUE);
         }
         if ((perms & S_IFMT) == S_IFLNK) {
-            attrs.put(SshFile.Attribute.IsSymbolicLink, Boolean.TRUE);
+            attrs.put(Attribute.IsSymbolicLink, Boolean.TRUE);
         }
-        EnumSet<SshFile.Permission> p = EnumSet.noneOf(SshFile.Permission.class);
+        EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
         if ((perms & S_IRUSR) != 0) {
-            p.add(SshFile.Permission.UserRead);
+            p.add(Permission.UserRead);
         }
         if ((perms & S_IWUSR) != 0) {
-            p.add(SshFile.Permission.UserWrite);
+            p.add(Permission.UserWrite);
         }
         if ((perms & S_IXUSR) != 0) {
-            p.add(SshFile.Permission.UserExecute);
+            p.add(Permission.UserExecute);
         }
         if ((perms & S_IRGRP) != 0) {
-            p.add(SshFile.Permission.GroupRead);
+            p.add(Permission.GroupRead);
         }
         if ((perms & S_IWGRP) != 0) {
-            p.add(SshFile.Permission.GroupWrite);
+            p.add(Permission.GroupWrite);
         }
         if ((perms & S_IXGRP) != 0) {
-            p.add(SshFile.Permission.GroupExecute);
+            p.add(Permission.GroupExecute);
         }
         if ((perms & S_IROTH) != 0) {
-            p.add(SshFile.Permission.OthersRead);
+            p.add(Permission.OthersRead);
         }
         if ((perms & S_IWOTH) != 0) {
-            p.add(SshFile.Permission.OthersWrite);
+            p.add(Permission.OthersWrite);
         }
         if ((perms & S_IXOTH) != 0) {
-            p.add(SshFile.Permission.OthersExecute);
+            p.add(Permission.OthersExecute);
         }
-        attrs.put(SshFile.Attribute.Permissions, p);
+        attrs.put(Attribute.Permissions, p);
         return attrs;
     }
 
-    protected int getPermissions(Map<SshFile.Attribute, Object> attributes) {
-        boolean isReg = (Boolean) attributes.get(SshFile.Attribute.IsRegularFile);
-        boolean isDir = (Boolean) attributes.get(SshFile.Attribute.IsDirectory);
-        boolean isLnk = (Boolean) attributes.get(SshFile.Attribute.IsSymbolicLink);
+    protected int getPermissions(Map<Attribute, Object> attributes) {
+        boolean isReg = (Boolean) attributes.get(Attribute.IsRegularFile);
+        boolean isDir = (Boolean) attributes.get(Attribute.IsDirectory);
+        boolean isLnk = (Boolean) attributes.get(Attribute.IsSymbolicLink);
         int pf = 0;
-        EnumSet<SshFile.Permission> perms = (EnumSet<SshFile.Permission>) attributes.get(SshFile.Attribute.Permissions);
-        for (SshFile.Permission p : perms) {
+        EnumSet<Permission> perms = (EnumSet<Permission>) attributes.get(Attribute.Permissions);
+        for (Permission p : perms) {
             switch (p) {
                 case UserRead:      pf |= S_IRUSR; break;
                 case UserWrite:     pf |= S_IWUSR; break;
@@ -1118,41 +1116,41 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         return pf;
     }
 
-    protected void writeAttrs(Buffer buffer, SshFile file, boolean followLinks) throws IOException {
-        if (!file.doesExist()) {
-            throw new FileNotFoundException(file.getAbsolutePath());
+    protected void writeAttrs(Buffer buffer, Path file, boolean followLinks) throws IOException {
+        if (!Files.exists(file)) {
+            throw new FileNotFoundException(file.toString());
         }
-        Map<SshFile.Attribute, Object> attributes = file.getAttributes(followLinks);
-        boolean isReg = getBool((Boolean) attributes.get(SshFile.Attribute.IsRegularFile));
-        boolean isDir = getBool((Boolean) attributes.get(SshFile.Attribute.IsDirectory));
-        boolean isLnk = getBool((Boolean) attributes.get(SshFile.Attribute.IsSymbolicLink));
+        Map<Attribute, Object> attributes = getAttributes(file, followLinks);
+        boolean isReg = getBool((Boolean) attributes.get(Attribute.IsRegularFile));
+        boolean isDir = getBool((Boolean) attributes.get(Attribute.IsDirectory));
+        boolean isLnk = getBool((Boolean) attributes.get(Attribute.IsSymbolicLink));
         int flags = 0;
-        if ((isReg || isLnk) && attributes.containsKey(SshFile.Attribute.Size)) {
+        if ((isReg || isLnk) && attributes.containsKey(Attribute.Size)) {
             flags |= SSH_FILEXFER_ATTR_SIZE;
         }
-        if (attributes.containsKey(SshFile.Attribute.Uid) && attributes.containsKey(SshFile.Attribute.Gid)) {
+        if (attributes.containsKey(Attribute.Uid) && attributes.containsKey(Attribute.Gid)) {
             flags |= SSH_FILEXFER_ATTR_UIDGID;
         }
-        if (attributes.containsKey(SshFile.Attribute.Permissions)) {
+        if (attributes.containsKey(Attribute.Permissions)) {
             flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
         }
-        if (attributes.containsKey(SshFile.Attribute.LastAccessTime) && attributes.containsKey(SshFile.Attribute.LastModifiedTime)) {
+        if (attributes.containsKey(Attribute.LastAccessTime) && attributes.containsKey(Attribute.LastModifiedTime)) {
             flags |= SSH_FILEXFER_ATTR_ACMODTIME;
         }
         buffer.putInt(flags);
         if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-            buffer.putLong((Long) attributes.get(SshFile.Attribute.Size));
+            buffer.putLong((Long) attributes.get(Attribute.Size));
         }
         if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-            buffer.putInt((Integer) attributes.get(SshFile.Attribute.Uid));
-            buffer.putInt((Integer) attributes.get(SshFile.Attribute.Gid));
+            buffer.putInt((Integer) attributes.get(Attribute.Uid));
+            buffer.putInt((Integer) attributes.get(Attribute.Gid));
         }
         if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
             buffer.putInt(getPermissions(attributes));
         }
         if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-            buffer.putInt(((Long) attributes.get(SshFile.Attribute.LastAccessTime)) / 1000);
-            buffer.putInt(((Long) attributes.get(SshFile.Attribute.LastModifiedTime)) / 1000);
+            buffer.putInt(((Long) attributes.get(Attribute.LastAccessTime)) / 1000);
+            buffer.putInt(((Long) attributes.get(Attribute.LastModifiedTime)) / 1000);
         }
     }
 
@@ -1160,22 +1158,190 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         return bool != null && bool;
     }
 
-    protected Map<SshFile.Attribute, Object> readAttrs(Buffer buffer) throws IOException {
-        Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
+    protected Map<Attribute, Object> getAttributes(Path file, boolean followLinks) throws IOException {
+        String[] attrs = new String[] { "unix:*", "posix:*", "*" };
+        Map<String, Object> a = null;
+        for (String attr : attrs) {
+            try {
+                a = Files.readAttributes(
+                        file, attr,
+                        followLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
+                break;
+            } catch (UnsupportedOperationException e) {
+                // Ignore
+            }
+        }
+        if (a == null) {
+            throw new IllegalStateException();
+        }
+        Map<Attribute, Object> map = new HashMap<>();
+        map.put(Attribute.Size, a.get("size"));
+        if (a.containsKey("uid")) {
+            map.put(Attribute.Uid, a.get("uid"));
+        }
+        if (a.containsKey("owner")) {
+            map.put(Attribute.Owner, ((UserPrincipal) a.get("owner")).getName());
+        } else {
+            map.put(Attribute.Owner, session.getUsername());
+        }
+        if (a.containsKey("gid")) {
+            map.put(Attribute.Gid, a.get("gid"));
+        }
+        if (a.containsKey("group")) {
+            map.put(Attribute.Group, ((GroupPrincipal) a.get("group")).getName());
+        } else {
+            map.put(Attribute.Group, session.getUsername());
+        }
+        if (a.containsKey("nlink")) {
+            map.put(Attribute.NLink, a.get("nlink"));
+        }
+        map.put(Attribute.IsDirectory, a.get("isDirectory"));
+        map.put(Attribute.IsRegularFile, a.get("isRegularFile"));
+        map.put(Attribute.IsSymbolicLink, a.get("isSymbolicLink"));
+        map.put(Attribute.CreationTime, ((FileTime) a.get("creationTime")).toMillis());
+        map.put(Attribute.LastModifiedTime, ((FileTime) a.get("lastModifiedTime")).toMillis());
+        map.put(Attribute.LastAccessTime, ((FileTime) a.get("lastAccessTime")).toMillis());
+        if (a.containsKey("permissions")) {
+            map.put(Attribute.Permissions, fromPerms((Set<PosixFilePermission>) a.get("permissions")));
+        } else {
+            EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
+            if (Files.isReadable(file)) {
+                p.add(Permission.UserRead);
+                p.add(Permission.GroupRead);
+                p.add(Permission.OthersRead);
+            }
+            if (Files.isWritable(file)) {
+                p.add(Permission.UserWrite);
+                p.add(Permission.GroupWrite);
+                p.add(Permission.OthersWrite);
+            }
+            if (Files.isExecutable(file)) {
+                p.add(Permission.UserExecute);
+                p.add(Permission.GroupExecute);
+                p.add(Permission.OthersExecute);
+            }
+            map.put(Attribute.Permissions, p);
+        }
+        return map;
+    }
+
+    protected void setAttributes(Path file, Map<Attribute, Object>  attributes) throws IOException {
+        Set<Attribute> unsupported = new HashSet<>();
+        for (Attribute attribute : attributes.keySet()) {
+            String name = null;
+            Object value = attributes.get(attribute);
+            switch (attribute) {
+            case Size:             {
+                long newSize = (Long) value;
+                try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
+                    channel.truncate(newSize);
+                }
+                continue;
+            }
+            case Uid:              name = "unix:uid"; break;
+            case Gid:              name = "unix:gid"; break;
+            case Owner:            name = "posix:owner"; value = toUser(file, (String) value); break;
+            case Group:            name = "posix:group"; value = toGroup(file, (String) value); break;
+            case Permissions:      name = "posix:permissions"; value = toPerms((EnumSet<Permission>) value); break;
+            case CreationTime:     name = "basic:creationTime"; value = FileTime.fromMillis((Long) value); break;
+            case LastModifiedTime: name = "basic:lastModifiedTime"; value = FileTime.fromMillis((Long) value); break;
+            case LastAccessTime:   name = "basic:lastAccessTime"; value = FileTime.fromMillis((Long) value); break;
+            }
+            if (name != null && value != null) {
+                try {
+                    Files.setAttribute(file, name, value, LinkOption.NOFOLLOW_LINKS);
+                } catch (UnsupportedOperationException e) {
+                    unsupported.add(attribute);
+                }
+            }
+        }
+        handleUnsupportedAttributes(unsupported);
+    }
+
+    protected void handleUnsupportedAttributes(Collection<Attribute> attributes) {
+        if (!attributes.isEmpty()) {
+            StringBuilder sb = new StringBuilder();
+            for (Attribute attr : attributes) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(attr.name());
+            }
+            switch (unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("Unsupported attributes: " + sb.toString());
+                break;
+            case ThrowException:
+                throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
+            }
+        }
+    }
+
+
+    private EnumSet<Permission> fromPerms(Set<PosixFilePermission> perms) {
+        EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
+        for (PosixFilePermission perm : perms) {
+            switch (perm) {
+            case OWNER_READ:     p.add(Permission.UserRead); break;
+            case OWNER_WRITE:    p.add(Permission.UserWrite); break;
+            case OWNER_EXECUTE:  p.add(Permission.UserExecute); break;
+            case GROUP_READ:     p.add(Permission.GroupRead); break;
+            case GROUP_WRITE:    p.add(Permission.GroupWrite); break;
+            case GROUP_EXECUTE:  p.add(Permission.GroupExecute); break;
+            case OTHERS_READ:    p.add(Permission.OthersRead); break;
+            case OTHERS_WRITE:   p.add(Permission.OthersWrite); break;
+            case OTHERS_EXECUTE: p.add(Permission.OthersExecute); break;
+            }
+        }
+        return p;
+    }
+
+    private GroupPrincipal toGroup(Path file, String name) throws IOException {
+        UserPrincipalLookupService lookupService = file.getFileSystem().getUserPrincipalLookupService();
+        return lookupService.lookupPrincipalByGroupName(name);
+    }
+
+    private UserPrincipal toUser(Path file, String name) throws IOException {
+        UserPrincipalLookupService lookupService = file.getFileSystem().getUserPrincipalLookupService();
+        return lookupService.lookupPrincipalByName(name);
+    }
+
+    private Set<PosixFilePermission> toPerms(EnumSet<Permission> perms) {
+        Set<PosixFilePermission> set = new HashSet<>();
+        for (Permission p : perms) {
+            switch (p) {
+            case UserRead:      set.add(PosixFilePermission.OWNER_READ); break;
+            case UserWrite:     set.add(PosixFilePermission.OWNER_WRITE); break;
+            case UserExecute:   set.add(PosixFilePermission.OWNER_EXECUTE); break;
+            case GroupRead:     set.add(PosixFilePermission.GROUP_READ); break;
+            case GroupWrite:    set.add(PosixFilePermission.GROUP_WRITE); break;
+            case GroupExecute:  set.add(PosixFilePermission.GROUP_EXECUTE); break;
+            case OthersRead:    set.add(PosixFilePermission.OTHERS_READ); break;
+            case OthersWrite:   set.add(PosixFilePermission.OTHERS_WRITE); break;
+            case OthersExecute: set.add(PosixFilePermission.OTHERS_EXECUTE); break;
+            }
+        }
+        return set;
+    }
+
+    protected Map<Attribute, Object> readAttrs(Buffer buffer) throws IOException {
+        Map<Attribute, Object> attrs = new HashMap<>();
         int flags = buffer.getInt();
         if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
-            attrs.put(SshFile.Attribute.Size, buffer.getLong());
+            attrs.put(Attribute.Size, buffer.getLong());
         }
         if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
-            attrs.put(SshFile.Attribute.Uid, buffer.getInt());
-            attrs.put(SshFile.Attribute.Gid, buffer.getInt());
+            attrs.put(Attribute.Uid, buffer.getInt());
+            attrs.put(Attribute.Gid, buffer.getInt());
         }
         if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
             attrs.putAll(getPermissions(buffer.getInt()));
         }
         if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
-            attrs.put(SshFile.Attribute.LastAccessTime, ((long) buffer.getInt()) * 1000);
-            attrs.put(SshFile.Attribute.LastModifiedTime, ((long) buffer.getInt()) * 1000);
+            attrs.put(Attribute.LastAccessTime, ((long) buffer.getInt()) * 1000);
+            attrs.put(Attribute.LastModifiedTime, ((long) buffer.getInt()) * 1000);
         }
         return attrs;
     }
@@ -1203,37 +1369,46 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     }
 
     public void destroy() {
-        // 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 (!closed) {
             if (log.isDebugEnabled()) {
-                log.debug("destroy() - cancel pending future=" + result);
+                log.debug("destroy() - mark as closed");
             }
-        }
 
-        pendingFuture = null;
+            closed = true;
 
-        if ((executors != null) && shutdownExecutor) {
-            Collection<Runnable> runners = executors.shutdownNow();
-            if (log.isDebugEnabled()) {
-                log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
+            // 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);
+                }
             }
-        }
 
-        executors = null;
+            pendingFuture = null;
 
-        if (!closed) {
-            if (log.isDebugEnabled()) {
-                log.debug("destroy() - mark as closed");
+            if ((executors != null) && shutdownExecutor) {
+                Collection<Runnable> runners = executors.shutdownNow();
+                if (log.isDebugEnabled()) {
+                    log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
+                }
             }
 
-            closed = true;
+            executors = null;
+
+            try {
+                fileSystem.close();
+            } catch (UnsupportedOperationException e) {
+                // Ignore
+            } catch (IOException e) {
+                log.debug("Error closing FileSystem", e);
+            }
         }
     }
 
-    private SshFile resolveFile(String path) {
-    	return this.root.getNormalizedView().getFile(path);
+    private Path resolveFile(String path) {
+        return defaultDir.resolve(path);
+//    	return this.fileSystem.getPath(path);
     }
 
     private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
@@ -1242,12 +1417,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     /**
      * Get unix style date string.
      */
-    private final static String getUnixDate(long millis) {
+    private static String getUnixDate(long millis) {
         if (millis < 0) {
             return "------------";
         }
 
-        StringBuffer sb = new StringBuffer(16);
+        StringBuilder sb = new StringBuilder(16);
         Calendar cal = new GregorianCalendar();
         cal.setTimeInMillis(millis);
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
index 42d1dcc..750ecc9 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
@@ -94,9 +94,10 @@ public class SftpFileSystemTest extends BaseTest {
 //        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
 
         Path link = fs.getPath("target/sftp/client/test2.txt");
-        Files.createSymbolicLink(link, file);
+        Files.createSymbolicLink(link, link.getParent().relativize(file));
         assertTrue(Files.isSymbolicLink(link));
-        assertEquals(file.toAbsolutePath().toString(), Files.readSymbolicLink(link).toString());
+        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
+        Files.delete(link);
 
         attrs = Files.readAttributes(file, "*", LinkOption.NOFOLLOW_LINKS);
         System.out.println(attrs);
@@ -109,4 +110,15 @@ public class SftpFileSystemTest extends BaseTest {
         fs.close();
     }
 
+    @Test
+    public void testRootFileSystem() throws Exception {
+        Path rootNative = Paths.get("target/root").toAbsolutePath();
+        Utils.deleteRecursive(rootNative.toFile());
+        Files.createDirectories(rootNative);
+
+        FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null);
+
+        Files.createDirectories(fs.getPath("test/foo"));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
index b4f2c0e..b44f6af 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
@@ -458,7 +458,7 @@ public class SftpTest extends BaseTest {
         assertTrue(target.exists());
         assertEquals("0123456789", readFile(unixPath));
 
-        c.symlink(unixPath, linkUnixPath);
+        c.symlink(linkUnixPath, unixPath);
 
         assertTrue(link.exists());
         assertEquals("0123456789", readFile(linkUnixPath));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeFileSystemViewTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeFileSystemViewTest.java b/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeFileSystemViewTest.java
deleted file mode 100644
index a77d950..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeFileSystemViewTest.java
+++ /dev/null
@@ -1,115 +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.file.nativefs;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-public class NativeFileSystemViewTest {
-
-    @Test
-    public void testResolveWithVirtualRoots() throws Exception {
-
-        Map<String, String> roots = new HashMap<String, String>();
-        roots.put("A:", "/fs/rootA");
-        roots.put("Z:", "/fs/rootZ");
-        String current = "Z:/git";
-        NativeFileSystemView view = new NativeFileSystemView("user", roots, current, '/', false);
-
-        assertEquals("Z:/git/foo/bar", view.getFile("foo/bar").getAbsolutePath());
-        assertEquals("Z:/foo/bar", view.getFile("../foo/bar").getAbsolutePath());
-        assertEquals("A:/temp", view.getFile("A:/./a/../temp").getAbsolutePath());
-        assertEquals("A:/temp", view.getFile("A:/../../temp").getAbsolutePath());
-
-        FileSystemView normView = view.getNormalizedView();
-
-        assertEquals("/Z/git/foo/bar", normView.getFile("foo/bar").getAbsolutePath());
-        assertEquals("/Z/foo/bar", normView.getFile("../foo/bar").getAbsolutePath());
-        assertEquals("/A/temp", normView.getFile("/A/./a/../temp").getAbsolutePath());
-        assertEquals("/Z/temp", normView.getFile("/A/../Z/temp").getAbsolutePath());
-    }
-
-    @Test
-    public void testResolveWithVirtualRootsWithBackslash() throws Exception {
-
-        Map<String, String> roots = new HashMap<String, String>();
-        roots.put("A:", "/fs/rootA");
-        roots.put("Z:", "/fs/rootZ");
-        String current = "Z:/git";
-        NativeFileSystemView view = new NativeFileSystemView("user", roots, current, '\\', false);
-
-        assertEquals("Z:\\git\\foo\\bar", view.getFile("/Z:/git/foo/bar").getAbsolutePath());
-        assertEquals("Z:\\git\\foo\\bar", view.getFile("foo/bar").getAbsolutePath());
-        assertEquals("Z:\\git\\foo", view.getFile("foo/bar").getParentFile().getAbsolutePath());
-        assertEquals("Z:\\git", view.getFile("foo/bar").getParentFile().getParentFile().getAbsolutePath());
-        assertEquals("Z:\\", view.getFile("foo/bar").getParentFile().getParentFile().getParentFile().getAbsolutePath());
-        assertFalse(view.getFile("foo/bar").getParentFile().getParentFile().getParentFile().isRemovable());
-        assertEquals("Z:\\foo\\bar", view.getFile("../foo/bar").getAbsolutePath());
-        assertEquals("A:\\temp", view.getFile("A:/./a/../temp").getAbsolutePath());
-        assertEquals("A:\\temp", view.getFile("A:/../../temp").getAbsolutePath());
-
-        FileSystemView normView = view.getNormalizedView();
-
-        assertEquals("/Z/git/foo/bar", normView.getFile("foo/bar").getAbsolutePath());
-        assertEquals("/Z/git/foo", normView.getFile("foo/bar").getParentFile().getAbsolutePath());
-        assertEquals("/Z/foo/bar", normView.getFile("../foo/bar").getAbsolutePath());
-        assertEquals("/A/temp", normView.getFile("/A/./a/../temp").getAbsolutePath());
-        assertEquals("/Z/temp", normView.getFile("/A/../Z/temp").getAbsolutePath());
-    }
-
-    @Test
-    public void testResolveWithPhysicalRoots() throws Exception {
-
-        Map<String, String> roots = new HashMap<String, String>();
-        roots.put("V:", "A:/bar");
-        roots.put("X:", "B:");
-        String current = "X:/git";
-        NativeFileSystemView view = new NativeFileSystemView("user", roots, current, '/', false);
-
-        assertEquals("X:/git/foo/bar", view.getFile("foo/bar").getAbsolutePath());
-
-        assertEquals("X:/foo/bar", view.getFile("X:/foo/bar").getAbsolutePath());
-        assertEquals(new File("B:/foo/bar").toString(), ((NativeSshFile) view.getFile("X:/foo/bar")).getNativeFile().toString());
-
-        assertEquals("X:/foo/bar", view.getFile("../foo/bar").getAbsolutePath());
-        assertEquals(new File("B:/foo/bar").toString(), ((NativeSshFile) view.getFile("../foo/bar")).getNativeFile().toString());
-
-        assertEquals("V:/temp", view.getFile("V:/./a/../temp").getAbsolutePath());
-
-        assertEquals("V:/temp", view.getFile("V:/../../temp").getAbsolutePath());
-        assertEquals(new File("A:/bar/temp").toString(), ((NativeSshFile) view.getFile("V:/../../temp")).getNativeFile().toString());
-
-        assertEquals("X:/", view.getFile("..").getAbsolutePath());
-
-        SshFile cur = view.getFile(".");
-        assertEquals("X:/git", cur.getAbsolutePath());
-        cur = view.getFile(cur, "..");
-        assertEquals("X:/", cur.getAbsolutePath());
-
-
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeSshFileTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeSshFileTest.java b/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeSshFileTest.java
deleted file mode 100644
index 7ff2787..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/common/file/nativefs/NativeSshFileTest.java
+++ /dev/null
@@ -1,50 +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.file.nativefs;
-
-import org.apache.sshd.util.BaseTest;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class NativeSshFileTest extends BaseTest {
-
-    @Test
-    public void testResolve() {
-        assertEquals("Z:/git/mina-sshd/sshd-core/target/scp/remote/out.txt",
-                NativeSshFile.getPhysicalName("Z:\\git/", "Z:\\git\\mina-sshd\\sshd-core", "\\mina-sshd\\sshd-core\\target\\scp\\remote\\out.txt", false));
-        assertEquals("Z:/git/mina-sshd/sshd-core/target/scp/remote/out.txt",
-                NativeSshFile.getPhysicalName("Z:/", "Z:\\git\\mina-sshd\\sshd-core", "\\git\\mina-sshd\\sshd-core\\target\\scp\\remote\\out.txt", false));
-        assertEquals("Z:/git/mina-sshd/sshd-core/target/scp/remote/out.txt",
-                NativeSshFile.getPhysicalName("Z:/", "Z:\\git\\mina-sshd\\sshd-core", "/git/mina-sshd/sshd-core/target/scp/remote/out.txt", false));
-
-        assertEquals("/bar", NativeSshFile.getPhysicalName("/", "/foo", "/bar", false));
-        assertEquals("/bar", NativeSshFile.getPhysicalName("/", "/", "/bar", false));
-        assertEquals("/bar", NativeSshFile.getPhysicalName("/", "/", "bar", false));
-        assertEquals("/foo/bar", NativeSshFile.getPhysicalName("/", "/foo", "bar", false));
-        assertEquals("/foo/bar", NativeSshFile.getPhysicalName("/", "/foo/xyz", "../bar", false));
-        assertEquals("/foo/xyz/bar", NativeSshFile.getPhysicalName("/", "/foo/xyz", "./bar", false));
-
-        assertEquals("/foo/bar", NativeSshFile.getPhysicalName("/foo", "/", "bar", false));
-        assertEquals("/foo/bar", NativeSshFile.getPhysicalName("/foo", "/xyz", "../bar", false));
-        assertEquals("/foo/bar", NativeSshFile.getPhysicalName("/foo", "/xyz", "../../bar", false));
-        assertEquals("/foo/xyz/bar", NativeSshFile.getPhysicalName("/foo", "/xyz", "./bar", false));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/345604ba/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
deleted file mode 100644
index 17e651e..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
+++ /dev/null
@@ -1,107 +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.file.virtualfs;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.apache.sshd.SshServer;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
-import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory;
-import org.apache.sshd.common.file.nativefs.NativeSshFile;
-import org.apache.sshd.common.session.AbstractSession;
-import org.apache.sshd.common.util.Buffer;
-import org.apache.sshd.util.BaseTest;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class VirtualFileSystemTest extends BaseTest {
-
-    @Test
-    public void testNativeFileSystem() throws IOException {
-        String homeDir = System.getProperty("user.dir");
-        NativeFileSystemFactory vfs = new NativeFileSystemFactory();
-
-        FileSystemView view = vfs.createFileSystemView(new TestSession());
-
-        SshFile file = view.getFile("foo");
-        String physicalName = ((NativeSshFile) file).getNativeFile().getAbsolutePath();
-        assertEquals(homeDir + File.separator + "foo", physicalName);
-
-        file = view.getFile(view.getFile("foo"), "../bar");
-        physicalName = ((NativeSshFile) file).getNativeFile().getAbsolutePath();
-        assertEquals(homeDir + File.separator + "bar", physicalName);
-
-        file = view.getFile("../bar");
-        physicalName = ((NativeSshFile) file).getNativeFile().getAbsolutePath();
-        assertEquals(new File(homeDir, "../bar").getCanonicalPath(), physicalName);
-    }
-
-    @Test
-    public void testVirtualFileSystem() {
-        String homeDir = System.getProperty("user.dir");
-        VirtualFileSystemFactory vfs = new VirtualFileSystemFactory(homeDir);
-
-        FileSystemView view = vfs.createFileSystemView(new TestSession());
-
-        SshFile file = view.getFile("foo");
-        String physicalName = ((NativeSshFile) file).getNativeFile().getAbsolutePath();
-        assertEquals(homeDir + File.separator + "foo", physicalName);
-
-        file = view.getFile(view.getFile("foo"), "../bar");
-        physicalName = ((NativeSshFile) file).getNativeFile().getAbsolutePath();
-        assertEquals(homeDir + File.separator + "bar", physicalName);
-
-        file = view.getFile("../bar");
-        physicalName = ((NativeSshFile) file).getNativeFile().getAbsolutePath();
-        assertEquals(homeDir + File.separator + "bar", physicalName);
-    }
-
-    static class TestSession extends AbstractSession {
-        TestSession() {
-            super(true, SshServer.setUpDefaultServer(), null);
-            this.username = "userName";
-        }
-        @Override
-        protected void handleMessage(Buffer buffer) throws Exception {
-        }
-        @Override
-        protected boolean readIdentification(Buffer buffer) throws IOException {
-            return false;
-        }
-        @Override
-        protected void sendKexInit() throws IOException {
-        }
-        @Override
-        protected void checkKeys() {
-        }
-        @Override
-        protected void receiveKexInit(Buffer buffer) throws IOException {
-        }
-        @Override
-        public void startService(String name) throws Exception {
-        }
-        @Override
-        public void resetIdleTimeout() {
-        }
-    }
-
-}


[14/15] mina-sshd git commit: [SSHD-416] Avoid unnecessary memory copies when reading / writing data to files

Posted by gn...@apache.org.
[SSHD-416] Avoid unnecessary memory copies when reading / writing data to files


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

Branch: refs/heads/master
Commit: 656f0d17e5cf2b7384156feb77c193d5c85f0e77
Parents: 55d45e9
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Feb 23 01:08:48 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Feb 23 16:21:30 2015 +0100

----------------------------------------------------------------------
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 29 ++++++++++++++------
 1 file changed, 20 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/656f0d17/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
index 28fba31..b78315c 100644
--- 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
@@ -1196,15 +1196,23 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     protected void doWrite(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         long offset = buffer.getLong();
-        byte[] data = buffer.getBytes();
-        log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])", new Object[] { handle, offset, data.length });
+        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();
+        log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])", new Object[] { handle, offset, 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, offset);
+                fh.write(data, doff, length, offset);
                 sendStatus(id, SSH_FX_OK, "");
             }
         } catch (IOException e) {
@@ -1223,13 +1231,16 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
             } else {
                 FileHandle fh = (FileHandle) p;
-                byte[] b = new byte[Math.min(len, Buffer.MAX_LEN)];
-                len = fh.read(b, offset);
+                Buffer buf = new Buffer(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) {
-                    Buffer buf = new Buffer(len + 5);
-                    buf.putByte((byte) SSH_FXP_DATA);
-                    buf.putInt(id);
-                    buf.putBytes(b, 0, len);
+                    buf.wpos(pos);
+                    buf.putInt(len);
+                    buf.wpos(pos + 4 + len);
                     send(buf);
                 } else {
                     sendStatus(id, SSH_FX_EOF, "");