Repository: mina-sshd
Updated Branches:
  refs/heads/master df8324bcf -> 95e307d9c


[SSHD-514] Implement FileStore for SftpFileSystem


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

Branch: refs/heads/master
Commit: 95e307d9ce81fabf47b2c5edc8b65992f851194a
Parents: df8324b
Author: Lyor Goldstein <[email protected]>
Authored: Thu Jul 2 10:19:14 2015 +0300
Committer: Lyor Goldstein <[email protected]>
Committed: Thu Jul 2 10:19:14 2015 +0300

----------------------------------------------------------------------
 .../sshd/client/session/ClientSessionImpl.java  |   3 +-
 .../client/subsystem/sftp/SftpFileStore.java    | 109 ++++++++
 .../client/subsystem/sftp/SftpFileSystem.java   |  50 +++-
 .../subsystem/sftp/SftpFileSystemProvider.java  | 250 +++++++++++++------
 .../sshd/common/file/root/RootedFileSystem.java |  24 +-
 .../file/root/RootedFileSystemProvider.java     |  53 ++--
 .../org/apache/sshd/common/util/io/IoUtils.java |  25 +-
 .../server/subsystem/sftp/SftpSubsystem.java    |   5 +
 .../java/org/apache/sshd/client/ClientTest.java |   8 +-
 .../subsystem/sftp/SftpFileSystemTest.java      |  29 ++-
 .../apache/sshd/common/util/io/IoUtilsTest.java |  44 ++++
 11 files changed, 487 insertions(+), 113 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/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 73f1b46..936f24f 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
