Skip to content

Ticket #4720: skip entries vanished during deletion like AppleDouble - #4722

Merged
zyv merged 1 commit into
masterfrom
4720_macos_delete_forks
Oct 17, 2026
Merged

zyv merged 1 commit into
masterfrom
4720_macos_delete_forks

Conversation

@zyv

@zyv zyv commented Jun 9, 2025

Copy link
Copy Markdown
Member

Proposed changes

Resolves: #4720

@zyv
zyv requested a review from aborodin June 9, 2025 05:41
@zyv zyv self-assigned this Jun 9, 2025
@github-actions github-actions Bot added the needs triage Needs triage by maintainers label Jun 9, 2025
@github-actions github-actions Bot added this to the Future Releases milestone Jun 9, 2025
@github-actions github-actions Bot added the prio: medium Has the potential to affect progress label Jun 9, 2025
@zyv

zyv commented Jun 9, 2025

Copy link
Copy Markdown
Member Author

Could you please test this, @ollixx77?

@aborodin I'm still not sure if I'm handling return codes correctly.

@zyv zyv added area: core Issues not related to a specific subsystem and removed needs triage Needs triage by maintainers labels Jun 9, 2025
@zyv zyv modified the milestones: Future Releases, 4.8.34 Jun 9, 2025
Comment thread lib/fs.h Outdated
Comment thread src/filemanager/file.c Outdated
Comment thread src/filemanager/file.c Outdated
@ollixx77

ollixx77 commented Jun 9, 2025

Copy link
Copy Markdown

Could you please test this, @ollixx77?

@aborodin I'm still not sure if I'm handling return codes correctly.

The original change worked so far.
But took me while to get this all setup to install.

Comment thread src/filemanager/file.c Outdated
Comment thread src/filemanager/file.c Outdated
Comment thread src/filemanager/file.c Outdated
@ossilator

Copy link
Copy Markdown
Contributor

now i wonder ...

given that these are "shadow" files that always go along with "real" files, and we're talking about a situation of actually running on macos (otherwise the case of the files magically disappearing would be irrelevant), why aren't the files hidden by the os? this looks like a shortcoming that would cause all kinds of problems.

which brings me to the next point: shouldn't for example move/rename also have related code? that would suggest that this should be handled at the vfs level.

but then there is copy. how would copying a file with forks even work on an fs that supports them natively, when they are invisible to regular unix apis? or does the posix layer actually inject the shadow files?

i guess this should be properly researched before going any further with this.

@zyv zyv modified the milestones: 4.8.34, 4.8.35 Jan 25, 2026
@zyv
zyv force-pushed the 4720_macos_delete_forks branch from b2363ba to a179515 Compare May 9, 2026 06:18
@zyv zyv modified the milestones: 4.9.1, 4.9.0 May 9, 2026
@zyv
zyv force-pushed the 4720_macos_delete_forks branch from a179515 to 5469c92 Compare May 10, 2026 11:40
@zyv

zyv commented May 10, 2026

Copy link
Copy Markdown
Member Author

now i wonder ...

given that these are "shadow" files that always go along with "real" files, and we're talking about a situation of actually running on macos (otherwise the case of the files magically disappearing would be irrelevant), why aren't the files hidden by the os? this looks like a shortcoming that would cause all kinds of problems.

which brings me to the next point: shouldn't for example move/rename also have related code? that would suggest that this should be handled at the vfs level.

but then there is copy. how would copying a file with forks even work on an fs that supports them natively, when they are invisible to regular unix apis? or does the posix layer actually inject the shadow files?

i guess this should be properly researched before going any further with this.

I did some basic research on that, and I can't tell why the designers of macOS made the decision to expose the resource forks like this via the POSIX API.

