Skip to content

Some work on FUSE#306

Draft
cgwalters wants to merge 4 commits into
composefs:mainfrom
cgwalters:fuse-lowlevel
Draft

Some work on FUSE#306
cgwalters wants to merge 4 commits into
composefs:mainfrom
cgwalters:fuse-lowlevel

Conversation

@cgwalters
Copy link
Copy Markdown
Collaborator

See commits for details.

cgwalters added 4 commits June 3, 2026 20:59
fuser 0.17 is needed to support multithreaded FUSE sessions: the new
API requires `Filesystem: Send + Sync + 'static`, which forces proper
Arc-based ownership of the filesystem state and makes it possible to
safely hand the implementation to multiple worker threads.

The breaking API changes and how they are addressed:

- `&self` instead of `&mut self` on all trait methods: the only mutable
  state (open file handles) is now protected by a Mutex.
- New newtypes (INodeNo, FileHandle, LockOwner, Generation) and bitflags
  (OpenFlags, FopenFlags) — updated at call sites.
- readdir/read offsets changed from i64 to u64.
- Session::from_fd now takes SessionACL + Config separately.
- Session::run() is no longer public; replaced by spawn().join().
- reply.error() takes fuser::Errno instead of raw i32.

To satisfy the `'static` bound, serve_tree_fuse() now takes
`Arc<FileSystem>` and `Arc<Repository>`. A pre-built flat Vec<InodeData>
(indexed by ino-1) replaces the old HashMap<Ino, InodeRef<'a>>, removing
the lifetime that was incompatible with `'static`. An InodeLookup index
(path→ino for dirs, LeafId→ino for leaves) handles child ino resolution
without raw pointers.

Assisted-by: OpenCode (claude-sonnet-4-6)
Signed-off-by: Colin Walters <walters@verbum.org>
Wire the composefs-fuse crate into cfsctl behind a new `fuse` cargo
feature (on by default) and expose it through both the command line and
the varlink RPC API, with an integration test exercising the FUSE mount
end to end.

CLI surface:
  - `cfsctl fuse-serve <image> <mountpoint>` serves an EROFS composefs
    image over FUSE from a file on disk.
  - `cfsctl oci mount --fuse[=<opts>]` FUSE-serves an OCI image's EROFS
    instead of doing a kernel composefs mount, so it works without
    fs-verity on the backing store. `--fuse=passthrough` opts into
    kernel-bypass reads (Linux 6.9+). Options are parsed via a small
    FuseOptions FromStr so the surface can grow without new flags.

Varlink surface:
  - `org.composefs.Repository.FuseServe` and `org.composefs.Oci.OciFuseMount`
    let a client drive FUSE mounts over the RPC socket. Both take a
    `wait` parameter: with `wait=true` the call blocks for the session;
    with `wait=false` the FUSE session is detached into a background task
    and the call returns once the mount is registered, so a caller can
    mount and then go on to use the filesystem.

The privileged_fuse_dumpfile_roundtrip integration test spawns
`cfsctl fuse-serve` as a subprocess, polls for mount readiness via st_dev
change, reads external files directly, and compares the dumpfile produced
by `cfsctl create-dumpfile` over the FUSE mount against the expected
output from write_dumpfile, asserting the FUSE implementation reports
every piece of metadata the dumpfile format captures. Uses
similar_asserts for readable diffs on mismatch.

Assisted-by: OpenCode (claude-sonnet-4-6)
Signed-off-by: Colin Walters <walters@verbum.org>
Implement readdirplus (combined readdir + lookup in one round-trip),
no-op forget (inode table is static for session lifetime), and
FOPEN_KEEP_CACHE on open replies.

Serve with one thread per logical CPU using FUSE_DEV_IOC_CLONE
(clone_fd=true) so each worker gets its own /dev/fuse fd, eliminating
per-request channel lock contention. Arc<OwnedFd> allows read() to
clone the handle and drop the mutex before calling pread, so concurrent
reads on the same file don't serialise.

Add FUSE passthrough support (Linux 6.9+): when FuseConfig::passthrough
is true and the kernel advertises FUSE_PASSTHROUGH, external file reads
are routed directly in-kernel to the repository object fds. Opt-in via
FuseConfig because passthrough requires root and a non-tmpfs backing
filesystem.

Assisted-by: OpenCode (claude-sonnet-4-6)
Signed-off-by: Colin Walters <walters@verbum.org>
Demonstrates composefs-oci as a container storage layer end to end, the
same way podman + containers-storage work: pull a real image into a
composefs repo (content-addressed, deduped, fs-verity protected), mount
its EROFS image in-kernel as a read-only container rootfs, and run a
command inside it with crun.

There is no daemon and no FUSE. `cfsctl oci mount` does a synchronous
fsmount/move_mount and returns, leaving a normal kernel mount at a host
path; crun then bind-mounts that path into the container's mount namespace
and pivots into it — the path-based handoff podman uses. A MountGuard
lazily detaches the mount on drop, so nothing leaks even if crun fails.

Runs privileged in a bcvk ephemeral VM via the existing require_privileged
harness, so it gets real root and CAP_SYS_ADMIN. The repo lives on a
loop-mounted ext4+verity filesystem (not the VM's tmpfs /var) so the secure
in-kernel composefs mount works without --insecure — faithful to the real
fs-verity-protected story. Fedora is used as the image because (unlike
busybox) it ships the /proc, /sys and /dev mountpoint directories crun
needs on a read-only rootfs. The runtime spec's env/cwd are synthesized
from the image config.

To prove the container actually runs on composefs (rather than just that
some process ran), the container command is `findmnt -J /` and the test
parses that output from inside the container, asserting the root is an
overlay mount whose source is `composefs:<image-id>` and whose options
carry `verity=require` — the unambiguous signature of a verity-backed
composefs rootfs.

crun is added to the test image dependencies.

Assisted-by: OpenCode (claude-sonnet-4-6)
Signed-off-by: Colin Walters <walters@verbum.org>
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.

1 participant