Skip to content

Rebase Modal patches onto upstream v0.18.0 - #11

Open
dflemstr wants to merge 336 commits into
fs/pin-bcff57cfrom
modal/v0.18.0
Open

Rebase Modal patches onto upstream v0.18.0#11
dflemstr wants to merge 336 commits into
fs/pin-bcff57cfrom
modal/v0.18.0

Conversation

@dflemstr

Copy link
Copy Markdown

Summary

Rebuilds Modal's fuser patches on top of upstream v0.18.0, replacing the set carried on fs/pin-bcff57c (upstream 0.14.0 plus local patches).

Upstream has since absorbed most of what we carried: the multi-threaded session loop is now Config::n_threads / Config::clone_fd, destroy handling lives in FilesystemHolder, and dispatch is already factored out of the read loop. What remains are the extension points Modal needs to serve FUSE over a virtiofs virtqueue rather than /dev/fuse, which upstream keeps internal:

  • Reply transport. ReplySender becomes an opaque public type with a custom constructor backed by a new CustomReplySender trait. Notifier and PollNotifier carry a ReplySender instead of a ChannelSender so notifications work over any transport too.
  • Dispatch entry point. RequestWithSender::new and dispatch are public and take a DispatchContext — the filesystem, ACL and session owner — instead of a /dev/fuse-backed SessionEventLoop.
  • Handshake entry point. handshake_request processes a single FUSE_INIT message from raw bytes against any sender and reports through HandshakeOutcome whether the kernel will send another. Session::handshake now drives it in a loop.
  • ForgetOne::new plus a ForgetOne re-export, so filesystems that adapt or forward requests can rewrite batch forget entries. ForgetOne appears in the public Filesystem::batch_forget signature but was not exported.
  • Uid re-export, so callers building a DispatchContext need not depend on a matching version of nix.

One optimization is also preserved: event loop read buffers are sized from the negotiated max_write rather than the protocol maximum, which upstream allocates at 16 MiB per event loop thread.

Note that libfuse is no longer a default feature upstream, so this builds the pure-Rust mount path by default — which is what Modal wants, as it removes the libfuse code path that GHSA-cvmj-47v9-35m9 affects.

Test plan

  • cargo build clean, no warnings
  • cargo test — 53 passed
  • cargo clippy --all-targets clean
  • Modal-side consumers build and their FUSE tests pass (in progress on the modal repo branch)

Made with Cursor

