Peter De Maeyer created IO-885:
----------------------------------
Summary: PathUtils.copyDirectory ignores symlinks with
NOFOLLOW_LINKS
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
Reporter: Peter De Maeyer
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
JUnit 5 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 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 such 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 {{CopyOption}} to allow
really fine-grained control over the behavior, but it would complicate matters
too much in a way that nobody understands it anymore.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)