filesystem: report rename and removal refusals with their POSIX errors

Merged
VA
4d ago
+160-3

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.

  • WorkspaceError gains IsDirectory and DirectoryNotEmpty. Before this change both stayed WorkspaceError::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 NotDirectory and the other precondition failures.
  • The harness host keys map them to Error::Invalid.

Native mounts.

  • MountSourceError gains NotDirectory, IsDirectory and DirectoryNotEmpty. The adapter previously folded all three into MountSourceError::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 other linux_ mount tests) fails on main and passes here. On main 8c97625a6 it fails with:
    assertion `left == right` failed
      left: Some(5)
     right: Some(39)
    
    That is EIO where ENOTEMPTY is expected.
  • kind_and_emptiness_refusals_keep_their_posix_errors pins the adapter mapping and, on Linux, the errno for each refusal.
  • transaction_rename_follows_posix_kind_rules now also checks that the Workspace API returns IsDirectory and DirectoryNotEmpty, 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_cases and linux_installed_callbacks_observe_original_finite_mount_cancellation fail 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_edit and scheduler_native_stage_preserves_original_key_receipt_and_finite_hash_guard fail on work accounting, native_manifest_resolution_never_calls_the_ordinary_resolver on the native boundary code, summary_fork_captures_private_payloads_and_binds_publication with Unauthorized, and default_compaction_bounds_small_message_continuation with QuotaExceeded. The harness change is the two match arms in keys.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-features on 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 IsDirectory and DirectoryNotEmpty to WorkspaceError; the wire service and harness now map them to INVALID_ARGUMENT/FAILED_PRECONDITION and Error::Invalid instead of a retryable UNAVAILABLE.
  • Adds NotDirectory, IsDirectory, and DirectoryNotEmpty to MountSourceError; 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.