I have discovered that if the files are deleted, then their resource forks are removed by "magic". If the files are copied, the resource forks are apparently taken along (or created anew, it's hard to find out).

If the files are moved, then their resource forks are also magically moved with them. So probably ideally one could implement a similar mechanism for moves, as in, first move everything that doesn't look like a fork, and then there is probably nothing else to move.

However, in cases of copies and moves the situation is much less annoying than in the case of deletion. If the fork is moved first, apparently it gets re-created. And then the newly created one is taken along when the file is moved. If the file is moved, then its fork is moved, and so there is nothing else to move. For copies, it also strangely seems to work somehow.

So, I wouldn't do anything here because it doesn't present a practical problem (apparently), and makes things more complicated. Also, I find it quite annoying that apparently there is no good way to detect forks other than by name, and even then you don't know if it's a valid file or a fork.

Anyways, it has been like that for decades. If this problem gets reported, then we could do something about that. A big issue that I have with our whole system is that we are doing things recursively, so it's not possible to build a list first and then just sort it before executing the command. Changing this is way beyond my time and capabilities at the moment.

@zyv
zyv force-pushed the 4720_macos_delete_forks branch from 5469c92 to 3e940ae Compare May 10, 2026 12:02
@zyv

zyv commented May 10, 2026

Copy link
Copy Markdown
Member Author

@mc-worker I'm now working a lot with FAT32 volumes on macOS, and this has become a real problem for me. I would like to merge the current solution because it works and doesn't seem to make any issues.

@mc-worker

Copy link
Copy Markdown
Contributor

@mc-worker I'm now working a lot with FAT32 volumes on macOS, and this has become a real problem for me. I would like to merge the current solution because it works and doesn't seem to make any issues.

On Linux, if file ._* is in a directory that is being deleted, the extra error window is raised.
w1
w2
I think, users will be confused.

@zyv

zyv commented May 10, 2026

Copy link
Copy Markdown
Member Author

@mc-worker I'm now working a lot with FAT32 volumes on macOS, and this has become a real problem for me. I would like to merge the current solution because it works and doesn't seem to make any issues.

On Linux, if file ._* is in a directory that is being deleted, the extra error window is raised. I think, users will be confused.

Hmmm, this is very strange... Thank you for testing! It seems that I am doing something wrong with the return codes in our crazy recursive system :( Do you happen to know why or how to schedule files to be deleted last properly?

@zyv

zyv commented May 10, 2026

Copy link
Copy Markdown
Member Author

Maybe I should return FILE_CONT instead? Could you please give me your test directory?

@mc-worker

mc-worker commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

Could you please give me your test directory?

mkdir tmp/a tmp/b
touch tmp/a/aaa tmp/a/._aaa tmp/b/bbb tmp/b/._bbb

Press F8 on tmp.

JFYI: https://gist.github.com/lpar/1515061

@mc-worker

Copy link
Copy Markdown
Contributor

I think I've fixed this issue.

@zyv

zyv commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

/rebase

@mc-butler
mc-butler force-pushed the 4720_macos_delete_forks branch from 48d633d to e976b7a Compare October 17, 2026 08:29
@zyv
zyv force-pushed the 4720_macos_delete_forks branch from e976b7a to 114f665 Compare October 17, 2026 09:13
@ossilator

Copy link
Copy Markdown
Contributor

the near-identical commit messages tell me that this should be squashed.

@zyv

zyv commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

So, I've burned my last $5 of Claude cloud credit on this PR, and here are the results:

Claude checked XNU kernel sources, and contrary to my previous (unsubstantiated) belief, the problem with our loop is not that ._filename files keep popping up after being deleted, but quite the opposite: they vanish by magic when filename is deleted, mc tries to delete already deleted files, fails, and shows the confusing error dialog.

The "proper" fix would be to check whether the entries have vanished during the deletion process, and skip such entries, as the goal of having them deleted has been achieved through external influence - be it the kernel playing tricks on ._ files, or the user doing parallel deletion elsewhere.

This removes the filename heuristic and second recursive pass, at the expense of not explicitly erroring out when deleting files that disappear during deletion through something else other than our explicit action. However, I think this is actually an expected behavior.

Incidentally, this needs a fix in two places, but resolves both F8 and F6 issues. The copy part of the story is apparently not affected, and I assume there is nothing to fix there, because whatever magic XNU does, it's somehow designed such as not to break the copying. If not, this is a separate topic either way.

For verification, check the following branch:

Claude has written LD_PRELOAD'able unlink/rmdir implementations mimicking XNU, and also driven tmux-based harness to check the end result on real FAT volumes on macOS.

P.S. Apparently, the official name for those pesky ._ files is "AppleDouble companion files", and not resource forks - the latter is what's stored in the former.

@zyv

zyv commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

the near-identical commit messages tell me that this should be squashed.

Well, I'm always at a loss as to when you want to have something squashed, and when you want it in separate commits - partly it appears to be changes/volume/problem/explanation driven, and partly arbitrary. The reasoning for two commits is that the first one is fixing F8, and the second one a similar related F6 problem. But I can squash them in one commit, of course - I don't care.

@zyv zyv changed the title Ticket #4720: delete resource forks on macOS last Ticket #4720: ignore errors when deleting already vanished files like AppleDouble Oct 17, 2026
@zyv
zyv requested review from mc-worker and removed request for aborodin October 17, 2026 09:45
@ossilator

Copy link
Copy Markdown
Contributor

the guiding principle is always "make changes atomic". the question is what to prioritize when different aspects conflict.

my general stance is that if the virtually same small change is done in multiple locations, then it should be one commit. this reduces clutter, and makes things more easily searchable in some cases (e.g., with git log --full-diff <one file>).

On macOS, file systems without native extended attributes support (FAT,
exFAT, SMB, ...) keep file metadata in AppleDouble "._" companion files.
Unlinking a file makes the kernel unlink its companion, so the companion
entry already returned by readdir() cannot be stat'ed anymore.

recursive_erase() treated this as an error and bailed out with FILE_RETRY,
so only one file and its companion were deleted per attempt. Instead, skip
directory entries that disappeared after the directory was read.

Also, when moving a directory to another file system, the sources are
erased at the end in the copy order, so a "Cannot remove file ... No such
file or directory" error popped up for every "._" file. Instead, treat
ENOENT in try_remove_file() from unlink() as success.

Assisted-By: Claude Opus 5.5
Signed-off-by: Yury V. Zaytsev <yury@shurup.com>
@zyv
zyv force-pushed the 4720_macos_delete_forks branch from 114f665 to 4bd2f87 Compare October 17, 2026 10:04
@zyv zyv changed the title Ticket #4720: ignore errors when deleting already vanished files like AppleDouble Ticket #4720: skip entries vanished during deletion like AppleDouble Oct 17, 2026
@zyv

zyv commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

the guiding principle is always "make changes atomic". the question is what to prioritize when different aspects conflict.

Yes, exactly, and your prioritization algorithm appears to be non-deterministic. It would be nice to make a mechanical gate out of it, actually. Then it can be simply added to the CI. Obviously, it will not always be correct, but if it produces only some number of false negatives and doesn't produce false positives, this can be very valuable in my opinion.

my general stance is that if the virtually same small change is done in multiple locations, then it should be one commit. this reduces clutter, and makes things more easily searchable in some cases (e.g., with git log --full-diff <one file>).

I have squashed two commits into one.

@ossilator

Copy link
Copy Markdown
Contributor

it's actually fairly deterministic, but it's rather fine-grained, which makes it hard to follow from the outside. the only written document i have is https://wiki.qt.io/Commit_Policy point 8. i don't think this can be automated to any meaningful degree, unless you put an AI into the loop.

the PR LGTM now.

@zyv

zyv commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

it's actually fairly deterministic, but it's rather fine-grained, which makes it hard to follow from the outside. the only written document i have is https://wiki.qt.io/Commit_Policy point 8. i don't think this can be automated to any meaningful degree, unless you put an AI into the loop.

Well, I don't think you necessarily need an LLM for that. You'd be surprised (as I was surprised) how much of this "judgement" stuff can be automated deterministically by using some sort of sum signals and gates, where signals can be regex matches, or some other functions. I used stuff like that to try to partially cure Claude's 1) unbearable comment diarrhea and 2) totally bad design judgement. I can't automatically apply it for this project though, because it's all Python stuff, but that would be actually a good use of tokens by sloppers...

@zyv
zyv merged commit ae42724 into master Oct 17, 2026
17 checks passed
@zyv
zyv deleted the 4720_macos_delete_forks branch October 17, 2026 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Issues not related to a specific subsystem prio: medium Has the potential to affect progress

Development

Successfully merging this pull request may close these issues.

Can't delete folder recursively due to MacOS resource forks (._* files)

4 participants