Skip to content

Add basic WinFSP support on Windows (#3) - #327

Open
qtcqtc wants to merge 6 commits into
xoriors:mainfrom
qtcqtc:feat/windows-winfsp
Open

Add basic WinFSP support on Windows (#3)#327
qtcqtc wants to merge 6 commits into
xoriors:mainfrom
qtcqtc:feat/windows-winfsp

Conversation

@qtcqtc

@qtcqtc qtcqtc commented Jul 30, 2026

Copy link
Copy Markdown

Description

Adds a basic native Windows mount implementation backed by WinFSP and the existing EncryptedFs.

Key changes:

  • implement WinFSP callbacks for create/open/read/write/flush/resize/rename/delete, metadata, security, and directory enumeration
  • enable the existing CLI on Windows and cleanly stop the WinFSP filesystem through MountHandle
  • make rencfs storage Windows-safe (|, trailing-dot directory entries, directory/file flush behavior)
  • keep legacy Unix storage names readable for cross-platform compatibility
  • document WinFSP installation and Windows drive-letter mounts
  • install a checksum-pinned WinFSP MSI in Windows CI
  • add callback-level lifecycle tests and an end-to-end R: mount smoke test

The Rust binding is winfsp_wrs, which is MIT-licensed. WinFSP remains a separately installed runtime/driver and publishes its GPLv3 license with the WinFSP Free/Libre and Open Source Software exception.

This also exposed a Windows VirtualUnlock panic in shush-rs; the dependency is temporarily pinned to the tested fix from Eyob94/shush-rs#22.

Fixes #3


Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Checklist

  • I have performed a self-review of my code
  • I have tested my code on different platforms (GitHub's Linux/macOS/Windows matrix is configured for this PR)
  • I have commented my code, particularly in hard-to-understand areas
  • I have added necessary documentation
  • My changes generate no new code warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (focused Windows tests pass; the repository already skips the full suite on Windows)

Validation

  • cargo test --release --lib mount::windows::tests: 3 passed
  • adapter test covers initialization, create, write, truncate/reopen, append, flush, read, rename with replacement, enumerate, and delete
  • cargo build --release --all-targets --all-features: passed
  • repository Clippy command: passed
  • cargo fmt --all -- --check: passed
  • cargo package --allow-dirty --no-verify: passed

The GitHub Windows job additionally installs WinFSP and performs a real mounted-drive create/read/rename/delete smoke test.

Summary by CodeRabbit

  • New Features

    • Added Windows support through WinFSP, including mounting, file operations, read-only mode, and unmounting.
    • Added Windows-safe encrypted filename handling and cross-platform path compatibility.
    • Added reliable file and directory synchronization across platforms.
    • Improved rename operations with replacement support and safer recovery when operations fail.
  • Documentation

    • Updated Linux and Windows setup and mounting instructions, including WinFSP requirements and drive-letter examples.
  • Tests

    • Added Windows adapter coverage and an end-to-end mount smoke test for common file operations.

@qtcqtc
qtcqtc requested a review from radumarias as a code owner July 30, 2026 23:28
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b4403510-5e9e-43ae-b583-4204d3394b64

📥 Commits

Reviewing files that changed from the base of the PR and between 852a66e and e8e1047.

📒 Files selected for processing (2)
  • src/encryptedfs.rs
  • src/encryptedfs/test.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/encryptedfs/test.rs
  • src/encryptedfs.rs

📝 Walkthrough

Walkthrough

Changes

The pull request adds Windows support through a WinFSP filesystem adapter, platform-specific encrypted filename and synchronization behavior, Windows build dependencies, runtime wiring, CI coverage, and updated usage documentation.

Windows WinFSP support

Layer / File(s) Summary
Platform build and mount wiring
Cargo.toml, build.rs, src/fs_util.rs, src/main.rs, src/mount.rs
Windows dependencies and build setup are added, runtime execution includes Windows, filesystem synchronization helpers are introduced, and mount/unmount behavior is made platform-specific.
Cross-platform encrypted storage
src/crypto.rs, src/encryptedfs.rs, src/crypto/read.rs, src/encryptedfs/test.rs
Filename separators and dot entries use Windows-safe storage names, directory mutations support normalized paths and rollback-aware replacement, and related tests are added.
WinFSP filesystem adapter
src/mount/windows.rs
The Windows adapter implements filesystem operations, metadata, error translation, mounting, unmounting, lifecycle handling, and adapter tests.
Windows CI and usage validation
.github/workflows/*, docs/readme/Usage.md, examples/internal_ring_speed.rs
CI installs checksum-verified WinFSP, runs adapter tests and a mount smoke test, and documents platform-specific setup and mount-point behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RencfsCLI
  participant MountPointImpl
  participant WinFSP
  participant EncryptedFs
  User->>RencfsCLI: Start rencfs with Windows drive letter
  RencfsCLI->>MountPointImpl: Provide mount configuration
  MountPointImpl->>WinFSP: Configure and mount volume
  WinFSP->>EncryptedFs: Forward create, read, rename, and delete operations
  EncryptedFs-->>WinFSP: Return filesystem results
  WinFSP-->>User: Expose mounted drive operations
Loading

Suggested reviewers: radumarias

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: adding basic WinFSP support for Windows.
Docstring Coverage ✅ Passed Docstring coverage is 82.93% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qtcqtc

qtcqtc commented Jul 30, 2026

Copy link
Copy Markdown
Author

Opire claim status: the reward record still points to radumarias/rencfs, which is now a separate stale fork (GitHub repository ID 906513469), while this canonical repository is ID 782253071. Opire therefore rejected this PR as unrelated even though the PR contains Fixes #3. I have sent the exact repository IDs and URLs to Opire support. @radumarias, if the reward/project mapping can be updated to xoriors/rencfs, PR #327 should become claimable without creating a misleading duplicate PR against the stale fork.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
.github/workflows/build_and_tests.yaml (1)

27-27: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Switching to the in-repo reusable workflow is the right call; consider scoping secrets explicitly.

Now that the callee is local and reviewable, secrets: inherit is far less risky than the previous remote @main reference, but listing only the secrets the reusable workflow actually needs would silence the zizmor secrets-inherit warning and keep the blast radius small.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build_and_tests.yaml at line 27, Update the workflow
invocation that uses ./.github/workflows/build_and_tests_reusable.yaml to
replace broad secrets inheritance with an explicit mapping of only the secrets
consumed by the reusable workflow. Inspect the callee’s declared secrets and
pass those specific entries while preserving the existing workflow inputs.

Source: Linters/SAST tools

.github/workflows/build_and_tests_reusable.yaml (1)

144-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Smoke test never exercises graceful unmount.

Stop-Process -Force skips MountHandle::umount, so the test proves mounting works but never validates clean shutdown — the very behavior this PR adds. Sending a graceful stop first (and asserting R: disappears) would cover it, falling back to -Force on timeout. Cleaning up $dataDir afterwards would also keep the runner tidy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build_and_tests_reusable.yaml around lines 144 - 149,
Update the smoke-test cleanup in the PowerShell finally block around
$rencfsProcess to request graceful termination first, wait for the process to
exit, and verify that the R: drive is unmounted; only fall back to Stop-Process
-Force if graceful shutdown times out. Remove the temporary $dataDir after
process cleanup.
src/mount/windows.rs (1)

505-530: 🎯 Functional Correctness | 🔵 Trivial | ⚖️ Poor tradeoff

Directory enumeration should be cached; dot entries are already translated

./.. are normalized to ./.. by create_directory_entry_plus, so that part does not expose .$/$.. to WinFSP. However, read_directory still re-reads and re-sorts the whole directory for every sorted-marker chunk, which makes large directory enumeration O(n² log n); cache the sorted snapshot on the directory FileContext for the enumeration lifetime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mount/windows.rs` around lines 505 - 530, Update read_directory around
the self.fs.read_dir_plus call to cache the sorted directory-entry snapshot on
FileContext for the lifetime of one enumeration, reusing it for subsequent
marker-based chunks instead of re-reading and re-sorting. Ensure the cache is
initialized once, preserves the existing name ordering, marker filtering, UTF-16
length check, and add_dir_info behavior, and is cleared when the enumeration
completes or is otherwise reset.
build.rs (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the winfsp_wrs build step target-conditional.

build.rs is compiled for the host, so #[cfg(target_os = "windows")] makes the WinFSP linker setup run on any Windows host and skip cross-compiles to Windows from Linux. Gate this on CARGO_CFG_TARGET_OS; keep the inner #[cfg(target_os = "windows")] because winfsp_wrs_build is only available for Windows target builds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.rs` around lines 1 - 4, Update the `main` function in `build.rs` to run
`winfsp_wrs_build::build()` only when `CARGO_CFG_TARGET_OS` identifies a Windows
target, while retaining the inner `#[cfg(target_os = "windows")]` guard so the
Windows-only dependency is compiled only for Windows targets.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/crypto.rs`:
- Around line 269-274: Update hash_file_name and the encryptedfs lookup paths
around directory listing, resolution, rename, and removal to recognize both
native and non-native dot-entry spellings, rather than relying on one
platform-specific result. Preserve normal hashing for other names, and add a
fixture/test covering a filesystem containing the non-native spelling so
cross-platform lookup succeeds.

In `@src/mount/windows.rs`:
- Around line 56-61: Update the security descriptor construction in the Windows
mount flow to replace the Everyone (`WD`) full-control ACE with the current
mounting user’s SID (`OW`), while retaining SYSTEM and Administrators access.
Resolve the current user SID at mount time and build the descriptor dynamically;
update `test_security_descriptor()` to use the same production descriptor format
and behavior.
- Around line 786-800: Keep setup_runtime alive for the entire test instead of
dropping it immediately after EncryptedFs::new; remove the explicit
drop(setup_runtime) and retain the runtime until all WindowsFs work completes,
preserving the existing setup flow.

---

Nitpick comments:
In @.github/workflows/build_and_tests_reusable.yaml:
- Around line 144-149: Update the smoke-test cleanup in the PowerShell finally
block around $rencfsProcess to request graceful termination first, wait for the
process to exit, and verify that the R: drive is unmounted; only fall back to
Stop-Process -Force if graceful shutdown times out. Remove the temporary
$dataDir after process cleanup.

In @.github/workflows/build_and_tests.yaml:
- Line 27: Update the workflow invocation that uses
./.github/workflows/build_and_tests_reusable.yaml to replace broad secrets
inheritance with an explicit mapping of only the secrets consumed by the
reusable workflow. Inspect the callee’s declared secrets and pass those specific
entries while preserving the existing workflow inputs.

In `@build.rs`:
- Around line 1-4: Update the `main` function in `build.rs` to run
`winfsp_wrs_build::build()` only when `CARGO_CFG_TARGET_OS` identifies a Windows
target, while retaining the inner `#[cfg(target_os = "windows")]` guard so the
Windows-only dependency is compiled only for Windows targets.

In `@src/mount/windows.rs`:
- Around line 505-530: Update read_directory around the self.fs.read_dir_plus
call to cache the sorted directory-entry snapshot on FileContext for the
lifetime of one enumeration, reusing it for subsequent marker-based chunks
instead of re-reading and re-sorting. Ensure the cache is initialized once,
preserves the existing name ordering, marker filtering, UTF-16 length check, and
add_dir_info behavior, and is cleared when the enumeration completes or is
otherwise reset.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7781076a-17ff-4f54-8db4-4434caaf3b51

📥 Commits

Reviewing files that changed from the base of the PR and between 60c2327 and 96211f0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .github/workflows/build_and_tests.yaml
  • .github/workflows/build_and_tests_reusable.yaml
  • Cargo.toml
  • build.rs
  • docs/readme/Usage.md
  • examples/internal_ring_speed.rs
  • src/crypto.rs
  • src/crypto/read.rs
  • src/encryptedfs.rs
  • src/fs_util.rs
  • src/main.rs
  • src/mount.rs
  • src/mount/windows.rs
💤 Files with no reviewable changes (1)
  • src/crypto/read.rs

Comment thread src/crypto.rs
Comment thread src/mount/windows.rs Outdated
Comment thread src/mount/windows.rs Outdated
@qtcqtc

qtcqtc commented Jul 31, 2026

Copy link
Copy Markdown
Author

Pushed b0eeab8, follow-up d932a29, and rollback-safety follow-up 852a66e to address all actionable Windows review findings:

  • preserve both Unix and Windows dot-entry spellings across lookup, enumeration, update, rename, and removal (including Windows verbatim paths for trailing-dot legacy entries)
  • scope the default ACL to the mounting user, SYSTEM, and Administrators instead of Everyone
  • keep the setup runtime alive in the adapter lifecycle test
  • cache directory enumeration snapshots per WinFSP file context
  • make WinFSP delay-load setup target-aware for cross-compilation without relying on the helper's host cfg! checks
  • surface trailing-dot directory canonicalization failures through structured warnings
  • remove unused secret inheritance

Validation: the earlier Windows run passed all 111 library tests, the repository Clippy command, rustfmt, and package creation. For d932a29, a Linux-target cargo check and an x86/x86_64/aarch64 MSVC build-script matrix pass. The local Windows re-run is environment-blocked before project compilation because WinFSP is not installed.

The latest Actions run is awaiting first-time-contributor approval: https://github.com/xoriors/rencfs/actions/runs/30595747096. Could a maintainer approve it so the real WinFSP mount smoke test can run on windows-latest?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mount/windows.rs (1)

487-514: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make replacing rename atomic with rollback protection.

In EncryptedFsFuse3::rename, replace_if_exists deletes the existing destination before calling EncryptedFs::rename. That leaves a multi-step path where, if find_by_name, remove_*, remove_directory_entry, insert_directory_entry, or storage movement fails mid-way, the original destination entry/content can be removed without the rename completing. Prefer an atomic “rename replacing destination” path in EncryptedFs, or keep the destination under a coherent lock and rollback/detect failure before data loss.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mount/windows.rs` around lines 487 - 514, Update rename in
EncryptedFsFuse3 so replace_if_exists does not remove the destination before the
source rename can complete. Add or reuse an atomic replacement operation in
EncryptedFs::rename that preserves the existing destination until all
directory-entry and storage updates succeed, or provides rollback under one
coherent lock; keep collision behavior unchanged when replacement is disabled.
🧹 Nitpick comments (1)
src/mount/windows.rs (1)

524-580: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cloning the entire cached directory listing on every paginated call.

cached_entries.as_ref().unwrap().clone() at Line 552 deep-copies every (String, FileInfo) in the directory on each read_directory invocation, even though only entries after marker are needed. For large directories enumerated across many WinFSP buffer-sized pages, this is O(n) work (allocations + string copies) per page, i.e. effectively O(n²) for a full listing.

Since directory_entries is a std::sync::Mutex (not held across an .await), you can iterate directly under the lock instead of cloning out:

♻️ Avoid the full-vector clone
-        let entries = cached_entries.as_ref().unwrap().clone();
-        drop(cached_entries);
-
-        let mut exhausted = true;
-        for (name, info) in entries {
+        let entries_ref = cached_entries.as_ref().unwrap();
+        let mut exhausted = true;
+        for (name, info) in entries_ref {
             if marker
                 .as_deref()
-                .is_some_and(|marker| name.as_str() <= marker)
+                .is_some_and(|marker| name.as_str() <= marker.as_str())
             {
                 continue;
             }
             if name.encode_utf16().count() >= 255 {
                 continue;
             }
-            if !add_dir_info(DirInfo::from_str(info, &name)) {
+            if !add_dir_info(DirInfo::from_str(info.clone(), name)) {
                 exhausted = false;
                 break;
             }
         }
+        drop(cached_entries);

Separately, entries with names encoding to ≥255 UTF-16 units are silently skipped (Line 563-565) rather than surfaced as an error — worth a comment noting this is an intentional limit of the DirInfo buffer rather than a bug, since it means such files/directories would simply never appear in directory listings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mount/windows.rs` around lines 524 - 580, Update read_directory to avoid
cloning the entire cached_entries vector on each paginated call: keep the
directory_entries mutex guard while iterating cached entries and invoking
add_dir_info, releasing it only before any await (the existing read_dir_plus
path). Preserve marker filtering, exhaustion cleanup, and pagination behavior.
Add a brief comment at the UTF-16 name-length check documenting that skipping
names of 255 or more units is an intentional DirInfo buffer limit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build.rs`:
- Around line 1-6: In the build script’s main function, remove the compile-time
#[cfg(target_os = "windows")] guard around winfsp_wrs_build::build(). Keep the
existing CARGO_CFG_TARGET_OS runtime check as the sole condition so
cross-compiling to Windows invokes the build dependency.

In `@src/encryptedfs.rs`:
- Around line 50-59: Update storage_entry_path for Windows trailing-dot names so
canonicalize() failures are surfaced instead of silently falling back to
directory.join(name). Prefer propagating the canonicalization error through
callers; if the function must remain infallible, emit a warning containing the
failure details before the fallback, ensuring trailing-dot entries do not
silently use a potentially altered path.

---

Outside diff comments:
In `@src/mount/windows.rs`:
- Around line 487-514: Update rename in EncryptedFsFuse3 so replace_if_exists
does not remove the destination before the source rename can complete. Add or
reuse an atomic replacement operation in EncryptedFs::rename that preserves the
existing destination until all directory-entry and storage updates succeed, or
provides rollback under one coherent lock; keep collision behavior unchanged
when replacement is disabled.

---

Nitpick comments:
In `@src/mount/windows.rs`:
- Around line 524-580: Update read_directory to avoid cloning the entire
cached_entries vector on each paginated call: keep the directory_entries mutex
guard while iterating cached entries and invoking add_dir_info, releasing it
only before any await (the existing read_dir_plus path). Preserve marker
filtering, exhaustion cleanup, and pagination behavior. Add a brief comment at
the UTF-16 name-length check documenting that skipping names of 255 or more
units is an intentional DirInfo buffer limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09bfdb98-73b6-47e1-be78-b17f9c96649f

📥 Commits

Reviewing files that changed from the base of the PR and between 96211f0 and b0eeab8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .github/workflows/build_and_tests.yaml
  • Cargo.toml
  • build.rs
  • src/crypto.rs
  • src/encryptedfs.rs
  • src/encryptedfs/test.rs
  • src/mount/windows.rs
💤 Files with no reviewable changes (1)
  • .github/workflows/build_and_tests.yaml

Comment thread build.rs
Comment thread src/encryptedfs.rs
@qtcqtc

qtcqtc commented Jul 31, 2026

Copy link
Copy Markdown
Author

Follow-up 852a66e addresses the remaining outside-diff rename finding and directory-enumeration nitpick from the previous CodeRabbit review: - Windows no longer deletes the destination before calling the core rename path. - Namespace mutations are serialized across create/remove/rename. - Replacement rename snapshots the encrypted HASH/LS records for the source, destination, and moved directory parent link; it commits the new destination first and restores the exact old storage paths on failure. - Replaced inode storage is removed only after the namespace commit, and stale destination listing records are cleaned without duplicating names. - A deterministic failure-injection regression test proves both source and destination contents/inodes and exactly one listing for each survive rollback. - Successful file and directory replacement now assert that the old inode/content storage is removed; replace_if_exists=false is covered end-to-end. - WinFSP directory pagination iterates the cached snapshot under the mutex instead of cloning the whole vector on every page, with the UTF-16 buffer limit documented. Validation on Windows with the isolated WinFSP build fixture: all 111 library tests pass, the release WinFSP adapter test passes, rustfmt passes, and the repository Clippy command passes.
Additional follow-up e8e1047 addresses the two findings from the review of 852a66e:

  • replaces the filesystem-wide namespace mutex with per-parent inode locks; cross-directory rename acquires both parents in deterministic inode order
  • treats HASH/LS entry writes as a transaction by snapshotting and restoring the exact previous encrypted bytes when either half fails
  • removes listing records first and restores the complete entry if the subsequent hash removal fails
  • invalidates metadata cache entries after commit/rollback without turning a committed namespace change into a false operation failure
  • adds deterministic hash-write and hash-remove failure injection covering both absent-after-failed-create and fully-present-after-failed-remove invariants

Validation: all 112 Windows library tests pass, including the new rollback regression and WinFSP adapter tests; rustfmt and the repository release Clippy command pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/encryptedfs.rs (2)

720-720: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Consider per-parent locking instead of one global mutation lock.

directory_entry_mutation_lock is a single filesystem-wide mutex, and in create it is held across inode serialization, contents-file creation with sync_all, directory syncs and open() — per the comments at Lines 760-763 that path is deliberately slow. Every concurrent create/remove/rename in any directory is now serialized behind it (same pattern at Lines 927 and 1005), so multi-threaded workloads lose the parallelism the JoinSet fan-out was built for.

A keyed lock (e.g. ArcHashMap<u64, Mutex<()>> per parent ino, taking both parents in a stable order for rename) would preserve the atomicity you need for the check-then-insert races while keeping unrelated directories independent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/encryptedfs.rs` at line 720, Replace the filesystem-wide
directory_entry_mutation_lock usage in create, remove, and rename with keyed
mutexes scoped to each parent inode, retaining the lock across the existing
mutation sequence. For rename, acquire both source and destination parent locks
in a deterministic inode order to avoid deadlocks, while preserving atomic
check-then-insert behavior and allowing unrelated directories to proceed
concurrently.

2646-2678: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Hash/ls entry pair has no compensation when only one half succeeds. Both the write and the remove path treat the hash file and the listing file as two independent operations, so a failure of the second leaves a half-entry: an orphan ls file appears in read_dir but not in find_by_name/exists_by_name, or an orphan hash file blocks name reuse while staying invisible in listings. rename compensates on its own paths, but create, remove_file and remove_dir do not.

  • src/encryptedfs.rs#L2646-L2678: after awaiting both handles, best-effort remove the side that succeeded before returning the error (see the sketch in the inline diff).
  • src/encryptedfs.rs#L2758-L2766: if remove_directory_entry_ls_file fails after the hash file was already deleted, restore the hash entry (or remove the ls file first and delete the hash entry last) so the entry stays consistently visible or consistently gone.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/encryptedfs.rs` around lines 2646 - 2678, In src/encryptedfs.rs lines
2646-2678, update the create-entry flow around the hash and ls task handles to
best-effort remove whichever side succeeded before returning an error from
either operation, preserving consistent pair creation. In src/encryptedfs.rs
lines 2758-2766, update the remove flow so a failure removing the ls entry does
not leave the hash entry deleted: remove the ls side first and delete the hash
side last, or restore the hash entry on failure, keeping both files consistently
present or absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/encryptedfs.rs`:
- Line 720: Replace the filesystem-wide directory_entry_mutation_lock usage in
create, remove, and rename with keyed mutexes scoped to each parent inode,
retaining the lock across the existing mutation sequence. For rename, acquire
both source and destination parent locks in a deterministic inode order to avoid
deadlocks, while preserving atomic check-then-insert behavior and allowing
unrelated directories to proceed concurrently.
- Around line 2646-2678: In src/encryptedfs.rs lines 2646-2678, update the
create-entry flow around the hash and ls task handles to best-effort remove
whichever side succeeded before returning an error from either operation,
preserving consistent pair creation. In src/encryptedfs.rs lines 2758-2766,
update the remove flow so a failure removing the ls entry does not leave the
hash entry deleted: remove the ls side first and delete the hash side last, or
restore the hash entry on failure, keeping both files consistently present or
absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a219de1-ec4e-4eca-948a-4e644f7a80e0

📥 Commits

Reviewing files that changed from the base of the PR and between d932a29 and 852a66e.

📒 Files selected for processing (3)
  • src/encryptedfs.rs
  • src/encryptedfs/test.rs
  • src/mount/windows.rs

@qtcqtc

qtcqtc commented Jul 31, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

FUSE (or similar) support on Windows

1 participant