This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push: new fbc5d18 [SSHD-1102] Provide filter support for SftpDirectoryStream fbc5d18 is described below commit fbc5d187bb2d792f57bf584e2f1c3922e4fd1444 Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Thu Nov 12 11:38:40 2020 +0200 [SSHD-1102] Provide filter support for SftpDirectoryStream --- CHANGES.md | 2 + .../sshd/sftp/client/fs/SftpDirectoryStream.java | 55 ++++++- .../sftp/client/fs/SftpFileSystemProvider.java | 2 +- .../sshd/sftp/client/fs/SftpPathIterator.java | 70 +++++++-- .../client/fs/AbstractSftpFilesSystemSupport.java | 165 +++++++++++++++++++++ .../sftp/client/fs/SftpDirectoryScannersTest.java | 75 ++++++++++ .../sshd/sftp/client/fs/SftpFileSystemTest.java | 136 ----------------- 7 files changed, 349 insertions(+), 156 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d6d1370..0c8196a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -60,3 +60,5 @@ or `-key-file` command line option. * [SSHD-1066](https://issues.apache.org/jira/browse/SSHD-1066) Allow multiple binding to local port tunnel on different addresses * [SSHD-1070](https://issues.apache.org/jira/browse/SSHD-1070) OutOfMemoryError when use async port forwarding * [SSHD-1100](https://issues.apache.org/jira/browse/SSHD-1100) Updated used moduli for DH group KEX +* [SSHD-1102](https://issues.apache.org/jira/browse/SSHD-1102) Provide filter support for SftpDirectoryStream + diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpDirectoryStream.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpDirectoryStream.java index fab00fd..c53625b 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpDirectoryStream.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpDirectoryStream.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.util.Iterator; +import java.util.Objects; import org.apache.sshd.sftp.client.SftpClient; @@ -31,19 +32,36 @@ import org.apache.sshd.sftp.client.SftpClient; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public class SftpDirectoryStream implements DirectoryStream<Path> { + protected SftpPathIterator pathIterator; + + private final SftpPath path; + private final Filter<? super Path> filter; private final SftpClient sftp; - private final Iterable<SftpClient.DirEntry> iter; - private final SftpPath p; /** * @param path The remote {@link SftpPath} * @throws IOException If failed to initialize the directory access handle */ public SftpDirectoryStream(SftpPath path) throws IOException { + this(path, null); + } + + /** + * + * @param path The remote {@link SftpPath} + * @param filter An <U>optional</U> {@link java.nio.file.DirectoryStream.Filter filter} - ignored if + * {@code null} + * @throws IOException If failed to initialize the directory access handle + */ + public SftpDirectoryStream(SftpPath path, Filter<? super Path> filter) throws IOException { + this.path = Objects.requireNonNull(path, "No path specified"); + this.filter = filter; + SftpFileSystem fs = path.getFileSystem(); - p = path; sftp = fs.getClient(); - iter = sftp.readDir(path.toString()); + + Iterable<SftpClient.DirEntry> iter = sftp.readDir(path.toString()); + pathIterator = new SftpPathIterator(getRootPath(), iter, getFilter()); } /** @@ -55,9 +73,36 @@ public class SftpDirectoryStream implements DirectoryStream<Path> { return sftp; } + /** + * @return The root {@link SftpPath} for this directory stream + */ + public final SftpPath getRootPath() { + return path; + } + + /** + * @return The original filter - may be {@code null} to indicate no filter + */ + public final Filter<? super Path> getFilter() { + return filter; + } + @Override public Iterator<Path> iterator() { - return new SftpPathIterator(p, iter); + if (!sftp.isOpen()) { + throw new IllegalStateException("Stream has been closed"); + } + + /* + * According to documentation this method can be called only once + */ + if (pathIterator == null) { + throw new IllegalStateException("Iterator has already been consumed"); + } + + Iterator<Path> iter = pathIterator; + pathIterator = null; + return iter; } @Override diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java index e1645c2..f4b62b6 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java @@ -520,7 +520,7 @@ public class SftpFileSystemProvider extends FileSystemProvider { @Override public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException { final SftpPath p = toSftpPath(dir); - return new SftpDirectoryStream(p); + return new SftpDirectoryStream(p, filter); } @Override diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java index 259dd06..d34a66d 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java @@ -19,30 +19,65 @@ package org.apache.sshd.sftp.client.fs; +import java.io.IOException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryStream.Filter; import java.nio.file.Path; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import org.apache.sshd.sftp.client.SftpClient; /** + * Implements and {@link Iterator} of {@link SftpPath}-s returned by a {@link DirectoryStream#iterator()} method. + * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public class SftpPathIterator implements Iterator<Path> { - private final SftpPath p; - private final Iterator<? extends SftpClient.DirEntry> it; - private boolean dotIgnored; - private boolean dotdotIgnored; - private SftpClient.DirEntry curEntry; + protected final Iterator<? extends SftpClient.DirEntry> it; + protected boolean dotIgnored; + protected boolean dotdotIgnored; + protected SftpPath curEntry; + + private final SftpPath path; + private DirectoryStream.Filter<? super Path> filter; public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter) { - this(path, (iter == null) ? null : iter.iterator()); + this(path, iter, null); + } + + public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter, + DirectoryStream.Filter<? super Path> filter) { + this(path, (iter == null) ? null : iter.iterator(), filter); } public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter) { - p = path; + this(path, iter, null); + } + + public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter, + DirectoryStream.Filter<? super Path> filter) { + this.path = Objects.requireNonNull(path, "No root path provided"); + this.filter = filter; + it = iter; - curEntry = nextEntry(); + curEntry = nextEntry(path, filter); + } + + /** + * @return The root {@link SftpPath} for this directory iterator + */ + public final SftpPath getRootPath() { + return path; + } + + /** + * @return The original filter - may be {@code null} to indicate no filter + */ + public final Filter<? super Path> getFilter() { + return filter; } @Override @@ -56,12 +91,12 @@ public class SftpPathIterator implements Iterator<Path> { throw new NoSuchElementException("No next entry"); } - SftpClient.DirEntry entry = curEntry; - curEntry = nextEntry(); - return p.resolve(entry.getFilename()); + SftpPath returnValue = curEntry; + curEntry = nextEntry(getRootPath(), getFilter()); + return returnValue; } - private SftpClient.DirEntry nextEntry() { + protected SftpPath nextEntry(SftpPath root, DirectoryStream.Filter<? super Path> selector) { while ((it != null) && it.hasNext()) { SftpClient.DirEntry entry = it.next(); String name = entry.getFilename(); @@ -70,7 +105,14 @@ public class SftpPathIterator implements Iterator<Path> { } else if ("..".equals(name) && (!dotdotIgnored)) { dotdotIgnored = true; } else { - return entry; + SftpPath candidate = root.resolve(entry.getFilename()); + try { + if ((selector == null) || selector.accept(candidate)) { + return candidate; + } + } catch (IOException e) { + throw new DirectoryIteratorException(e); + } } } @@ -79,6 +121,6 @@ public class SftpPathIterator implements Iterator<Path> { @Override public void remove() { - throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A"); + throw new UnsupportedOperationException("newDirectoryStream(" + getRootPath() + ") Iterator#remove() N/A"); } } diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java index 7ede88f..b516290 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java @@ -21,14 +21,31 @@ package org.apache.sshd.sftp.client.fs; import java.io.IOException; import java.net.URI; +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.FileSystem; +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.attribute.AclEntry; +import java.nio.file.attribute.AclFileAttributeView; import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.util.OsUtils; import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport; 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.util.test.CommonTestSupportUtils; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> @@ -38,6 +55,154 @@ public abstract class AbstractSftpFilesSystemSupport extends AbstractSftpClientT super(); } + protected void testFileSystem(FileSystem fs, int version) throws Exception { + testRootDirs(fs); + + Path targetPath = detectTargetFolder(); + Path lclSftp = CommonTestSupportUtils.resolve(targetPath, + SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); + CommonTestSupportUtils.deleteRecursive(lclSftp); + + Path current = fs.getPath(".").toRealPath().normalize(); + outputDebugMessage("CWD: %s", current); + + Path parentPath = targetPath.getParent(); + Path clientFolder = lclSftp.resolve("client"); + String remFile1Path = CommonTestSupportUtils.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) { + testAclFileAttributeView(file1); + } + + String remFile2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt")); + Path file2 = fs.getPath(remFile2Path); + String remFile3Path = CommonTestSupportUtils.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); + + testSymbolicLinks(link, relPath); + } + + 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)); + + testFileChannelLock(file1); + + Files.delete(file1); + } + + protected static Iterable<Path> testRootDirs(FileSystem fs) throws IOException { + 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; + } + } + } + + return rootDirs; + } + + protected static AclFileAttributeView testAclFileAttributeView(Path file) throws IOException { + outputDebugMessage("getFileAttributeView(%s)", file); + AclFileAttributeView aclView + = Files.getFileAttributeView(file, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + assertNotNull("No ACL view for " + file, aclView); + + Map<String, ?> attrs = Files.readAttributes(file, "acl:*", LinkOption.NOFOLLOW_LINKS); + outputDebugMessage("readAttributes(%s) %s", file, attrs); + assertEquals("Mismatched owner for " + file, aclView.getOwner(), attrs.get("owner")); + + @SuppressWarnings("unchecked") + List<AclEntry> acl = (List<AclEntry>) attrs.get("acl"); + outputDebugMessage("acls(%s) %s", file, acl); + assertListEquals("Mismatched ACLs for " + file, aclView.getAcl(), acl); + + return aclView; + } + + protected static void testSymbolicLinks(Path link, Path relPath) throws IOException { + 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); + } + + protected static void testFileChannelLock(Path file) throws IOException { + try (FileChannel channel = FileChannel.open(file)) { + try (FileLock lock = channel.lock()) { + outputDebugMessage("Lock %s: %s", file, lock); + + try (FileChannel channel2 = FileChannel.open(file)) { + try (FileLock lock2 = channel2.lock()) { + fail("Unexpected success in re-locking " + file + ": " + lock2); + } catch (OverlappingFileLockException e) { + // expected + } + } + } + } + } + protected static FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException { return SftpClientFactory.instance().createSftpFileSystem(session, selector); } diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java index 12ee16b..995eb35 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java @@ -22,11 +22,13 @@ package org.apache.sshd.sftp.client.fs; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -91,6 +93,79 @@ public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport { testSftpClientDirectoryScanner(setupFileSuffixMatching(), "*.txt"); } + @Test // see SSHD-1102 + public void testDirectoryStreamFilter() throws IOException { + SetupDetails details = setupFileSuffixMatching(); + List<Path> expected = details.getExpected(); + List<Path> actual = new ArrayList<>(); + try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) { + DirectoryStream.Filter<Path> filter = p -> { + if (Files.isDirectory(p)) { + return true; + } + + if (!Files.isRegularFile(p)) { + return false; + } + + return p.getFileName().toString().endsWith(".txt"); + }; + collectMatchingFiles(fs.getPath(details.getRemoteFilePath()), filter, actual); + } + + Collections.sort(actual); + + assertListEquals(getCurrentTestName(), expected, actual, PathUtils.EQ_CASE_SENSITIVE_FILENAME); + } + + private static void collectMatchingFiles( + Path dir, DirectoryStream.Filter<? super Path> filter, Collection<Path> matches) + throws IOException { + try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, filter)) { + for (Path p : ds) { + assertTrue("Unfiltered path: " + p, filter.accept(p)); + + if (Files.isDirectory(p)) { + collectMatchingFiles(p, filter, matches); + } else if (Files.isRegularFile(p)) { + matches.add(p); + } + } + } + } + + @Test(expected = IllegalStateException.class) + public void testClosedDirectoryStreamIteration() throws IOException { + SetupDetails details = setupDeepScanning(); + try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) { + Path dir = fs.getPath(details.getRemoteFilePath()); + try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) { + ds.close(); + + for (Path p : ds) { + fail("Unexpected iterated path: " + p); + } + } + } + } + + @Test(expected = IllegalStateException.class) + public void testDirectoryStreamRepeatedIteration() throws IOException { + SetupDetails details = setupDeepScanning(); + try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) { + Path dir = fs.getPath(details.getRemoteFilePath()); + try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) { + for (Path p : ds) { + assertNotNull(p); + } + + for (Path p : ds) { + fail("Unexpected iterated path: " + p); + } + } + } + } + private void testSftpClientDirectoryScanner(SetupDetails setup, String pattern) throws IOException { List<Path> expected = setup.getExpected(); String remRoot = setup.getRemoteFilePath(); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java index 94727ca..12552ed 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java @@ -26,22 +26,13 @@ import java.io.Reader; 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; @@ -341,131 +332,4 @@ public class SftpFileSystemTest extends AbstractSftpFilesSystemSupport { assertTrue("No configuration found", found); } - - 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 = CommonTestSupportUtils.resolve(targetPath, - SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - CommonTestSupportUtils.deleteRecursive(lclSftp); - - Path current = fs.getPath(".").toRealPath().normalize(); - outputDebugMessage("CWD: %s", current); - - Path parentPath = targetPath.getParent(); - Path clientFolder = lclSftp.resolve("client"); - String remFile1Path = CommonTestSupportUtils.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 = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt")); - Path file2 = fs.getPath(remFile2Path); - String remFile3Path = CommonTestSupportUtils.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); - } }