You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2018/04/16 11:47:52 UTC
[05/30] mina-sshd git commit: [SSHD-815] Extract SFTP in its own
module
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/simple/SimpleSftpClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/simple/SimpleSftpClientTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/simple/SimpleSftpClientTest.java
new file mode 100644
index 0000000..d48a19d
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/simple/SimpleSftpClientTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.simple;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.EnumSet;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.Utils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SimpleSftpClientTest extends BaseSimpleClientTestSupport {
+ private final Path targetPath;
+ private final Path parentPath;
+ private final FileSystemFactory fileSystemFactory;
+ private SimpleSftpClient sftpClient;
+
+ public SimpleSftpClientTest() throws Exception {
+ targetPath = detectTargetFolder();
+ parentPath = targetPath.getParent();
+ fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.setFileSystemFactory(fileSystemFactory);
+ client.start();
+ sftpClient = new SimpleSftpClientImpl(simple);
+ }
+
+ @Test
+ public void testSessionClosedWhenClientClosed() throws Exception {
+ try (SftpClient sftp = login()) {
+ assertTrue("SFTP not open", sftp.isOpen());
+
+ Session session = sftp.getClientSession();
+ assertTrue("Session not open", session.isOpen());
+
+ sftp.close();
+ assertFalse("Session not closed", session.isOpen());
+ assertFalse("SFTP not closed", sftp.isOpen());
+ }
+ }
+
+ @Test
+ public void testSftpProxyCalls() throws Exception {
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Path clientFolder = assertHierarchyTargetFolderExists(lclSftp).resolve("client");
+ Path clientFile = clientFolder.resolve("file.txt");
+ String remoteFileDir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
+ String clientFileName = clientFile.getFileName().toString();
+ String remoteFilePath = remoteFileDir + "/" + clientFileName;
+
+ try (SftpClient sftp = login()) {
+ sftp.mkdir(remoteFileDir);
+
+ byte[] written = (getClass().getSimpleName() + "#" + getCurrentTestName() + IoUtils.EOL).getBytes(StandardCharsets.UTF_8);
+ try (SftpClient.CloseableHandle h = sftp.open(remoteFilePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+ sftp.write(h, 0L, written);
+
+ SftpClient.Attributes attrs = sftp.stat(h);
+ assertNotNull("No handle attributes", attrs);
+ assertEquals("Mismatched remote file size", written.length, attrs.getSize());
+ }
+
+ assertTrue("Remote file not created: " + clientFile, Files.exists(clientFile, IoUtils.EMPTY_LINK_OPTIONS));
+ byte[] local = Files.readAllBytes(clientFile);
+ assertArrayEquals("Mismatched remote written data", written, local);
+
+ try (SftpClient.CloseableHandle h = sftp.openDir(remoteFileDir)) {
+ boolean matchFound = false;
+ for (SftpClient.DirEntry entry : sftp.listDir(h)) {
+ String name = entry.getFilename();
+ if (clientFileName.equals(name)) {
+ matchFound = true;
+ break;
+ }
+ }
+
+ assertTrue("No directory entry found for " + clientFileName, matchFound);
+ }
+
+ sftp.remove(remoteFilePath);
+ assertFalse("Remote file not removed: " + clientFile, Files.exists(clientFile, IoUtils.EMPTY_LINK_OPTIONS));
+ }
+ }
+
+ private SftpClient login() throws IOException {
+ return sftpClient.sftpLogin(TEST_LOCALHOST, port, getCurrentTestName(), getCurrentTestName());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
new file mode 100644
index 0000000..629bbc9
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
@@ -0,0 +1,106 @@
+/*
+ * 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.Path;
+import java.util.Collections;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.JSchLogger;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpClientTestSupport extends BaseTestSupport {
+ protected static SshServer sshd;
+ protected static int port;
+ protected static SshClient client;
+
+ protected final FileSystemFactory fileSystemFactory;
+
+ protected AbstractSftpClientTestSupport() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+ }
+
+ @BeforeClass
+ public static void setupClientAndServer() throws Exception {
+ JSchLogger.init();
+ sshd = Utils.setupTestServer(AbstractSftpClientTestSupport.class);
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.start();
+ port = sshd.getPort();
+
+ client = Utils.setupTestClient(AbstractSftpClientTestSupport.class);
+ client.start();
+ }
+
+ @AfterClass
+ public static void tearDownClientAndServer() throws Exception {
+ if (sshd != null) {
+ try {
+ sshd.stop(true);
+ } finally {
+ sshd = null;
+ }
+ }
+
+ if (client != null) {
+ try {
+ client.stop();
+ } finally {
+ client = null;
+ }
+ }
+ }
+
+ protected void setupServer() throws Exception {
+ sshd.setFileSystemFactory(fileSystemFactory);
+ }
+
+ protected SftpClient createSftpClient(ClientSession session) throws IOException {
+ return SftpClientFactory.instance().createSftpClient(session);
+ }
+
+ protected SftpClient createSftpClient(ClientSession session, int selector) throws IOException {
+ return SftpClientFactory.instance().createSftpClient(session, selector);
+ }
+
+ protected static <E extends SftpClientExtension> E assertExtensionCreated(SftpClient sftp, Class<E> type) {
+ E instance = sftp.getExtension(type);
+ assertNotNull("Extension not created: " + type.getSimpleName(), instance);
+ assertTrue("Extension not supported: " + instance.getName(), instance.isSupported());
+ return instance;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
new file mode 100644
index 0000000..e5265d5
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.client.subsystem.sftp.impl.DefaultCloseableHandle;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class DefaultCloseableHandleTest extends BaseTestSupport {
+ public DefaultCloseableHandleTest() {
+ super();
+ }
+
+ @Test
+ public void testChannelBehavior() throws IOException {
+ final byte[] id = getCurrentTestName().getBytes(StandardCharsets.UTF_8);
+ SftpClient client = Mockito.mock(SftpClient.class);
+ Mockito.doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ Handle handle = (Handle) args[0];
+ assertArrayEquals("Mismatched closing handle", id, handle.getIdentifier());
+ return null;
+ }).when(client).close(ArgumentMatchers.any(Handle.class));
+
+ CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName(), id);
+ try {
+ assertTrue("Handle not initially open", handle.isOpen());
+ } finally {
+ handle.close();
+ }
+ assertFalse("Handle not marked as closed", handle.isOpen());
+ // make sure close was called
+ Mockito.verify(client).close(handle);
+ }
+
+ @Test
+ public void testCloseIdempotent() throws IOException {
+ SftpClient client = Mockito.mock(SftpClient.class);
+ final AtomicBoolean closeCalled = new AtomicBoolean(false);
+ Mockito.doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true));
+ return null;
+ }).when(client).close(ArgumentMatchers.any(Handle.class));
+
+ CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName(), getCurrentTestName().getBytes(StandardCharsets.UTF_8));
+ for (int index = 0; index < Byte.SIZE; index++) {
+ handle.close();
+ }
+
+ assertTrue("Close method not called", closeCalled.get());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
new file mode 100644
index 0000000..4b34f92
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.subsystem.sftp;
+
+/**
+ * Just a test class used to invoke {@link SftpCommand#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class SftpCommandMain {
+ private SftpCommandMain() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void main(String[] args) throws Exception {
+ SftpCommand.main(args);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
new file mode 100644
index 0000000..5dc9bcf
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -0,0 +1,494 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.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;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpFileSystemTest extends BaseTestSupport {
+ private static SshServer sshd;
+ private static int port;
+
+ private final FileSystemFactory fileSystemFactory;
+
+ public SftpFileSystemTest() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+ }
+
+ @BeforeClass
+ public static void setupServerInstance() throws Exception {
+ sshd = Utils.setupTestServer(SftpFileSystemTest.class);
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.start();
+ port = sshd.getPort();
+ }
+
+ @AfterClass
+ public static void tearDownServerInstance() throws Exception {
+ if (sshd != null) {
+ try {
+ sshd.stop(true);
+ } finally {
+ sshd = null;
+ }
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd.setFileSystemFactory(fileSystemFactory);
+ }
+
+ @Test
+ public void testFileSystem() throws Exception {
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
+ GenericUtils.<String, Object>mapBuilder()
+ .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
+ .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
+ .build())) {
+ assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+ testFileSystem(fs, ((SftpFileSystem) fs).getVersion());
+ }
+ }
+
+ @Test // see SSHD-578
+ public void testFileSystemURIParameters() throws Exception {
+ Map<String, Object> params = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ params.put("test-class-name", getClass().getSimpleName());
+ params.put("test-pkg-name", getClass().getPackage().getName());
+ params.put("test-name", getCurrentTestName());
+
+ int expectedVersion = (SftpSubsystemEnvironment.LOWER_SFTP_IMPL + SftpSubsystemEnvironment.HIGHER_SFTP_IMPL) / 2;
+ params.put(SftpFileSystemProvider.VERSION_PARAM, expectedVersion);
+ try (SftpFileSystem fs = (SftpFileSystem) FileSystems.newFileSystem(createDefaultFileSystemURI(params), Collections.<String, Object>emptyMap())) {
+ try (SftpClient sftpClient = fs.getClient()) {
+ assertEquals("Mismatched negotiated version", expectedVersion, sftpClient.getVersion());
+
+ Session session = sftpClient.getClientSession();
+ params.forEach((key, expected) -> {
+ if (SftpFileSystemProvider.VERSION_PARAM.equalsIgnoreCase(key)) {
+ return;
+ }
+
+ Object actual = session.getObject(key);
+ assertEquals("Mismatched value for param '" + key + "'", expected, actual);
+ });
+ }
+ }
+ }
+
+ @Test
+ public void testAttributes() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
+ GenericUtils.<String, Object>mapBuilder()
+ .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, SftpClient.MIN_READ_BUFFER_SIZE)
+ .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, SftpClient.MIN_WRITE_BUFFER_SIZE)
+ .build())) {
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file.txt"));
+ Path file = fs.getPath(remFilePath);
+ assertHierarchyTargetFolderExists(file.getParent());
+ Files.write(file, (getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
+
+ Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+ assertNotNull("No attributes read for " + file, attrs);
+
+ Files.setAttribute(file, "basic:size", 2L);
+ Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
+ Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
+
+ FileSystem fileSystem = file.getFileSystem();
+ try {
+ UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
+ GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
+ Files.setAttribute(file, "posix:group", group);
+ } catch (UserPrincipalNotFoundException e) {
+ // Also, according to the Javadoc:
+ // "Where an implementation does not support any notion of
+ // group then this method always throws UserPrincipalNotFoundException."
+ // Therefore we are lenient with this exception for Windows
+ if (OsUtils.isWin32()) {
+ System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testRootFileSystem() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path rootNative = targetPath.resolve("root").toAbsolutePath();
+ Utils.deleteRecursive(rootNative);
+ assertHierarchyTargetFolderExists(rootNative);
+
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
+ Path dir = assertHierarchyTargetFolderExists(fs.getPath("test/foo"));
+ outputDebugMessage("Created %s", dir);
+ }
+ }
+
+ @Test // see SSHD-697
+ public void testFileChannel() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
+ Files.deleteIfExists(lclFile);
+ byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8);
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
+ Path parentPath = targetPath.getParent();
+ String remFilePath = Utils.resolveRelativeRemotePath(parentPath, lclFile);
+ Path file = fs.getPath(remFilePath);
+
+ FileSystemProvider provider = fs.provider();
+ try (FileChannel fc = provider.newFileChannel(file, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
+ int writeLen = fc.write(ByteBuffer.wrap(expected));
+ assertEquals("Mismatched written length", expected.length, writeLen);
+
+ FileChannel fcPos = fc.position(0L);
+ assertSame("Mismatched positioned file channel", fc, fcPos);
+
+ byte[] actual = new byte[expected.length];
+ int readLen = fc.read(ByteBuffer.wrap(actual));
+ assertEquals("Mismatched read len", writeLen, readLen);
+ assertArrayEquals("Mismatched read data", expected, actual);
+ }
+ }
+
+ byte[] actual = Files.readAllBytes(lclFile);
+ assertArrayEquals("Mismatched persisted data", expected, actual);
+ }
+
+ @Test
+ public void testFileStore() throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.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 : fs.supportedFileAttributeViews()) {
+ assertTrue("Unsupported view name: " + name, store.supportsFileAttributeView(name));
+ }
+
+ for (Class<? extends FileAttributeView> type : SftpFileSystemProvider.UNIVERSAL_SUPPORTED_VIEWS) {
+ assertTrue("Unsupported view type: " + type.getSimpleName(), store.supportsFileAttributeView(type));
+ }
+ }
+ }
+
+ @Test
+ public void testMultipleFileStoresOnSameProvider() throws IOException {
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
+ Collection<SftpFileSystem> fsList = new LinkedList<>();
+ try {
+ Collection<String> idSet = new HashSet<>();
+ Map<String, Object> empty = Collections.emptyMap();
+ for (int index = 0; index < 4; index++) {
+ String credentials = getCurrentTestName() + "-user-" + index;
+ SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
+ fsList.add(expected);
+
+ String id = expected.getId();
+ assertTrue("Non unique file system id: " + id, idSet.add(id));
+
+ SftpFileSystem actual = provider.getFileSystem(id);
+ assertSame("Mismatched cached instances for " + id, expected, actual);
+ outputDebugMessage("Created file system id: %s", id);
+ }
+
+ for (SftpFileSystem fs : fsList) {
+ String id = fs.getId();
+ fs.close();
+ assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
+ }
+ } finally {
+ IOException err = null;
+ for (FileSystem fs : fsList) {
+ try {
+ fs.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+ }
+
+ client.stop();
+
+ if (err != null) {
+ throw err;
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testSftpVersionSelector() throws Exception {
+ final AtomicInteger selected = new AtomicInteger(-1);
+ SftpVersionSelector selector = (session, current, available) -> {
+ int value = GenericUtils.stream(available)
+ .mapToInt(Integer::intValue)
+ .filter(v -> v != current)
+ .max()
+ .orElseGet(() -> current);
+ selected.set(value);
+ return value;
+ };
+
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (FileSystem fs = createSftpFileSystem(session, selector)) {
+ assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+ Collection<String> views = fs.supportedFileAttributeViews();
+ assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
+ views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
+ int expectedVersion = selected.get();
+ assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
+ testFileSystem(fs, expectedVersion);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ private FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
+ return SftpClientFactory.instance().createSftpFileSystem(session, selector);
+ }
+
+ private void testFileSystem(FileSystem fs, int version) throws Exception {
+ Iterable<Path> rootDirs = fs.getRootDirectories();
+ for (Path root : rootDirs) {
+ String rootName = root.toString();
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+ for (Path child : ds) {
+ String name = child.getFileName().toString();
+ assertNotEquals("Unexpected dot name", ".", name);
+ assertNotEquals("Unexpected dotdot name", "..", name);
+ outputDebugMessage("[%s] %s", rootName, child);
+ }
+ } catch (IOException | RuntimeException e) {
+ // TODO on Windows one might get share problems for *.sys files
+ // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
+ // for now, Windows is less of a target so we are lenient with it
+ if (OsUtils.isWin32()) {
+ System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+
+ Path current = fs.getPath(".").toRealPath().normalize();
+ outputDebugMessage("CWD: %s", current);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-1.txt"));
+ Path file1 = fs.getPath(remFile1Path);
+ assertHierarchyTargetFolderExists(file1.getParent());
+
+ String expected = "Hello world: " + getCurrentTestName();
+ outputDebugMessage("Write initial data to %s", file1);
+ Files.write(file1, expected.getBytes(StandardCharsets.UTF_8));
+ String buf = new String(Files.readAllBytes(file1), StandardCharsets.UTF_8);
+ assertEquals("Mismatched read test data", expected, buf);
+
+ if (version >= SftpConstants.SFTP_V4) {
+ outputDebugMessage("getFileAttributeView(%s)", file1);
+ AclFileAttributeView aclView = Files.getFileAttributeView(file1, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
+ assertNotNull("No ACL view for " + file1, aclView);
+
+ Map<String, ?> attrs = Files.readAttributes(file1, "acl:*", LinkOption.NOFOLLOW_LINKS);
+ outputDebugMessage("readAttributes(%s) %s", file1, attrs);
+ assertEquals("Mismatched owner for " + file1, aclView.getOwner(), attrs.get("owner"));
+
+ @SuppressWarnings("unchecked")
+ List<AclEntry> acl = (List<AclEntry>) attrs.get("acl");
+ outputDebugMessage("acls(%s) %s", file1, acl);
+ assertListEquals("Mismatched ACLs for " + file1, aclView.getAcl(), acl);
+ }
+
+ String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt"));
+ Path file2 = fs.getPath(remFile2Path);
+ String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-3.txt"));
+ Path file3 = fs.getPath(remFile3Path);
+ try {
+ outputDebugMessage("Move with failure expected %s => %s", file2, file3);
+ Files.move(file2, file3, LinkOption.NOFOLLOW_LINKS);
+ fail("Unexpected success in moving " + file2 + " => " + file3);
+ } catch (NoSuchFileException e) {
+ // expected
+ }
+
+ Files.write(file2, "h".getBytes(StandardCharsets.UTF_8));
+ try {
+ outputDebugMessage("Move with failure expected %s => %s", file1, file2);
+ Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS);
+ fail("Unexpected success in moving " + file1 + " => " + file2);
+ } catch (FileAlreadyExistsException e) {
+ // expected
+ }
+
+ outputDebugMessage("Move with success expected %s => %s", file1, file2);
+ Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
+ outputDebugMessage("Move with success expected %s => %s", file2, file1);
+ Files.move(file2, file1, LinkOption.NOFOLLOW_LINKS);
+
+ Map<String, Object> attrs = Files.readAttributes(file1, "*");
+ outputDebugMessage("%s attributes: %s", file1, attrs);
+
+ // TODO there are many issues with symbolic links on Windows
+ if (OsUtils.isUNIX()) {
+ Path link = fs.getPath(remFile2Path);
+ Path linkParent = link.getParent();
+ Path relPath = linkParent.relativize(file1);
+ outputDebugMessage("Create symlink %s => %s", link, relPath);
+ Files.createSymbolicLink(link, relPath);
+ assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+
+ Path symLink = Files.readSymbolicLink(link);
+ assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+
+ outputDebugMessage("Delete symlink %s", link);
+ Files.delete(link);
+ }
+
+ attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+ outputDebugMessage("%s no-follow attributes: %s", file1, attrs);
+ assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1), StandardCharsets.UTF_8));
+
+ try (FileChannel channel = FileChannel.open(file1)) {
+ try (FileLock lock = channel.lock()) {
+ outputDebugMessage("Lock %s: %s", file1, lock);
+
+ try (FileChannel channel2 = FileChannel.open(file1)) {
+ try (FileLock lock2 = channel2.lock()) {
+ fail("Unexpected success in re-locking " + file1 + ": " + lock2);
+ } catch (OverlappingFileLockException e) {
+ // expected
+ }
+ }
+ }
+ }
+
+ Files.delete(file1);
+ }
+
+ private URI createDefaultFileSystemURI() {
+ return createDefaultFileSystemURI(Collections.emptyMap());
+ }
+
+ private URI createDefaultFileSystemURI(Map<String, ?> params) {
+ return createFileSystemURI(getCurrentTestName(), params);
+ }
+
+ private URI createFileSystemURI(String username, Map<String, ?> params) {
+ return createFileSystemURI(username, port, params);
+ }
+
+ private static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
+ return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
+ }
+}