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))); + } +}
