filesystem: report rename and removal refusals with their POSIX errors
Merged
VA
4d ago+160+160+012345678901234567890123456789-3-3-0123456789
Summary
Follow-up to #472. That PR made the kernel refuse a rename across kinds with NotDirectory (ENOTDIR) and IsDirectory (EISDIR), but only NotDirectory reached the Workspace API and no native mount reported any of the kind or emptiness refusals with its POSIX error.
Workspace API.
WorkspaceErrorgainsIsDirectoryandDirectoryNotEmpty. Before this change both stayedWorkspaceError::Filesystem, which the wire service reports as UNAVAILABLE (retryable) and the harness reports as a storage error.- The wire service now reports them as INVALID_ARGUMENT and FAILED_PRECONDITION, matching
NotDirectoryand the other precondition failures. - The harness host keys map them to
Error::Invalid.
Native mounts.
MountSourceErrorgainsNotDirectory,IsDirectoryandDirectoryNotEmpty. The adapter previously folded all three intoMountSourceError::Filesystem, which FUSE and macOS answer with EIO and ProjFS with ERROR_IO_DEVICE.- FUSE and macOS now answer them with ENOTDIR, EISDIR and ENOTEMPTY. This also covers a lazy mount, which reports them through its workspace (
native_workspace_errno). - ProjFS answers them with ERROR_DIRECTORY, ERROR_ACCESS_DENIED and ERROR_DIR_NOT_EMPTY, as NTFS does.
On Linux the kernel checks a rename's kinds before it calls FUSE, but leaves emptiness to the file system. So a rename over a non-empty directory in a FUSE mount, or an rmdir of one, returned EIO.
Tests
a_linux_mount_refuses_replacing_or_removing_a_full_directory_with_enotempty(live, ignored like the otherlinux_mount tests) fails on main and passes here. On main 8c97625a6 it fails with:
That is EIO where ENOTEMPTY is expected.assertion `left == right` failed left: Some(5) right: Some(39)kind_and_emptiness_refusals_keep_their_posix_errorspins the adapter mapping and, on Linux, the errno for each refusal.transaction_rename_follows_posix_kind_rulesnow also checks that the Workspace API returnsIsDirectoryandDirectoryNotEmpty, including when a directory is renamed onto a non-empty directory.- The ProjFS status table test covers the three new HRESULTs.
Gates
Run on this change over main bbc7802cd:
cargo fmt --all --check: clean.cargo clippy -p acyclic-fs -p acyclic-harness --all-targets --all-features -- -W warnings --cap-lints warn: zero findings on changed lines. Main itself does not pass-D warnings.cargo test -p acyclic-fs --all-features --lib: 1200 passed. The failures match those of a main build on the same host (root_capture_publishes_metadata_and_children,filtered_callback_error_replies_without_overwriting_parent_errno,an_open_handle_reports_its_file_as_it_is_now,accepted_destination_fence_cleanup_survives_original_cancellation,partial_watch_cleanup_can_be_retried). One extra failure,a_dropped_watched_view_releases_its_stores_at_once, passed 3 of 3 on rerun.- Live
linux_tests: the new ENOTEMPTY test passes.linux_mount_behaviour_casesandlinux_installed_callbacks_observe_original_finite_mount_cancellationfail on main too. cargo test -p acyclic-harness --all-features --lib: 439 passed, 5 failed. None of the failures touch rename, removal or the new variants:native_host_original_key_recovers_immutable_upload_after_head_editandscheduler_native_stage_preserves_original_key_receipt_and_finite_hash_guardfail on work accounting,native_manifest_resolution_never_calls_the_ordinary_resolveron the native boundary code,summary_fork_captures_private_payloads_and_binds_publicationwith Unauthorized, anddefault_compaction_bounds_small_message_continuationwith QuotaExceeded. The harness change is the two match arms inkeys.rs. I did not build a harness baseline on main to confirm these failures are pre-existing.- Rebased onto main ccab904f3 afterwards.
cargo check -p acyclic-fs -p acyclic-harness --all-targets --all-featureson the rebased head (5604cc566): passes.
š¤ Generated with Claude Code
Summary by cubic
Reports rename and removal refusals with their POSIX errors (ENOTDIR, EISDIR, ENOTEMPTY) across both the Workspace API and native mounts.
- Adds
IsDirectoryandDirectoryNotEmptytoWorkspaceError; the wire service and harness now map them to INVALID_ARGUMENT/FAILED_PRECONDITION andError::Invalidinstead of a retryable UNAVAILABLE. - Adds
NotDirectory,IsDirectory, andDirectoryNotEmptytoMountSourceError; FUSE and macOS answer with ENOTDIR/EISDIR/ENOTEMPTY, and ProjFS with ERROR_DIRECTORY/ERROR_ACCESS_DENIED/ERROR_DIR_NOT_EMPTY, instead of EIO/ERROR_IO_DEVICE. - On Linux, FUSE now returns ENOTEMPTY for renames or removals over/into non-empty directories, which previously returned EIO.