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

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

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

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

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

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

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

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

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