[
https://issues.apache.org/jira/browse/IO-885?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18053988#comment-18053988
]
Elliotte Rusty Harold commented on IO-885:
------------------------------------------
There's more than one reasonable approach here. I doubt any one choice will
satisfy everyone all the time. Whichever path is chosen, two things are
mandatory:
1. Provide thorough tests
2. Document it exhaustively
and of course make sure the tests match the documentation. The real problem is
that until now we've had neither. Random implementations just sort of happened
without conscious choice, tests, or documentation.
> PathUtils.copyDirectory with NOFOLLOW_LINKS ignores symlinks
> ------------------------------------------------------------
>
> Key: IO-885
> URL: https://issues.apache.org/jira/browse/IO-885
> Project: Commons IO
> Issue Type: Bug
> Components: Utilities
> Affects Versions: 2.20.0, 2.21.0
> Reporter: Peter De Maeyer
> Priority: Major
>
> Before 2.20.0, {{PathUtils.copyDirectory}} with {{NOFOLLOW_LINKS}} preserved
> symlinks.{^}(1)^ This supported the common use case of making an exact 1:1
> copy of a self-contained directory tree including symlinks, such as for
> example a {{java/}} installation of OpenJDK 17. This is illustrated by the
> following test:
> {code:java}
> import static java.nio.file.Files.createDirectory;
> import static java.nio.file.Files.createFile;
> import static java.nio.file.Files.createSymbolicLink;
> import static java.nio.file.Files.exists;
> import static java.nio.file.Files.isSymbolicLink;
> import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
> import static org.apache.commons.io.file.PathUtils.copyDirectory;
> import static org.junit.jupiter.api.Assertions.assertTrue;
>
> import java.nio.file.Path;
>
> import org.junit.jupiter.api.Test;
> import org.junit.jupiter.api.io.TempDir;
>
> class PathUtilsTest {
> @Test
> void copyDirectoryPreservesSymlinks(@TempDir Path tempDir) throws
> Exception {
> Path sourceDir = createDirectory(tempDir.resolve("source"));
> Path dir = createDirectory(sourceDir.resolve("dir"));
> Path dirLink = createSymbolicLink(sourceDir.resolve("link-to-dir"),
> dir);
> Path file = createFile(dir.resolve("file"));
> Path fileLink = createSymbolicLink(dir.resolve("link-to-file"), file);
> Path targetDir = tempDir.resolve("target");
> copyDirectory(sourceDir, targetDir, NOFOLLOW_LINKS);
> Path copyOfDir = targetDir.resolve("dir");
> assertTrue(exists(copyOfDir));
> Path copyOfDirLink = targetDir.resolve("link-to-dir");
> assertTrue(exists(copyOfDirLink));
> assertTrue(isSymbolicLink(copyOfDirLink));
> Path copyOfFileLink = copyOfDir.resolve("link-to-file");
> assertTrue(exists(copyOfFileLink));
> assertTrue(isSymbolicLink(copyOfFileLink));
> }
> }
> {code}
> This behavior changed in 2.20.0, so that it is now _impossible_ to make a 1:1
> copy whilst preserving symlinks, no matter what copy or link options you try.
> The above test thus fails.
> Explaining it in words, given a {{source/}} directory tree:
> {noformat}
> source/
> dir/
> file
> symlink-to-file
> symlink-to-dir
> {noformat}
> it was possible to copy it using {{NOFOLLOW_SYMLINKS}} to:
> {noformat}
> target/
> dir/
> file
> symlink-to-file
> symlink-to-dir
> {noformat}
> That is expected and intuitive behavior.
> Since 2.20.0 that behavior broke and now results in the following:
> {noformat}
> target/
> dir/
> file
> {noformat}
> Notice the missing symlinks.
> I didn't try, but I suspect the same applies to non-symbolic (hard) links.
> I consider this a regression that needs to be fixed.
> This issue is related to IO-845, which is not settled yet thus inconclusive.
> The behavior is undefined. That is a pity, I hereby request to _define_ that
> behavior so that there is at least a way to copy preserving symlinks. It
> essentially boils down to Eliotte's analysis in
> [this|https://issues.apache.org/jira/browse/IO-845?focusedCommentId=17813679&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-17813679]
> comment, with the additional remark that it should be dependent on the
> {{NOFOLLOW_LINKS}} option:
> * When {{NOFOLLOW_LINKS}}: then behave as Eliotte's option 2. In this case,
> circular (sym)links are handled gracefully, because they're just preserved as
> circular symlinks on the target.
> * When no option, then the behavior implicitly means "follow symlinks". Then
> behave as "going down the rabbit hole", flattenig/resolving symlinks into
> their actual content on the target. That (sort of) addresses [Gary's
> comment|https://issues.apache.org/jira/browse/IO-845?focusedCommentId=17830158&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-17830158]
> I think. This option has the drawback that in case of circular symlinks, you
> may end up in an endless recursive loop, but hey, that's a consequence what
> you asked for so acceptable IMO.{^}(2)^
> ----
> ^(1)^ _Not_ specifying {{NOFOLLOW_LINKS}} resulted in "going down the rabbit
> hole" and copying the linked content instead of the link, which is usually
> _not_ what you want, but anyway, that's the behavior that the JDK chose as
> default when they made the implicit "follow symlinks" the default and
> {{NOFOLLOW_LINKS}} the explicit option. Not a choice I particularly agree
> with, but I can understand it was made for portability reasons where the
> target directory may be on a file system that does not support symlinks.
> ^(2)^ Alternatively, we could define additional custom {{CopyOptions}} or
> {{LinkOptions}} to allow more fine-grained control over the behavior, but it
> would complicate matters too much, also in terms of having to define the
> semantic of all combinations of options. It would become too difficult to
> understand, so I don't advise it.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)