@@ -407,7 +407,8 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
 
     @Override
     public FileSystem createSftpFileSystem(int readBufferSize, int 
writeBufferSize) throws IOException {
-        SftpFileSystem  fs=new SftpFileSystem(new 
SftpFileSystemProvider((org.apache.sshd.client.SshClient) factoryManager), 
this);
+        SftpFileSystemProvider provider = new 
SftpFileSystemProvider((org.apache.sshd.client.SshClient) factoryManager);
+        SftpFileSystem  fs = provider.newFileSystem(this);
         fs.setReadBufferSize(readBufferSize);
         fs.setWriteBufferSize(writeBufferSize);
         return fs;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
new file mode 100644
index 0000000..14d43ae
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
@@ -0,0 +1,109 @@
+/*
+ * 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.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Collection;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class SftpFileStore extends FileStore {
+    private final SftpFileSystem fs;
+    private final String name;
+
+    public SftpFileStore(String name, SftpFileSystem fs) {
+        this.name = name;
+        this.fs = fs;
+    }
+
+    public final SftpFileSystem getFileSystem() {
+        return fs;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public String type() {
+        return SftpConstants.SFTP_SUBSYSTEM_NAME;
+    }
+
+    @Override
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    @Override
+    public long getTotalSpace() throws IOException {
+        return Long.MAX_VALUE;  // TODO use SFTPv6 space-available extension
+    }
+
+    @Override
+    public long getUsableSpace() throws IOException {
+        return Long.MAX_VALUE;
+    }
+
+    @Override
+    public long getUnallocatedSpace() throws IOException {
+        return Long.MAX_VALUE;
+    }
+
+    @Override
+    public boolean supportsFileAttributeView(Class<? extends 
FileAttributeView> type) {
+        SftpFileSystem  sftpFs = getFileSystem();
+        SftpFileSystemProvider provider = sftpFs.provider();
+        return provider.isSupportedFileAttributeView(type);
+    }
+
+    @Override
+    public boolean supportsFileAttributeView(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return false;   // debug breakpoint
+        }
+        
+        FileSystem  sftpFs = getFileSystem();
+        Collection<String> views = sftpFs.supportedFileAttributeViews();
+        if (GenericUtils.isEmpty(views) || (!views.contains(name))) {
+            return false;   // debug breakpoint
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public <V extends FileStoreAttributeView> V 
getFileStoreAttributeView(Class<V> type) {
+        return null;    // no special views supported
+    }
+
+    @Override
+    public Object getAttribute(String attribute) throws IOException {
+        return null;    // no special attributes supported
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/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
index edbf82b..4b8f54c 100644
--- 
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
@@ -21,40 +21,71 @@ package org.apache.sshd.client.subsystem.sftp;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystemException;
 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.List;
 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.FactoryManagerUtils;
 import org.apache.sshd.common.file.util.BaseFileSystem;
 import org.apache.sshd.common.file.util.ImmutableList;
+import org.apache.sshd.common.util.GenericUtils;
 
 public class SftpFileSystem extends BaseFileSystem<SftpPath> {
+    public static final String POOL_SIZE_PROP = "sftp-fs-pool-size";
+        public static final int DEFAULT_POOL_SIZE = 8;
 
+    public static final Set<String> SUPPORTED_VIEWS =
+            Collections.unmodifiableSet(
+                    GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
+                            Arrays.asList(
+                                "basic", "posix", "owner"
+                            )));
+
+    private final String id;
     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;
+    private final List<FileStore> stores;
 
-    public SftpFileSystem(SftpFileSystemProvider provider, ClientSession 
session) throws IOException {
+    public SftpFileSystem(SftpFileSystemProvider provider, String id, 
ClientSession session) throws IOException {
         super(provider);
+        this.id = id;
         this.session = session;
-        this.pool = new LinkedBlockingQueue<>(8);
+        this.stores = 
Collections.unmodifiableList(Collections.<FileStore>singletonList(new 
SftpFileStore(id, this)));
+        this.pool = new 
LinkedBlockingQueue<>(FactoryManagerUtils.getIntProperty(session, 
POOL_SIZE_PROP, DEFAULT_POOL_SIZE));
         try (SftpClient client = getClient()) {
             defaultDir = getPath(client.canonicalPath("."));
         }
     }
 
+    public final String getId() {
+        return id;
+    }
+
+    @Override
+    public SftpFileSystemProvider provider() {
+        return (SftpFileSystemProvider) super.provider();
+    }
+
+    @Override   // NOTE: co-variant return
+    public List<FileStore> getFileStores() {
+        return this.stores;
+    }
+
     public int getReadBufferSize() {
         return readBufferSize;
     }
@@ -111,20 +142,25 @@ public class SftpFileSystem extends 
BaseFileSystem<SftpPath> {
     @Override
     public void close() throws IOException {
         if (isOpen()) {
+            SftpFileSystemProvider provider = provider();
+            String fsId = getId();
+            SftpFileSystem fs = provider.removeFileSystem(fsId);
             session.close(true);
+            
+            if ((fs != null) && (fs != this)) {
+                throw new FileSystemException(fsId, fsId, "Mismatched FS 
instance for id=" + fsId);
+            }
         }
     }
 
     @Override
     public boolean isOpen() {
-        return !session.isClosing();
+        return session.isOpen();
     }
 
     @Override
     public Set<String> supportedFileAttributeViews() {
-        Set<String> set = new HashSet<>();
-        set.addAll(Arrays.asList("basic", "posix", "owner"));
-        return Collections.unmodifiableSet(set);
+        return SUPPORTED_VIEWS;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index 0e5169c..8cf981c 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -32,6 +32,8 @@ 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.InetSocketAddress;
+import java.net.SocketAddress;
 import java.net.URI;
 import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
@@ -63,7 +65,9 @@ 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.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -72,14 +76,15 @@ 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.FactoryManager;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -94,6 +99,15 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         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;
+    public static final String AUTH_TIME_PROP_NAME = "sftp-fs-auth-time";
+        public static final long DEFAULT_AUTH_TIME = 
SftpClient.DEFAULT_WAIT_TIMEOUT;
+
+    public static final Set<Class<? extends FileAttributeView>> 
SUPPORTED_VIEWS =
+            Collections.unmodifiableSet(
+                    new HashSet<Class<? extends FileAttributeView>>(
+                            Arrays.<Class<? extends FileAttributeView>>asList(
+                                    BasicFileAttributeView.class, 
PosixFileAttributeView.class
+                            )));
 
     private final SshClient client;
     private final Map<String, SftpFileSystem> fileSystems = new 
HashMap<String, SftpFileSystem>();
@@ -103,14 +117,20 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         this(null);
     }
 
+    /**
+     * @param client The {@link SshClient} to use - if {@code null} then a
+     * default one will be setup and started. Otherwise, it is assumed that
+     * the client has already been started
+     * @see SshClient#setUpDefaultClient()
+     */
     public SftpFileSystemProvider(SshClient client) {
         this.log = LoggerFactory.getLogger(getClass());
         if (client == null) {
             // TODO: make this configurable using system properties
-            client = ClientBuilder.builder().build();
+            client = SshClient.setUpDefaultClient();
+            client.start();
         }
         this.client = client;
-        this.client.start();
     }
 
     @Override
@@ -120,33 +140,35 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
 
     @Override
     public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws 
IOException {
+        String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), 
"Host not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+        int port = uri.getPort();
+        if (port <= 0) {
+            port = SshConfigFileReader.DEFAULT_PORT;
+        }
+
+        String userInfo = 
ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not 
provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+        String[] ui = GenericUtils.split(userInfo, ':');
+        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user 
info: %s", userInfo);
+        String username = ui[0], password = ui[1];
+        String id = getFileSystemIdentifier(host, port, userInfo);
+
+        SftpFileSystem fileSystem;
         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;
+            if ((fileSystem = fileSystems.get(id)) != null) {
+                throw new FileSystemAlreadyExistsException(id);
             }
 
             ClientSession session=null;
             try {
-                session = client.connect(ui[0], host, port)
+                session = client.connect(username, 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;
+                session.addPasswordIdentity(password);
+                session.auth().verify(FactoryManagerUtils.getLongProperty(env, 
AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME));
+
+                fileSystem = new SftpFileSystem(this, id, session);
+                fileSystems.put(id, fileSystem);
             } catch(Exception e) {
                 if (session != null) {
                     try {
@@ -170,17 +192,76 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
                 }
             }
         }
+        
+        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));
+        return fileSystem;
+    }
+
+    public SftpFileSystem newFileSystem(ClientSession session) throws 
IOException {
+        IoSession ioSession = session.getIoSession();
+        SocketAddress addr = ioSession.getRemoteAddress();
+        String username = session.getUsername();
+        String userauth = username + ":" + session.toString();
+        String id;
+        if (addr instanceof InetSocketAddress) {
+            InetSocketAddress inetAddr = (InetSocketAddress) addr;
+            id = getFileSystemIdentifier(inetAddr.getHostString(), 
inetAddr.getPort(), userauth);
+        } else {
+            id = getFileSystemIdentifier(addr.toString(), 
SshConfigFileReader.DEFAULT_PORT, userauth);
+        }
+        
+        SftpFileSystem fileSystem;
+        synchronized (fileSystems) {
+            if ((fileSystem=fileSystems.get(id)) != null) {
+                throw new FileSystemAlreadyExistsException(id);
+            }
+
+            fileSystem = new SftpFileSystem(this, id, session);
+            fileSystems.put(id, fileSystem);
+        }
+        
+        FactoryManager manager = session.getFactoryManager();
+        
fileSystem.setReadBufferSize(FactoryManagerUtils.getIntProperty(manager, 
READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+        
fileSystem.setWriteBufferSize(FactoryManagerUtils.getIntProperty(manager, 
WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+        return fileSystem;
     }
 
     @Override
     public FileSystem getFileSystem(URI uri) {
+        String id = getFileSystemIdentifier(uri);
+        SftpFileSystem fs = getFileSystem(id);
+        if (fs == null) {
+            throw new FileSystemNotFoundException(id);
+        }
+        return fs;
+    }
+
+    /**
+     * @param id File system identifier - ignored if {@code null}/empty
+     * @return The removed {@link SftpFileSystem} - {@code null} if no match
+     */
+    public SftpFileSystem removeFileSystem(String id) {
+        if (GenericUtils.isEmpty(id)) {
+            return null;
+        }
+
         synchronized (fileSystems) {
-            String authority = uri.getAuthority();
-            SftpFileSystem fileSystem = fileSystems.get(authority);
-            if (fileSystem == null) {
-                throw new FileSystemNotFoundException(authority);
-            }
-            return fileSystem;
+            return fileSystems.remove(id);
+        }
+    }
+
+    /**
+     * @param id File system identifier - ignored if {@code null}/empty
+     * @return The cached {@link SftpFileSystem} - {@code null} if no match
+     */
+    protected SftpFileSystem getFileSystem(String id) {
+        if (GenericUtils.isEmpty(id)) {
+            return null;
+        }
+
+        synchronized (fileSystems) {
+            return fileSystems.get(id);
         }
     }
 
@@ -461,7 +542,19 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
 
     @Override
     public FileStore getFileStore(Path path) throws IOException {
-        throw new FileSystemException(path.toString(), path.toString(), 
"getFileStore(" + path + ") N/A");
+        FileSystem fs = path.getFileSystem();
+        if (!(fs instanceof SftpFileSystem)) {
+            throw new FileSystemException(path.toString(), path.toString(), 
"getFileStore(" + path + ") path not attached to an SFTP file system");
+        }
+        
+        SftpFileSystem sftpFs = (SftpFileSystem) fs;
+        String id = sftpFs.getId();
+        SftpFileSystem cached = getFileSystem(id);
+        if (cached != sftpFs) {
+            throw new FileSystemException(path.toString(), path.toString(), 
"Mismatched file system instance for id=" + id);
+        }
+        
+        return sftpFs.getFileStores().get(0);
     }
 
     @Override
@@ -519,11 +612,10 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         }
     }
 
-    @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() {
+        if (isSupportedFileAttributeView(type)) {
+            return type.cast(new PosixFileAttributeView() {
                 @Override
                 public String name() {
                     return "view";
@@ -537,7 +629,7 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
                     final SftpClient.Attributes attributes;
                     try (SftpClient client =fs.getClient()) {
                         try {
-                            if (followLinks(options)) {
+                            if (IoUtils.followLinks(options)) {
                                 attributes = client.stat(p.toString());
                             } else {
                                 attributes = client.lstat(p.toString());
@@ -645,12 +737,20 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
                 public void setOwner(UserPrincipal owner) throws IOException {
                     setAttribute(path, "owner", owner, options);
                 }
-            };
+            });
         } else {
             throw new UnsupportedOperationException("getFileAttributeView(" + 
path + ") view not supported: " + type.getSimpleName());
         }
     }
 
+    public boolean isSupportedFileAttributeView(Class<?> type) {
+        if ((type != null) && SUPPORTED_VIEWS.contains(type)) {
+            return true;
+        } else {
+            return false;   // debug breakpoint 
+        }
+    }
+
     @Override
     public <A extends BasicFileAttributes> A readAttributes(Path path, 
Class<A> type, LinkOption... options) throws IOException {
         if (type.isAssignableFrom(PosixFileAttributes.class)) {
@@ -802,48 +902,6 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         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;
@@ -889,4 +947,44 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         return pf;
     }
 
+
+    public static 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;
+    }
+
+    public static final String getFileSystemIdentifier(URI uri) {
+        return getFileSystemIdentifier(uri.getHost(), uri.getPort(), 
uri.getUserInfo());
+    }
+    
+    public static final String getFileSystemIdentifier(String host, int port, 
String userAuth) {
+        return userAuth;
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/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
index 2a0769e..0140cfe 100644
--- 
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
@@ -19,6 +19,8 @@
 package org.apache.sshd.common.file.root;
 
 import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
 import java.nio.file.Path;
 import java.nio.file.attribute.UserPrincipalLookupService;
 import java.util.Map;
@@ -32,11 +34,13 @@ import org.apache.sshd.common.file.util.ImmutableList;
  */
 public class RootedFileSystem extends BaseFileSystem<RootedPath> {
 
-    private Path rootPath;
+    private final Path rootPath;
+    private final FileSystem rootFs;
 
     public RootedFileSystem(RootedFileSystemProvider fileSystemProvider, Path 
root, Map<String, ?> env) {
         super(fileSystemProvider);
         this.rootPath = root;
+        this.rootFs = root.getFileSystem();
     }
 
     public Path getRoot() {
@@ -50,26 +54,36 @@ public class RootedFileSystem extends 
BaseFileSystem<RootedPath> {
 
     @Override
     public boolean isOpen() {
-        return getRoot().getFileSystem().isOpen();
+        return rootFs.isOpen();
     }
 
     @Override
     public boolean isReadOnly() {
-        return getRoot().getFileSystem().isReadOnly();
+        return rootFs.isReadOnly();
     }
 
     @Override
     public Set<String> supportedFileAttributeViews() {
-        return rootPath.getFileSystem().supportedFileAttributeViews();
+        return rootFs.supportedFileAttributeViews();
     }
 
     @Override
     public UserPrincipalLookupService getUserPrincipalLookupService() {
-        return getRoot().getFileSystem().getUserPrincipalLookupService();
+        return rootFs.getUserPrincipalLookupService();
     }
 
     @Override
     protected RootedPath create(String root, ImmutableList<String> names) {
         return new RootedPath(this, root, names);
     }
+
+    @Override
+    public Iterable<FileStore> getFileStores() {
+        return rootFs.getFileStores();
+    }
+
+    @Override
+    public String toString() {
+        return rootPath.toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/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 ef67e34..2869417 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
@@ -32,6 +32,7 @@ import java.nio.file.DirectoryStream;
 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.OpenOption;
@@ -92,27 +93,18 @@ public class RootedFileSystemProvider extends 
FileSystemProvider {
 
     @Override
     public FileSystem getFileSystem(URI uri) {
-        Path path = uriToPath(uri);
-        Path real;
         try {
-            real = path.toRealPath();
-        } catch (IOException e) {
+            FileSystem fileSystem = getFileSystem(uriToPath(uri));
+            if (fileSystem == null) {
+                throw new FileSystemNotFoundException(uri.toString());
+            }
+
+            return fileSystem;
+        } catch(IOException e) {
             FileSystemNotFoundException err = new 
FileSystemNotFoundException(uri.toString());
             err.initCause(e);
             throw err;
         }
-
-        RootedFileSystem fileSystem = null;
-        synchronized (fileSystems) {
-            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
@@ -276,7 +268,34 @@ public class RootedFileSystemProvider extends 
FileSystemProvider {
 
     @Override
     public FileStore getFileStore(Path path) throws IOException {
-        throw new UnsupportedOperationException("getFileStore(" + path + ") 
N/A");
+        FileSystem fileSystem = getFileSystem(path);
+        if (fileSystem == null) {
+            throw new FileSystemNotFoundException(path.toString());
+        }
+
+        Iterable<FileStore> stores = fileSystem.getFileStores();
+        if (stores == null) {
+            throw new FileSystemException(path.toString(), path.toString(), 
"No stores");
+        }
+        
+        for (FileStore s : stores) {
+            return s;
+        }
+
+        throw new FileSystemException(path.toString(), path.toString(), "empty 
stores");
+    }
+
+    protected RootedFileSystem getFileSystem(Path path) throws IOException {
+        try {
+            Path real = path.toRealPath();
+            synchronized (fileSystems) {
+                return fileSystems.get(real);
+            }
+        } catch (IOException e) {
+            FileSystemNotFoundException err = new 
FileSystemNotFoundException(path.toString());
+            err.initCause(e);
+            throw err;
+        }
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/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 74dd77f..4550433 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
@@ -45,7 +45,7 @@ import org.apache.sshd.common.util.OsUtils;
  *
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
  */
-public class IoUtils {
+public final class IoUtils {
     public static final OpenOption[] EMPTY_OPEN_OPTIONS = new OpenOption[0];
     public static final LinkOption[] EMPTY_LINK_OPTIONS = new LinkOption[0];
     private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[] { 
LinkOption.NOFOLLOW_LINKS };
@@ -325,4 +325,27 @@ public class IoUtils {
         }
         return path;
     }
+
+    /**
+     * @param options The {@link LinkOption}s - OK if {@code null}/empty
+     * @return {@code true} if the link options are {@code null}/empty or do
+     * not contain {@link LinkOption#NOFOLLOW_LINKS}, {@code false} otherwise
+     * (i.e., the array is not empty and contains the special value)
+     */
+    public static boolean followLinks(LinkOption... options) {
+        if (GenericUtils.isEmpty(options)) {
+            return true;
+        }
+
+        for (LinkOption localLinkOption : options) {
+            if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    private IoUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index c3a3efa..70d729f 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -1254,6 +1254,10 @@ public class SftpSubsystem extends AbstractLoggingBean 
implements Command, Runna
         buffer.putString("versions");
         buffer.putString(all);
 
+        // TODO text-seek - see 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-13.txt
+        // TODO space-available - see 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt
+        // TODO home-directory - see 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt
+
         // supported
         buffer.putString("supported");
         buffer.putInt(5 * 4); // length of 5 integers
@@ -1297,6 +1301,7 @@ public class SftpSubsystem extends AbstractLoggingBean 
implements Command, Runna
         // extension-count
         buffer.putInt(0);
 
+        // TODO vendor-id see 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt
         /*
         buffer.putString("acl-supported");
         buffer.putInt(4);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java 
b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 4ec4891..85fa7dc 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -830,20 +830,20 @@ public class ClientTest extends BaseTestSupport {
 
         try {
             for (int index = 0; index < xformers.size(); index++) {
-                try(ClientSession session = 
client.connect(getCurrentTestName(), "localhost", port).verify(700L, 
TimeUnit.SECONDS).getSession()) {
+                try(ClientSession session = 
client.connect(getCurrentTestName(), "localhost", port).verify(7, 
TimeUnit.SECONDS).getSession()) {
                     String password = "bad-" + getCurrentTestName() + "-" + 
index;
                     session.addPasswordIdentity(password);
                     
                     AuthFuture future = session.auth();
-                    assertTrue("Failed to verify password=" + password + " in 
time", future.await(500L, TimeUnit.SECONDS));
+                    assertTrue("Failed to verify password=" + password + " in 
time", future.await(5L, TimeUnit.SECONDS));
                     assertFalse("Unexpected success for password=" + password, 
future.isSuccess());
                     session.removePasswordIdentity(password);
                 }
             }
 
-            try(ClientSession session = client.connect(getCurrentTestName(), 
"localhost", port).verify(700L, TimeUnit.SECONDS).getSession()) {
+            try(ClientSession session = client.connect(getCurrentTestName(), 
"localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
                 session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(500L, TimeUnit.SECONDS);
+                session.auth().verify(5L, TimeUnit.SECONDS);
                 assertTrue("Mismatched prompts evaluation results", 
mismatchedPrompts.isEmpty());
             }
         } finally {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
index fe3a123..ff909f8 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -25,6 +25,7 @@ import java.nio.channels.FileLock;
 import java.nio.channels.OverlappingFileLockException;
 import java.nio.file.DirectoryStream;
 import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
@@ -32,6 +33,7 @@ import java.nio.file.LinkOption;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileAttributeView;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.GroupPrincipal;
 import java.nio.file.attribute.PosixFilePermissions;
@@ -39,11 +41,10 @@ import java.nio.file.attribute.UserPrincipalLookupService;
 import java.nio.file.attribute.UserPrincipalNotFoundException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.apache.sshd.client.subsystem.sftp.SftpClient;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.file.root.RootedFileSystemProvider;
@@ -285,4 +286,28 @@ public class SftpFileSystemTest extends BaseTestSupport {
             System.out.println("Created " + dir);
         }
     }
+
+    @Test
+    public void testFileStore() throws IOException {
+        try(FileSystem fs = FileSystems.newFileSystem(
+                URI.create("sftp://"; + getCurrentTestName() + ":" + 
getCurrentTestName() + "@localhost:" + port + "/"), 
Collections.<String,Object>emptyMap())) {
+            Iterable<FileStore> iter = fs.getFileStores();
+            assertTrue("Not a list", iter instanceof List<?>);
+            
+            List<FileStore> list = (List<FileStore>) iter;
+            assertEquals("Mismatched stores count", 1, list.size());
+            
+            FileStore store = list.get(0);
+            assertEquals("Mismatched type", SftpConstants.SFTP_SUBSYSTEM_NAME, 
store.type());
+            assertFalse("Read-only ?", store.isReadOnly());
+            
+            for (String name : SftpFileSystem.SUPPORTED_VIEWS) {
+                assertTrue("Unsupported view name: " + name, 
store.supportsFileAttributeView(name));
+            }
+            
+            for (Class<? extends FileAttributeView> type : 
SftpFileSystemProvider.SUPPORTED_VIEWS) {
+                assertTrue("Unsupported view type: " + type.getSimpleName(), 
store.supportsFileAttributeView(type));
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/95e307d9/sshd-core/src/test/java/org/apache/sshd/common/util/io/IoUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/util/io/IoUtilsTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/util/io/IoUtilsTest.java
new file mode 100644
index 0000000..41ac467
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/util/io/IoUtilsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.util.io;
+
+import java.nio.file.LinkOption;
+
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class IoUtilsTest extends BaseTestSupport {
+    public IoUtilsTest() {
+        super();
+    }
+
+    @Test
+    public void testFollowLinks() {
+        assertTrue("Null ?", IoUtils.followLinks((LinkOption[]) null));
+        assertTrue("Empty ?", IoUtils.followLinks(IoUtils.EMPTY_LINK_OPTIONS));
+        assertFalse("No-follow ?", 
IoUtils.followLinks(IoUtils.getLinkOptions(false)));
+    }
+}

Reply via email to