blob: 82d9876cba375533f2e28d8286dd55ea61c7163a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.sftp.client.fs;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
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.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
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.ArrayList;
import java.util.Arrays;
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.atomic.AtomicInteger;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.channel.Channel;
import org.apache.sshd.common.channel.ChannelListener;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.ExceptionUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils.MapBuilder;
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.session.ServerSession;
import org.apache.sshd.sftp.SftpModuleProperties;
import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
import org.apache.sshd.sftp.client.SftpClientFactory;
import org.apache.sshd.sftp.client.SftpVersionSelector;
import org.apache.sshd.sftp.common.SftpConstants;
import org.apache.sshd.sftp.server.SftpEventListener;
import org.apache.sshd.sftp.server.SftpSubsystemEnvironment;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.apache.sshd.util.test.CommonTestSupportUtils;
import org.apache.sshd.util.test.CoreTestSupportUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.MatcherAssert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@SuppressWarnings("checkstyle:MethodCount")
public class SftpFileSystemTest extends AbstractSftpFilesSystemSupport {
private static final Logger LOG = LoggerFactory.getLogger(SftpFileSystemTest.class);
public SftpFileSystemTest() throws IOException {
super();
}
@Before
public void setUp() throws Exception {
setupServer();
}
@Test
public void testFileSystem() throws Exception {
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
testFileSystem(fs, ((SftpFileSystem) fs).getVersion());
}
}
@Test
public void testFileSystemWriteAppend() throws Exception {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
Path localFile = clientFolder.resolve("file.txt");
Files.write(localFile, "Hello".getBytes(StandardCharsets.UTF_8));
String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile);
Path remoteFile = fs.getPath(remFilePath);
assertHierarchyTargetFolderExists(remoteFile.getParent());
byte[] buf = new byte[32000];
for (int i = 0; i < buf.length; i++) {
buf[i] = (byte) i;
}
try (OutputStream out = Files.newOutputStream(remoteFile, StandardOpenOption.APPEND, StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
out.write(buf);
out.write(buf);
}
byte[] data = Files.readAllBytes(remoteFile);
assertEquals("Unexpected length", 64005, data.length);
assertArrayEquals("Hello".getBytes(StandardCharsets.UTF_8), Arrays.copyOf(data, 5));
for (int i = 5; i < buf.length; i++) {
assertEquals("Mismatched data at " + i, (byte) (i - 5), data[i]);
assertEquals("Mismatched data at " + (i + buf.length), (byte) (i - 5), data[i + buf.length]);
}
}
}
@Test // See GH-325
public void testDeleteLink() throws Exception {
// This test creates symbolic links.
Assume.assumeFalse(OsUtils.isWin32());
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
List<Path> toRemove = new ArrayList<>();
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
Path localFile = clientFolder.resolve("file.txt");
Files.write(localFile, "Hello".getBytes(StandardCharsets.UTF_8));
toRemove.add(localFile);
Path existingSymlink = clientFolder.resolve("existing.txt");
Files.createSymbolicLink(existingSymlink, localFile);
toRemove.add(existingSymlink);
String remExistingLink = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, existingSymlink);
Path remoteExistingLink = fs.getPath(remExistingLink);
assertHierarchyTargetFolderExists(remoteExistingLink.getParent());
assertTrue(Files.exists(remoteExistingLink));
assertTrue(Files.exists(remoteExistingLink, LinkOption.NOFOLLOW_LINKS));
assertTrue(Files.isSymbolicLink(remoteExistingLink));
Files.delete(remoteExistingLink);
assertTrue(Files.exists(localFile));
assertFalse(Files.exists(existingSymlink, LinkOption.NOFOLLOW_LINKS));
assertFalse(Files.exists(remoteExistingLink, LinkOption.NOFOLLOW_LINKS));
} finally {
for (Path p : toRemove) {
Files.deleteIfExists(p);
}
}
}
@Test // See GH-325
public void testDeleteNonexistingLink() throws Exception {
// This test creates symbolic links.
Assume.assumeFalse(OsUtils.isWin32());
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
List<Path> toRemove = new ArrayList<>();
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
Path nonExistingSymlink = clientFolder.resolve("nonexisting.txt");
Files.createSymbolicLink(nonExistingSymlink, clientFolder.resolve("gone.txt"));
toRemove.add(nonExistingSymlink);
String remNonExistingLink = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, nonExistingSymlink);
Path remoteNonExistingLink = fs.getPath(remNonExistingLink);
assertFalse(Files.exists(remoteNonExistingLink));
assertTrue(Files.exists(remoteNonExistingLink, LinkOption.NOFOLLOW_LINKS));
assertTrue(Files.isSymbolicLink(remoteNonExistingLink));
Files.delete(remoteNonExistingLink);
assertFalse(Files.exists(nonExistingSymlink, LinkOption.NOFOLLOW_LINKS));
assertFalse(Files.exists(remoteNonExistingLink, LinkOption.NOFOLLOW_LINKS));
} finally {
for (Path p : toRemove) {
Files.deleteIfExists(p);
}
}
}
@Test // See GH-325
public void testDeleteDirectoryLink() throws Exception {
// This test creates symbolic links.
Assume.assumeFalse(OsUtils.isWin32());
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
List<Path> toRemove = new ArrayList<>();
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
Path directory = clientFolder.resolve("subdir");
Files.createDirectory(directory);
toRemove.add(directory);
Path directorySymlink = clientFolder.resolve("dirlink");
Files.createSymbolicLink(directorySymlink, directory);
toRemove.add(directorySymlink);
String remDirectoryLink = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, directorySymlink);
Path remoteDirectoryLink = fs.getPath(remDirectoryLink);
assertTrue(Files.isDirectory(remoteDirectoryLink));
assertTrue(Files.exists(remoteDirectoryLink, LinkOption.NOFOLLOW_LINKS));
assertFalse(Files.isDirectory(remoteDirectoryLink, LinkOption.NOFOLLOW_LINKS));
assertTrue(Files.isSymbolicLink(remoteDirectoryLink));
Files.delete(remoteDirectoryLink);
assertTrue(Files.isDirectory(directory));
assertFalse(Files.exists(directorySymlink, LinkOption.NOFOLLOW_LINKS));
assertFalse(Files.exists(remoteDirectoryLink, LinkOption.NOFOLLOW_LINKS));
} finally {
for (Path p : toRemove) {
Files.deleteIfExists(p);
}
}
}
@Test // See GH-325
public void testDeleteNonexistingFile() throws Exception {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
String doesNotExist = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath,
clientFolder.resolve("neverExisted.txt"));
Path neverExisted = fs.getPath(doesNotExist);
assertFalse(Files.exists(neverExisted));
assertFalse(Files.deleteIfExists(neverExisted));
assertThrows(NoSuchFileException.class, () -> Files.delete(neverExisted));
}
}
private Map<String, Object> defaultOptions() {
return MapBuilder.<String, Object> builder()
.put(SftpModuleProperties.READ_BUFFER_SIZE.getName(), IoUtils.DEFAULT_COPY_SIZE)
.put(SftpModuleProperties.WRITE_BUFFER_SIZE.getName(), IoUtils.DEFAULT_COPY_SIZE).build();
}
private SshServer createIntermediaryServer(FileSystem fileSystem) throws IOException {
SshServer sshd = CoreTestSupportUtils.setupTestFullSupportServer(AbstractSftpClientTestSupport.class);
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
sshd.setFileSystemFactory(new FileSystemFactory() {
@Override
public Path getUserHomeDir(SessionContext session) throws IOException {
return null;
}
@Override
public FileSystem createFileSystem(SessionContext session) throws IOException {
return fileSystem;
}
});
sshd.start();
return sshd;
}
@Test // see SSHD-1217
public void testFileSystemListDirIndirect() throws Exception {
// Instrument the upstream server to verify what gets called there
SftpSubsystemFactory factory = (SftpSubsystemFactory) NamedResource.findByName(SftpConstants.SFTP_SUBSYSTEM_NAME,
String.CASE_INSENSITIVE_ORDER, sshd.getSubsystemFactories());
AtomicInteger statCount = new AtomicInteger();
AtomicInteger readDirCount = new AtomicInteger();
factory.addSftpEventListener(new SftpEventListener() {
@Override
public void received(ServerSession session, int type, int id) throws IOException {
switch (type) {
case SftpConstants.SSH_FXP_STAT:
case SftpConstants.SSH_FXP_LSTAT:
statCount.getAndIncrement();
break;
case SftpConstants.SSH_FXP_READDIR:
readDirCount.getAndIncrement();
break;
default:
break;
}
}
});
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
FileSystem secondHop = FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions());
assertTrue("Not an SftpFileSystem", secondHop instanceof SftpFileSystem);
SshServer intermediary = createIntermediaryServer(secondHop);
try (FileSystem fs = FileSystems.newFileSystem(
createFileSystemURI(getCurrentTestName(), intermediary.getPort(), Collections.emptyMap()), defaultOptions())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
// Create files
final int numberOfFiles = 2000;
for (int i = 1; i <= numberOfFiles; i++) {
Path localFile = clientFolder.resolve("file" + i + ".txt");
Files.createFile(localFile);
}
String remDirPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder);
Path remoteDir = fs.getPath(remDirPath);
assertHierarchyTargetFolderExists(remoteDir);
// Clear counters; verifying the remote dir calls (L)STAT
statCount.set(0);
assertEquals("READ_DIR should not have been called yet", 0, readDirCount.get());
SftpPath sftpPath = (SftpPath) remoteDir;
// Actual test starts here
int i = 0;
long start = System.currentTimeMillis();
try (SftpClient client = sftpPath.getFileSystem().getClient();
CloseableHandle dir = client.openDir(remDirPath)) {
for (SftpClient.DirEntry entry : client.listDir(dir)) {
i++;
assertNotNull(entry);
}
}
LOG.info(
"{}: directory listing with {} files from intermediary server took {}ms, got {} entries; upstream READDIR called {} times, (L)STAT called {} times",
getCurrentTestName(), numberOfFiles, System.currentTimeMillis() - start, i, readDirCount, statCount);
assertEquals(numberOfFiles + 2, i); // . and ..
assertTrue("Upstream server not called", readDirCount.get() > 0);
// The current implementation stats 3 times: once to detect whether the directory exists, is a directory,
// and is readable; once again for the "." entry, and the parent directory once for "..".
MatcherAssert.assertThat(
"Files.getAttributes() should have been called at most a few times for the directory itself",
statCount.get(), new BaseMatcher<Integer>() {
@Override
public boolean matches(Object item) {
return item instanceof Integer && ((Integer) item).intValue() < 4;
}
@Override
public void describeTo(Description description) {
description.appendText("smaller than 4");
}
});
// Repeat this a few times to get slightly more reliable timings
final int maxRepeats = 10;
long directTime = 0;
long indirectTime = 0;
for (int attempt = 0; attempt < maxRepeats; attempt++) {
// Now try the same directly at the upstream server
statCount.set(0);
readDirCount.set(0);
i = 0;
start = System.currentTimeMillis();
try (SftpClient client = ((SftpFileSystem) secondHop).getClient();
CloseableHandle dir = client.openDir(remDirPath)) {
for (SftpClient.DirEntry entry : client.listDir(dir)) {
i++;
assertNotNull(entry);
}
}
long elapsed = System.currentTimeMillis() - start;
directTime += elapsed;
assertTrue("Upstream server not called", readDirCount.get() > 0);
assertEquals("(L)STAT should not have been called on upstream server", 0, statCount.get());
LOG.info(
"{}: directory listing with {} files from upstream server took {}ms, got {} entries: READDIR called {} times, (L)STAT called {} times",
getCurrentTestName(), numberOfFiles, elapsed, i, readDirCount, statCount);
statCount.set(0);
readDirCount.set(0);
i = 0;
start = System.currentTimeMillis();
try (SftpClient client = sftpPath.getFileSystem().getClient();
CloseableHandle dir = client.openDir(remDirPath)) {
for (SftpClient.DirEntry entry : client.listDir(dir)) {
i++;
assertNotNull(entry);
}
}
elapsed = System.currentTimeMillis() - start;
indirectTime += elapsed;
assertTrue("Upstream server not called", readDirCount.get() > 0);
LOG.info(
"{}: directory listing with {} files from intermediary server took {}ms, got {} entries: READDIR called {} times, (L)STAT called {} times",
getCurrentTestName(), numberOfFiles, elapsed, i, readDirCount, statCount);
}
LOG.info("{}: average directory listing times: direct {}ms; indirect {}ms", getCurrentTestName(),
directTime / maxRepeats, indirectTime / maxRepeats);
} finally {
if (secondHop != null) {
secondHop.close();
}
if (intermediary != null) {
intermediary.stop(true);
}
}
}
@Test // SSHD-1220
public void testAttributeCache() throws Exception {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
MapBuilder.<String, Object> builder()
.put(SftpModuleProperties.READ_BUFFER_SIZE.getName(), IoUtils.DEFAULT_COPY_SIZE)
.put(SftpModuleProperties.WRITE_BUFFER_SIZE.getName(), IoUtils.DEFAULT_COPY_SIZE).build())) {
assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
assertHierarchyTargetFolderExists(clientFolder);
Path localFile = clientFolder.resolve("file.txt");
Files.write(localFile, "Hello".getBytes(StandardCharsets.UTF_8));
Path localFile2 = clientFolder.resolve("file2.txt");
Files.write(localFile2, "World".getBytes(StandardCharsets.UTF_8));
String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile);
Path remoteFile = fs.getPath(remFilePath);
assertHierarchyTargetFolderExists(remoteFile.getParent());
int n = 0;
try (DirectoryStream<Path> directory = Files.newDirectoryStream(remoteFile.getParent())) {
for (Path p : directory) {
n++;
assertTrue("Expected an SftpPath", p instanceof SftpPath);
SftpClient.Attributes cached = ((SftpPath) p).getAttributes();
assertNotNull("Path should have cached attributes", cached);
assertEquals("Unexpected size reported", 5, cached.getSize());
// Now modify the file and fetch attributes again
Files.write(p, "Bye".getBytes(StandardCharsets.UTF_8));
BasicFileAttributes attributes = Files.readAttributes(p, BasicFileAttributes.class);
assertNotEquals("Sizes should be different", attributes.size(), cached.getSize());
assertEquals("Unexpected size after modification", 3, attributes.size());
assertNull("Path should not have cached attributes anymore", ((SftpPath) p).getAttributes());
}
}
assertEquals("Unexpected number of files", 2, n);
// And again
List<Path> obtained = new ArrayList<>(2);
try (DirectoryStream<Path> directory = Files.newDirectoryStream(remoteFile.getParent())) {
for (Path p : directory) {
assertTrue("Expected an SftpPath", p instanceof SftpPath);
SftpClient.Attributes cached = ((SftpPath) p).getAttributes();
assertNotNull("Path should have cached attributes", cached);
assertEquals("Unexpected size reported", 3, cached.getSize());
obtained.add(p);
}
}
assertEquals("Unexpected number of files", 2, obtained.size());
// Now modify the files and fetch attributes again
for (Path p : obtained) {
Files.write(p, "Again".getBytes(StandardCharsets.UTF_8));
BasicFileAttributes attributes = Files.readAttributes(p, BasicFileAttributes.class);
// If this fails because the size is 3, we mistakenly got data from previously cached SFTP attributes
assertEquals("Unexpected file size reported via attributes", 5, attributes.size());
}
}
}
@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 = CommonTestSupportUtils.resolve(targetPath,
SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
CommonTestSupportUtils.deleteRecursive(lclSftp);
try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
MapBuilder.<String, Object> builder()
.put(SftpModuleProperties.READ_BUFFER_SIZE.getName(), SftpClient.MIN_READ_BUFFER_SIZE)
.put(SftpModuleProperties.WRITE_BUFFER_SIZE.getName(), SftpClient.MIN_WRITE_BUFFER_SIZE)
.build())) {
Path parentPath = targetPath.getParent();
Path clientFolder = lclSftp.resolve("client");
String remFilePath = CommonTestSupportUtils.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();
CommonTestSupportUtils.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 = CommonTestSupportUtils.resolve(targetPath,
SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
Files.createDirectories(lclSftp);
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 = CommonTestSupportUtils.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 testFileCopy() throws IOException {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME,
getClass().getSimpleName());
Files.createDirectories(lclSftp);
Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
Files.deleteIfExists(lclFile);
Path lclFile2 = lclSftp.resolve(getCurrentTestName() + ".txt2");
Files.deleteIfExists(lclFile2);
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 = CommonTestSupportUtils.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);
}
Path sibling = file.getParent().resolve(file.getFileName().toString() + '2');
Files.copy(file, sibling);
}
byte[] actual = Files.readAllBytes(lclFile);
assertArrayEquals("Mismatched persisted data", expected, actual);
actual = Files.readAllBytes(lclFile2);
assertArrayEquals("Mismatched copied data", expected, actual);
}
@Test
public void testFileWriteRead() throws IOException {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME,
getClass().getSimpleName());
Files.createDirectories(lclSftp);
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 = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
try (SftpClient client = ((SftpFileSystem) fs).getClient()) {
try (OutputStream out = client.write(remFilePath)) {
IoUtils.copy(new ByteArrayInputStream(expected), out);
}
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = client.read(remFilePath)) {
IoUtils.copy(in, out);
assertArrayEquals("Mismatched persisted data", expected, out.toByteArray());
}
}
}
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 {
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 = ExceptionUtils.accumulateException(err, e);
}
}
if (err != null) {
throw err;
}
}
}
@Test
public void testSftpVersionSelector() throws Exception {
AtomicInteger selected = new AtomicInteger(-1);
SftpVersionSelector selector = (session, initial, current, available) -> {
int value = initial
? current : GenericUtils.stream(available)
.mapToInt(Integer::intValue)
.filter(v -> v != current)
.max()
.orElseGet(() -> current);
selected.set(value);
return value;
};
try (ClientSession session = createAuthenticatedClientSession();
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);
}
}
@Test
public void testSessionNotClosed() throws Exception {
try (ClientSession session = createAuthenticatedClientSession()) {
List<Channel> channels = new ArrayList<>();
session.addChannelListener(new ChannelListener() {
@Override
public void channelOpenSuccess(Channel channel) {
channels.add(channel);
}
@Override
public void channelClosed(Channel channel, Throwable reason) {
channels.remove(channel);
}
});
SftpFileSystem fs = SftpClientFactory.instance().createSftpFileSystem(session);
try {
testFileSystem(fs, fs.getVersion());
} finally {
fs.close();
}
assertFalse("File system should not be open", fs.isOpen());
assertEquals("No open channels expected", "[]", channels.toString());
assertTrue("Non-owned session should still be open", session.isOpen());
}
}
@Test
public void testSessionClosed() throws Exception {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME,
getClass().getSimpleName());
Files.createDirectories(lclSftp);
Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
Files.deleteIfExists(lclFile);
byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")")
.getBytes(StandardCharsets.UTF_8);
ClientSession session;
FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap());
try {
assertTrue("Should be an SftpFileSystem", fs instanceof SftpFileSystem);
Path remotePath = fs.getPath(CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), lclFile));
Files.write(remotePath, expected);
session = ((SftpFileSystem) fs).getClientSession();
} finally {
fs.close();
}
byte[] actual = Files.readAllBytes(lclFile);
assertArrayEquals("Mismatched persisted data", expected, actual);
assertFalse("File system should not be open", fs.isOpen());
assertFalse("Owned session should not be open", session.isOpen());
}
@Test
public void testSessionRecreate() throws Exception {
Path targetPath = detectTargetFolder();
Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME,
getClass().getSimpleName());
Files.createDirectories(lclSftp);
Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
Files.deleteIfExists(lclFile);
byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")")
.getBytes(StandardCharsets.UTF_8);
ClientSession session;
FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap());
try {
assertTrue("Should be an SftpFileSystem", fs instanceof SftpFileSystem);
Path remotePath = fs.getPath(CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), lclFile));
Files.write(remotePath, "foo".getBytes(StandardCharsets.US_ASCII));
session = ((SftpFileSystem) fs).getClientSession();
session.close();
Files.write(remotePath, expected);
ClientSession session2 = ((SftpFileSystem) fs).getClientSession();
assertNotNull("Expected a session", session2);
assertNotSame("Expected different sessions", session, session2);
session = session2;
assertTrue("Second session should still be open", session.isOpen());
assertTrue("Recreated session should be owned by the file system",
session.getAttribute(SftpFileSystem.OWNED_SESSION));
} finally {
fs.close();
}
byte[] actual = Files.readAllBytes(lclFile);
assertArrayEquals("Mismatched persisted data", expected, actual);
assertFalse("File system should not be open", fs.isOpen());
assertFalse("Owned session should not be open", session.isOpen());
}
@Test
public void testFileSystemProviderServiceEntry() throws IOException {
Path configFile = CommonTestSupportUtils.resolve(detectSourcesFolder(),
MAIN_SUBFOLDER, "filtered-resources", "META-INF", "services", FileSystemProvider.class.getName());
assertTrue("Missing " + configFile, Files.exists(configFile));
boolean found = false;
try (InputStream stream = Files.newInputStream(configFile);
Reader r = new InputStreamReader(stream, StandardCharsets.UTF_8);
BufferedReader b = new BufferedReader(r)) {
for (String line = b.readLine(); line != null; line = b.readLine()) {
line = line.trim();
if (GenericUtils.isEmpty(line) || (line.charAt(0) == '#')) {
continue;
}
assertFalse("Multiple configurations: " + line, found);
assertEquals("Mismatched configuration", SftpFileSystemProvider.class.getName(), line);
found = true;
}
}
assertTrue("No configuration found", found);
}
}