[IO-751] When deleting symlinks, File/PathUtils.deleteDirectory()
changes file permissions of the target.
POSIX: Deleting a file requires write and execute on the parent. Listing
a file also requires reading.
Looking for a fix to the Ubuntu failure in the GitHub builds.
diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java b/src/main/java/org/apache/commons/io/file/PathUtils.java
index 01877d0..13d304d 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -53,7 +53,6 @@
import java.time.Duration;
import java.time.Instant;
import java.time.chrono.ChronoZonedDateTime;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -68,7 +67,6 @@
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOExceptionList;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.UncheckedIOExceptions;
import org.apache.commons.io.file.Counters.PathCounters;
@@ -512,7 +510,7 @@ public static PathCounters deleteFile(final Path file, final LinkOption[] linkOp
pathCounts.getByteCounter().add(size);
return pathCounts;
}
- } catch (AccessDeniedException e) {
+ } catch (final AccessDeniedException e) {
// Ignore and try again below.
}
final Path parent = file.getParent();
@@ -538,7 +536,7 @@ public static PathCounters deleteFile(final Path file, final LinkOption[] linkOp
}
private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
- IOFunction<PosixFileAttributes, R> function) throws IOException {
+ final IOFunction<PosixFileAttributes, R> function) throws IOException {
final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
try {
return function.apply(posixFileAttributes);
@@ -1305,67 +1303,53 @@ public static void setLastModifiedTime(final Path sourceFile, final Path targetF
* @since 2.8.0
*/
public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
- final List<Exception> causeList = new ArrayList<>(3);
try {
// Windows is simplest
if (setDosReadOnly(path, readOnly, linkOptions)) {
return path;
}
} catch (final IOException e) {
- // Remember and retry with POSIX
- causeList.add(e);
+ // Retry with POSIX below.
}
// POSIX
if (readOnly) {
// RO
// File, then parent dir (if any).
- final boolean set = setPosixReadOnlyFile(path, readOnly, linkOptions);
+ setPosixReadOnlyFile(path, readOnly, linkOptions);
setPosixDeletePermissions(path.getParent(), false, linkOptions);
- if (set) {
- return path;
- }
} else {
// RE
// Parent dir (if any), then file.
- if (setPosixDeletePermissions(path.getParent(), true, linkOptions)) {
- return path;
- }
+ setPosixDeletePermissions(path.getParent(), true, linkOptions);
}
- IOExceptionList.checkEmpty(causeList, path);
- throw new IOException(String.format("No DosFileAttributeView or PosixFileAttributeView for '%s' (linkOptions=%s)", path, Arrays.toString(linkOptions)));
+ return path;
}
- private static boolean setPosixReadOnlyFile(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
- final PosixFileAttributeView posixFileAttributeView = getPosixFileAttributeView(path, linkOptions);
- if (posixFileAttributeView != null) {
- // Not Windows 10
- final PosixFileAttributes readAttributes = posixFileAttributeView.readAttributes();
- final Set<PosixFilePermission> permissions = readAttributes.permissions();
- // @formatter:off
- final List<PosixFilePermission> readPermissions = Arrays.asList(
- PosixFilePermission.OWNER_READ
- //PosixFilePermission.GROUP_READ,
- //PosixFilePermission.OTHERS_READ
- );
- final List<PosixFilePermission> writePermissions = Arrays.asList(
- PosixFilePermission.OWNER_WRITE
- //PosixFilePermission.GROUP_WRITE,
- //PosixFilePermission.OTHERS_WRITE
- );
- // @formatter:on
- if (readOnly) {
- // RO: We can read, we cannot write.
- permissions.addAll(readPermissions);
- permissions.removeAll(writePermissions);
- } else {
- // Not RO: We can read, we can write.
- permissions.addAll(readPermissions);
- permissions.addAll(writePermissions);
- }
- Files.setPosixFilePermissions(path, permissions);
- return true;
+ private static void setPosixReadOnlyFile(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
+ // Not Windows 10
+ final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
+ // @formatter:off
+ final List<PosixFilePermission> readPermissions = Arrays.asList(
+ PosixFilePermission.OWNER_READ
+ //PosixFilePermission.GROUP_READ,
+ //PosixFilePermission.OTHERS_READ
+ );
+ final List<PosixFilePermission> writePermissions = Arrays.asList(
+ PosixFilePermission.OWNER_WRITE
+ //PosixFilePermission.GROUP_WRITE,
+ //PosixFilePermission.OTHERS_WRITE
+ );
+ // @formatter:on
+ if (readOnly) {
+ // RO: We can read, we cannot write.
+ permissions.addAll(readPermissions);
+ permissions.removeAll(writePermissions);
+ } else {
+ // Not RO: We can read, we can write.
+ permissions.addAll(readPermissions);
+ permissions.addAll(writePermissions);
}
- return false;
+ Files.setPosixFilePermissions(path, permissions);
}
private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
@@ -1381,26 +1365,24 @@ private static boolean setDosReadOnly(final Path path, final boolean readOnly, f
* To delete a file in POSIX, you need Write and Execute permissions on its parent directory.
*
* @param parent The parent path for a file element to delete which needs RW permissions.
- * @param enableDelete true to set permissions to delete.
+ * @param enableDeleteChildren true to set permissions to delete.
* @param linkOptions options indicating how handle symbolic links.
* @return true if the operation was attempted and succeeded, false if parent is null.
* @throws IOException if an I/O error occurs.
*/
- private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDelete, final LinkOption... linkOptions) throws IOException {
- if (parent != null) {
- // To delete a file in POSIX, you need write and execute permissions on its parent directory.
- // @formatter:off
- return setPosixPermissions(parent, enableDelete, Arrays.asList(
- PosixFilePermission.OWNER_WRITE,
- //PosixFilePermission.GROUP_WRITE,
- //PosixFilePermission.OTHERS_WRITE,
- PosixFilePermission.OWNER_EXECUTE
- //PosixFilePermission.GROUP_EXECUTE,
- //PosixFilePermission.OTHERS_EXECUTE
- ), linkOptions);
- // @formatter:on
- }
- return false;
+ private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
+ throws IOException {
+ // To delete a file in POSIX, you need write and execute permissions on its parent directory.
+ // @formatter:off
+ return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
+ PosixFilePermission.OWNER_WRITE,
+ //PosixFilePermission.GROUP_WRITE,
+ //PosixFilePermission.OTHERS_WRITE,
+ PosixFilePermission.OWNER_EXECUTE
+ //PosixFilePermission.GROUP_EXECUTE,
+ //PosixFilePermission.OTHERS_EXECUTE
+ ), linkOptions);
+ // @formatter:on
}
/**
@@ -1416,17 +1398,14 @@ private static boolean setPosixDeletePermissions(final Path parent, final boolea
private static boolean setPosixPermissions(final Path path, final boolean addPermissions, final List<PosixFilePermission> updatePermissions,
final LinkOption... linkOptions) throws IOException {
if (path != null) {
- final PosixFileAttributeView posixFileAttributeView = getPosixFileAttributeView(path, linkOptions);
- if (posixFileAttributeView != null) {
- final Set<PosixFilePermission> permissions = posixFileAttributeView.readAttributes().permissions();
- if (addPermissions) {
- permissions.addAll(updatePermissions);
- } else {
- permissions.removeAll(updatePermissions);
- }
- Files.setPosixFilePermissions(path, permissions);
- return true;
+ final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
+ if (addPermissions) {
+ permissions.addAll(updatePermissions);
+ } else {
+ permissions.removeAll(updatePermissions);
}
+ Files.setPosixFilePermissions(path, permissions);
+ return true;
}
return false;
}
diff --git a/src/test/java/org/apache/commons/io/DeleteDirectoryTest.java b/src/test/java/org/apache/commons/io/DeleteDirectoryTest.java
index 1bc2420..866195c 100644
--- a/src/test/java/org/apache/commons/io/DeleteDirectoryTest.java
+++ b/src/test/java/org/apache/commons/io/DeleteDirectoryTest.java
@@ -100,8 +100,10 @@ public void testDeleteFileCheckParentAccess() throws IOException {
// A file is RO in POSIX if the parent is not W and not E.
PathUtils.setReadOnly(file, true);
final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(testDir);
- assertFalse(Files.isWritable(testDir), () -> String.format("Directory '%s' should NOT be Writable, permissions are %s ", testDir, permissions));
- assertFalse(Files.isExecutable(testDir), () -> String.format("Directory '%s' should NOT be Executable, permissions are %s ", testDir, permissions));
+ assertFalse(Files.isWritable(testDir),
+ () -> String.format("Parent directory '%s' of '%s' should NOT be Writable, permissions are %s ", testDir, file, permissions));
+ assertFalse(Files.isExecutable(testDir),
+ () -> String.format("Parent directory '%s' of '%s' should NOT be Executable, permissions are %s ", testDir, file, permissions));
assertThrows(IOException.class, () -> PathUtils.delete(file));
// Nothing happened, we're not even allowed to test attributes, so the file seems deleted, but it is not.