[ 
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 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.

  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 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 {{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.


> 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
>            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)

Reply via email to