rarensu and others added 30 commits September 14, 2025 20:35
Fixed a bug where the documentation was missing from the `NO_XATTR` constant on a non-linux OS.
Using a macro to prevent duplication.
Clippy says it's better to write long constants with underscore separators: [https://rust-lang.github.io/rust-clippy/rust-1.51.0/index.html#unreadable_literal]
Promoted some important items to `public(crate)` for easier testing and future reorganization.
Fixed a bug where the passthrough test would fail on macOS simply because the target file doesn't exist. It can still fail, but the test failure should be related to the feature being tested (i.e., passthrough).
It's safer to define the block size as u32 (its true native type) and cast up to u64 as needed for file attr.
These `check_*` helper functions don't borrow anything from self, so they are better expressed as associated functions.
This `write_directory_content` function copies but doesn't move the entry data, so it is better as a borrowed parameter.
Clippy says that functions with the potential to error or panic should be labeled by a markdown section `#` in the doc string. [missing_errors_doc](https://rust-lang.github.io/rust-clippy/rust-1.51.0/index.html#missing_errors_doc)
I've done my best to write some kind of reasonable statements; there is room for improvement.
Repaired incorrect doc strings which were likely the result of copy and paste errors.
* Enable cross-compiling with non-Linux host

* build.rs always needs pkg-config at build time

* clippy build.rs
* Enable pure FUSE mount path on BSD

* Add constant for BSD mount helper

* Fix FreeBSD mount flag constant and imports

* Handle mount_fusefs helper invocation on BSD

* Preserve fuse device fd for mount_fusefs
More types, less unsafe.

In a few places errors were ignored, but should be handled. That would
be separate change, so I left it as is, preserving current behavior.
This is example, it does not matter much, but clearner since the project
uses nix.
MSRV is 1.85, above 1.77.
When reading sources, make more clear what code is public API and what
is internals.
- reserve capacity
- use next_multiple_of
- resize instead of memcpy for padding
- documentation assertion
If cloexec flag is cleared in parent process, another thread may fork/exec
and inherit the descriptor.
mount(2) on FreeBSD https://man.freebsd.org/cgi/man.cgi?mount(2)
does not mention the flag.

AFAIU the flag was removed a while ago.

Remove the flag, error if the flag is specified.

This change is done to migrate code to `nix`, where there's no constant
`MNT_NODEV` on FreeBSD.
cberner and others added 30 commits February 15, 2026 07:50
Adds methods to convert BackingIds from/to their raw `backing_id` values, which allows for marshalling of backing file references across process boundaries.
The kernel rejects all `FOPEN_PASSTHROUGH` replies for an inode that's already open in passthrough mode if the given `backing_id` does not match the current `backing_id`.
Clarify this in the relevant method documentation.
Expose the the `run` method while fixing an options check blindspot.
Maintaining it is too much work. If someone else would like to, please
do!
The fuse_mount_mount_fusefs function (FreeBSD mount path) was not
receiving or applying the SessionACL parameter, so the allow_other
mount option was never passed to mount_fusefs. This caused the FreeBSD
kernel to return EPERM for all operations by non-owner users (e.g. uid
65534 in pjdfstest), since only the mount owner was allowed access.

Pass the acl parameter through and convert it to the allow_other mount
option, matching the behavior of the Linux fusermount path.

https://claude.ai/code/session_017h551yq57ivFUqcJyAmAfv
Currently, `ReplyEntry::entry` forces the same TTL for both the
directory entry and its attributes. 

In our VFS in certain situations we need to audit all access, 
including stat, so we set attribute cache duration to zero. 

Setting entry cache to zero at the same time completely destroys 
performance, so I would like to add an ability to configure these 
timeouts separately.
Removed duplicate cargo test command from CI workflow.
This allows comparing Errno values in tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7W6G38VFdKggqEMQDAMAF
Modal serves FUSE over a virtiofs virtqueue as well as /dev/fuse, which needs
extension points this crate otherwise keeps internal:

- A reply transport. `ReplySender` becomes an opaque public type with a `custom`
  constructor backed by the new `CustomReplySender` trait, so replies and
  notifications can go anywhere. `Notifier` and `PollNotifier` carry a
  `ReplySender` rather than a `ChannelSender` for the same reason.
- A dispatch entry point. `RequestWithSender::new` and `dispatch` are public and
  take a `DispatchContext`, which holds just the filesystem, ACL and session
  owner instead of a whole `/dev/fuse`-backed event loop.
- A handshake entry point. `handshake_request` processes one FUSE_INIT message
  from raw bytes and any sender, reporting through `HandshakeOutcome` whether the
  kernel will send another. `Session::handshake` now drives it in a loop.
- `ForgetOne::new`, and a `ForgetOne` re-export, so that filesystems which adapt
  or forward requests can rewrite batch forget entries.
- `Uid` is re-exported so callers building a `DispatchContext` need not depend on
  a matching version of nix.

Also size event loop read buffers from the negotiated `max_write` rather than the
protocol maximum, which otherwise costs 16 MiB per event loop thread.
The kernel reports POLLERR on /dev/fuse when the connection is dead, so
is_mounted() returned true for an unmounted filesystem and never false.
This defeated the guard in umount_impl() that prevents unmounting the
mountpoint a second time after the filesystem was already unmounted
externally, which could unmount an unrelated filesystem that had since
been mounted at the same path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfPeUUeQKYjo26Y9mQG9sA
The pure-Rust mount backend left the unmount to the fusermount helper
whenever auto_unmount was requested, but that helper only acts once the
mounting process exits. Unmounting from within that process therefore did
nothing: the mountpoint was left behind as a dangling "transport endpoint
is not connected", and because the connection stayed alive, session
teardown gave up waiting and detached the still-running session thread.

Unmount in umount_impl() as libfuse's fuse_session_unmount() does, and
close the socket afterwards so the helper exits with no mount left to
clean up. auto_unmount keeps serving its purpose as a safety net for a
process that dies without unmounting.

Fixes cberner#407

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5akmePV1VnD9YRQLZH4gv
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.