[ 
https://issues.apache.org/jira/browse/IO-885?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Peter De Maeyer updated IO-885:
-------------------------------
    Description: 
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 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, so I don't advise it.

  was:
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 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, so I don't advise it.


> PathUtils.copyDirectory ignores symlinks when 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
>            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 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, so I don't 
> advise it.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to