tresf
tresf

Reputation: 7922

Apache commons-io copy and inherit target permissions

On Windows, when using the [email protected] class FileUtils to copy a directory (e.g. FileUtils.copyDirectory(...), the behavior differs from that of Windows default copy behavior, quoting (microsoft.com):

"By default, an object inherits permissions from its parent object, either at the time of creation or when it is copied or moved to its parent folder. The only exception to this rule occurs when you move an object to a different folder on the same volume. In this case, the original permissions are retained."

Expected behavior:

Actual behavior:

The reason this is important is that Windows historically manages these; setting them manually would be prone to even more potential errors. Thus, without the ability to inherit target permissions, this method does not serve the most common-use purpose on Windows.

How do I restore the old behavior of inheriting the target permissions without downgrading the commons-io library?

This seems to be a regression in behavior:

Version Status
[email protected] (2005) ✅ Inherits permissions of destination
[email protected] (2013) ✅ Inherits permissions of destination
[email protected] (2016) ✅ Inherits permissions of destination
[email protected] (2017) ✅ Inherits permissions of destination
[email protected] (2020) ✅ Inherits permissions of destination
[email protected] (2020) ✅ Inherits permissions of destination
[email protected] (2021) 🚫 Does not inherit permissions of destination
[email protected] (2021) 🚫 Does not inherit permissions of destination
[email protected] (2021) 🚫 Does not inherit permissions of destination

For example, if copying to C:\Program Files

Version Permissions (icals <file>)
[email protected] NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)
BUILTIN\Users:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(I)(RX)
[email protected] NT AUTHORITY\SYSTEM:(F)
BUILTIN\Administrators:(F)
WIN10ARM\owner:(F)

Sample code:

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class Main {
    private static final String PROGRAM_FILES = System.getenv().get("ProgramFiles");
    public static void main(String ... args) throws IOException {
        // Prepare our folder
        File folder = new File("MyApp");
        folder.mkdirs();
        File file = new File(folder, "test.txt");
        file.createNewFile();

        // Use FileUtils to copy it elsewhere
        File parent = new File(PROGRAM_FILES);
        File dest = new File(parent, "MyApp");
        System.out.println(String.format("Creating parent folder %s...", dest));
        dest.mkdirs();
        System.out.println(String.format("Copying contents %s to %s...", folder, dest));
        FileUtils.copyDirectory(folder, dest);

        // To test permissions call:
        //    icacls "%ProgramFiles%\MyApp\test.txt"
    }
}

To compile the code:

javac -cp lib\commons-io-2.8.jar src\Main.java

To run the code (open a CMD as administrator):

java -cp lib/commons-io-2.8.jar;src Main

To test permissions:

icacls "%ProgramFiles%\MyApp\test.txt"

Then manually delete %ProgramFiles%\MyApp and repeat for commons-io-2.9.0.jar.

Similar questions have been asked before, but I could not find an explanation for this behavior.

Apache commons-io Mailing list discussion:

Related:

A possible workaround, but much more complex:

Upvotes: 2

Views: 1310

Answers (3)

tresf
tresf

Reputation: 7922

The workaround for commons-io 2.9.0 and higher is:

- FileUtils.copyDirectory(folder, dest);
+ FileUtils.copyDirectory(folder, dest, false);
//                           HERE  -----^

Or alternately...

- FileUtils.copyDirectory(folder, dest);
+ PathUtils.copyDirectory(folder.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
//^------ HERE

Quoting some exchange with the Apache Commons mailing list:

I think this is the problematic line: https://github.com/apache/commons-io/blob/f22a4227401855ecbfdf8184bbe37275c3aeb5c3/src/main/java/org/apache/commons/io/FileUtils.java#L701

From what I'm reading, toggling off preserveFileDate will inadvertently and unobviously fix this, but at this point. This fix seems like undesired, undocumented and prone-to-break-later behavior, at least for Windows environments.

I'm curious what reasoning was used to associate a file's modified date with the rest of its attributes. Perhaps this was a backward-compat shortcut to maintain the modified date using the new NIO API? Regardless, this introduces permissions changes which can and will break standard file copy operations for environments which expect inherited permissions, e.g. Windows.

Ideally, the file modified date could stay without clobbering the default NIO behavior.

For now a viable workaround is:

FileUtils.copyDirectory(folder, dest, false /* <--- change to FALSE */);

... however this does have the disadvantage of (potentially) losing timestamp information, which is still a regression, albeit smaller.

... however local tests show that timestamp information (files, not folders) is preserved by default on Windows (even with this flag off), so this second concern may not be as... well... concerning. The impact of this change on Unixes is yet to be tested.

Upvotes: 1

Willis Blackburn
Willis Blackburn

Reputation: 8204

The reason for the difference is that commons-io 2.4 performs its own file copy (source) while commons-io 2.11 delegates to NIO's Files.copy (source).

The NIO Files.copy takes one or more CopyOption values. The relevant one here is CopyOption.COPY_ATTRIBUTES, which tells NIO to copy the attributes from the source file to the destination.

If you pass true as the preserveFileDate parameter to FileUtils.copyDirectory, which is the default if you use the two-argument variant, FileUtils converts that to CopyOption.COPY_ATTRIBUTES and passes it to Files.copyFile.

The upshot of this is that when you ask FileUtils to preserve the file date, it in fact copies all of the attributes over from the source file, including the ACLs.

As has already been mentioned in some other answers, the solution is to pass preserveFileDate as false, which gives you the same behavior as the original version 2.4: the files are created in the destination directory with the default attributes and ACLs.

FileUtils.copyDirectory(folder, dest, /* preserveFileDate= */ false);

Upvotes: 0

Gary Gregory
Gary Gregory

Reputation: 546

Use org.apache.commons.io.FileUtils.copyFile(File, File, boolean, CopyOption...) to exercise full control over what you want to do. Under the covers it ends up calling java.nio.file.Files.copy(Path, Path, CopyOption...).

Upvotes: 0

Related Questions