Skip to content

Improve FileUtils.forceDelete() tests on Windows#791

Merged
garydgregory merged 7 commits intomasterfrom
fix/file-delete
Oct 2, 2025
Merged

Improve FileUtils.forceDelete() tests on Windows#791
garydgregory merged 7 commits intomasterfrom
fix/file-delete

Conversation

@ppkarwasz
Copy link
Contributor

@ppkarwasz ppkarwasz commented Oct 2, 2025

On Windows, the DeleteFile Win32 API has a little quirk: it refuses to delete files with the legacy DOS read-only attribute set. (Because apparently 99% of Windows users don’t realize that “deleting a file” is actually an operation on the directory, not the file itself 😉).

So the usual drill is: clear the read-only flag first, then delete.

  • Until JDK 25, File.delete() did this for you behind the scenes: it quietly stripped the flag before calling into DeleteFile. That meant your file might be left behind (with the flag missing) if the real ACLs didn’t allow deletion.
  • From JDK 25 onward, File.delete() doesn’t touch the flag anymore. If the bit is set, DeleteFile fails, end of story.
  • FileUtils.forceDelete() already knows how to juggle the flag itself, so its behavior didn’t change.

This PR:

  • Updates two tests that were (unfairly) comparing File.delete with FileUtils.forceDelete. With JDK 25, their expectations diverged.
  • Adds a new test to confirm that FileUtils.forceDelete restores the read-only flag if the actual deletion fails.

On Windows, the `DeleteFile` Win32 API has a little quirk: it refuses to delete files with the legacy **DOS read-only attribute** set. (Because apparently 99% of Windows users don’t realize that “deleting a file” is actually an operation on the *directory*, not the file itself 😉).

So the usual drill is: clear the read-only flag first, then delete.

* Until JDK 25, `File.delete()` did this for you behind the scenes: it quietly stripped the flag before calling into `DeleteFile`. That meant your file might be left behind (with the flag missing) if the *real* ACLs didn’t allow deletion.
* From JDK 25 onward, `File.delete()` doesn’t touch the flag anymore. If the bit is set, `DeleteFile` fails, end of story.
* `FileUtils.forceDelete()` already knows how to juggle the flag itself, so its behavior didn’t change.

This PR:

* Updates two tests that were (unfairly) comparing `File.delete` with `FileUtils.forceDelete`. With JDK 25, their expectations diverged.
* Adds a new test to confirm that `FileUtils.forceDelete` restores the read-only flag if the actual deletion fails.

> [!WARNING]
> I didn’t develop this on a Windows box, so I couldn’t test it locally. Leaving this as a **draft PR** until CI tells us whether Windows agrees with me.
Introduce a helper that snapshots file and parent directory attributes before `setReadOnly` is applied.
If a deletion attempt fails, the holder can restore the original attributes to keep the filesystem state consistent.
On Linux/Unix, both POSIX and DOS attribute views may be supported,
while on Windows only DOS attributes are available.
Check for POSIX first to ensure the correct view is used across platforms.
@ppkarwasz ppkarwasz marked this pull request as ready for review October 2, 2025 14:29
@garydgregory garydgregory merged commit 028dd62 into master Oct 2, 2025
21 checks passed
@garydgregory
Copy link
Member

Merged, thank you @ppkarwasz.

@ppkarwasz
Copy link
Contributor Author

ppkarwasz commented Oct 2, 2025

Since the issues around FileUtils.delete and PathUtils.setReadOnly turned out to be broader than expected, this PR only contains the minimal changes needed to get the existing tests passing.

I’ve opened three follow-up JIRA tickets to track the remaining problems:

  • IO-878: semantic and implementation issues in PathUtils.setReadOnly that still need clarification.
  • IO-879: DOS read-only flag not being restored when a deletion fails.
  • IO-880: recursive deletion overriding permissions, where rm -r semantics would normally fail.

Partial fixes for these are available in the fix/read-only branch, but additional work is required.

@ppkarwasz ppkarwasz deleted the fix/file-delete branch October 2, 2025 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants