feat(sandboxfuse): macOS FSKit FUSE backend + kext→FSKit auto-fallback#130
Open
raphaelvigee wants to merge 3 commits into
Open
feat(sandboxfuse): macOS FSKit FUSE backend + kext→FSKit auto-fallback#130raphaelvigee wants to merge 3 commits into
raphaelvigee wants to merge 3 commits into
Conversation
Add a userspace FUSE backend selector on macOS. `fuse: { backend: kext | fskit }`
pins a backend; omit it and the engine tries the kernel-extension backend first
(fastest) and falls back to Apple's FSKit (unprivileged, macOS 15.4+) when the
kext mount can't be brought up. Linux is unchanged — one kernel backend.
- config: `FuseConfig.backend` (`FuseBackend::{Kext,Fskit}`) + `on`/`auto`/
`with_backend` constructors; documented in `.hephconfig2`.
- sandboxfuse: `MountBackend` threaded through `Mount::mount`; `configure_mount`
branches per platform/backend (kext keeps AutoUnmount + RootAndOwner ACL;
FSKit omits them and passes `-o backend=fskit`).
- engine: `backend_candidates` ordering, per-backend mountpoint (FSKit mounts
under `/Volumes`, kext at `<root>/lower`), actionable per-backend failure
hints, and a `/Volumes` sweep that force-umounts stale FSKit volumes whose
pid is dead. The supervisor now umounts the registered mountpoint directly.
Fix a self-referential `OwnedBlob` bug surfaced by the FUSE read path: the
pooled sqlite connection backing the seekable reader was moved out from under
the blob that borrowed it, aborting reads with "RefCell already mutably
borrowed". Box the connection so its address is stable across moves.
Fix a macOS-only SIGSEGV in the new `testkit::fuse_mount_works` probe: libfuse
is weak-linked on macOS (sandboxfuse build.rs), so its symbols bind to null when
macFUSE is absent. The probe called `Mount::mount` without first clearing the
`support_check` gate, dereferencing a null symbol and crashing instead of
returning false. Gate the probe on `support_check` so forced-on FUSE e2e tests
skip cleanly on hosts without macFUSE.
Add e2e `fuse.rs` exercising the real mount: dep read-through-layer (the
RefCell regression test), large byte-exact streaming, upper-layer write-back,
and concurrent shared-dep reads. Each skips when no usable FUSE mount exists.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The e2e fuse suite gates on `fuse_mount_works()`, which only attempted the default (Kernel/kext) backend at a temp dir. On a kext-less macOS host the engine auto-falls-back to FSKit (`FuseConfig::on()` → kext, else FSKit), so the mount would succeed for real while the probe reported "no FUSE" and the whole suite skipped — masking the FSKit path the PR adds. Mirror `engine::backend_candidates` / `backend_mountpoint`: try the kernel backend at a temp `lower`, then FSKit under `/Volumes`, and report usable when either mounts. Cache the result in a `OnceLock` so the probe mounts at most once per process (parallel tests don't race on the shared FSKit `/Volumes` mountpoint). The `support_check` null-symbol gate is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Omitting `enabled` (or the whole `fuse:` block) previously resolved to off everywhere. Make the omitted default platform-gated via `DEFAULT_OMITTED_MODE`: `auto` on Linux (the kernel backend is always available, so let the engine decide per target) and `off` on macOS/elsewhere (the kext and FSKit backends each need a one-time, often admin- or MDM-gated approval that can't be assumed present, so FUSE stays opt-in). Explicit `enabled: true|false|auto` is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a userspace FUSE backend selector on macOS and an auto-fallback so the sandbox overlay works on machines where the macFUSE kext isn't approved.
fuse: { backend: kext | fskit }pins a backend. Omit it and the engine tries the kernel-extension backend first (fastest), then falls back to Apple FSKit (unprivileged, macOS 15.4+) if the kext mount can't come up. Linux is unchanged — single kernel backend.Changes
FuseConfig.backend(FuseBackend::{Kext,Fskit}) +on/auto/with_backendconstructors; documented inexample/.hephconfig2.MountBackendthreaded throughMount::mount;configure_mountbranches per platform/backend (kext keepsAutoUnmount+RootAndOwnerACL; FSKit omits them and passes-o backend=fskit)./Volumes, kext at<root>/lower), per-backend actionable failure hints, and a/Volumessweep that force-umounts stale FSKit volumes whose pid is dead. The supervisor umounts the registered mountpoint directly.Bug fixes folded in
OwnedBlobself-referential move — the pooled sqlite connection backing the seekable FUSE reader was moved out from under the blob borrowing it, aborting reads with "RefCell already mutably borrowed". Box the connection for a stable address. Regression-tested bytest_fuse_dep_output_read_through_layer.testkit::fuse_mount_works— libfuse is weak-linked on macOS (sandboxfusebuild.rs), so its symbols bind to null when macFUSE is absent. The probe calledMount::mountwithout first clearing thesupport_checkgate → null-symbol deref → crash instead of returningfalse. Now gated onsupport_check, so forced-on FUSE e2e tests skip cleanly on hosts without macFUSE.Tests
New
crates/e2e/tests/fuse.rsexercises the real mount path: dep read-through-layer, large byte-exact streaming, upper-layer write-back, concurrent shared-dep reads. Each skips (not fails) when no usable FUSE mount exists.Verification (macOS, this host has no macFUSE)
cargo test -p config→ 59 passed (incl. 6 new backend-parsing tests)cargo test -p e2e --test fuse→ 4 passed (skip cleanly; no SIGSEGV after the probe fix — it crashed before)cargo clippy --all-targets -- -D warnings→ clean;cargo fmtcleanOwnedBlobfix run for real only on a FUSE-capable host (Linux CI / a mac with macFUSE); this host skips them.🤖 Generated with Claude Code