Skip to content

Commit fee56ca

Browse files
committed
fuse: Add readdirplus, multithreading, and passthrough
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>
1 parent fce1834 commit fee56ca

4 files changed

Lines changed: 343 additions & 55 deletions

File tree

crates/composefs-ctl/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,7 @@ where
13461346
if let Some(fuse_opts) = fuse {
13471347
#[cfg(feature = "fuse")]
13481348
{
1349-
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse};
1349+
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse_fd};
13501350

13511351
// Read the EROFS image from the repository's images/ directory.
13521352
let (image_fd, _verified) = repo.open_image(&erofs_id.to_hex())?;
@@ -1367,7 +1367,7 @@ where
13671367
// superblock so the connection stays alive while we serve.
13681368
let _mnt_fd = mnt_fd;
13691369

1370-
serve_tree_fuse(
1370+
serve_tree_fuse_fd(
13711371
dev_fuse,
13721372
Arc::new(filesystem),
13731373
Arc::clone(&repo),
@@ -1624,7 +1624,7 @@ where
16241624
ref mountpoint,
16251625
passthrough,
16261626
} => {
1627-
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse};
1627+
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse_fd};
16281628

16291629
let erofs_bytes = std::fs::read(image)
16301630
.with_context(|| format!("reading EROFS image {}", image.display()))?;
@@ -1640,7 +1640,7 @@ where
16401640
// superblock so the connection stays alive while we serve.
16411641
let _mnt_fd = mnt_fd;
16421642

1643-
serve_tree_fuse(
1643+
serve_tree_fuse_fd(
16441644
dev_fuse,
16451645
Arc::new(filesystem),
16461646
Arc::clone(&repo),

crates/composefs-ctl/src/varlink.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ fn run_mount<ObjectID: FsVerityHashValue>(
478478
Ok((MountReply { fd_index: 0 }, vec![mount_fd]))
479479
}
480480

481-
#[cfg(feature = "oci")]
481+
#[cfg(all(feature = "oci", not(feature = "fuse")))]
482482
fn run_oci_mount<ObjectID: composefs::fsverity::FsVerityHashValue>(
483483
repo: &Repository<ObjectID>,
484484
image: &str,
@@ -592,7 +592,7 @@ async fn run_fuse_serve<ObjectID: FsVerityHashValue>(
592592
wait: bool,
593593
) -> std::result::Result<(), RepositoryError> {
594594
use composefs::erofs::reader::erofs_to_filesystem;
595-
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse};
595+
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse_fd};
596596

597597
let erofs_bytes = std::fs::read(&image).map_err(|e| RepositoryError::InternalError {
598598
message: format!("reading EROFS image {image}: {e:#}"),
@@ -621,7 +621,7 @@ async fn run_fuse_serve<ObjectID: FsVerityHashValue>(
621621
// superblock so the connection stays alive while we serve.
622622
let _mnt_fd = mnt_fd;
623623
tokio::task::spawn_blocking(move || {
624-
serve_tree_fuse(dev_fuse, fs, repo, FuseConfig { passthrough })
624+
serve_tree_fuse_fd(dev_fuse, fs, repo, FuseConfig { passthrough })
625625
})
626626
.await
627627
.map_err(|e| RepositoryError::InternalError {
@@ -636,7 +636,7 @@ async fn run_fuse_serve<ObjectID: FsVerityHashValue>(
636636
// task is fully detached (errors are silently ignored).
637637
let _detached = tokio::task::spawn_blocking(move || {
638638
let _mnt_fd = mnt_fd;
639-
serve_tree_fuse(dev_fuse, fs, repo, FuseConfig { passthrough })
639+
serve_tree_fuse_fd(dev_fuse, fs, repo, FuseConfig { passthrough })
640640
});
641641
Ok(())
642642
}
@@ -752,7 +752,7 @@ async fn run_oci_fuse_mount<ObjectID: FsVerityHashValue>(
752752
wait: bool,
753753
) -> std::result::Result<(), oci::OciError> {
754754
use composefs::erofs::reader::erofs_to_filesystem;
755-
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse};
755+
use composefs_fuse::{FuseConfig, mount_fuse, open_fuse, serve_tree_fuse_fd};
756756

757757
// Resolve the OCI image reference (tag or digest).
758758
let img = if image.starts_with("sha256:") || image.starts_with("sha512:") {
@@ -824,13 +824,14 @@ async fn run_oci_fuse_mount<ObjectID: FsVerityHashValue>(
824824
message: format!("attaching FUSE mount at {mountpoint}: {e:#}"),
825825
}
826826
})?;
827+
827828
let fs = Arc::new(filesystem);
828829
if wait {
829830
// Hold mnt_fd alive for the session duration — it pins the FUSE
830831
// superblock so the connection stays alive while we serve.
831832
let _mnt_fd = mnt_fd;
832833
tokio::task::spawn_blocking(move || {
833-
serve_tree_fuse(dev_fuse, fs, repo, FuseConfig { passthrough })
834+
serve_tree_fuse_fd(dev_fuse, fs, repo, FuseConfig { passthrough })
834835
})
835836
.await
836837
.map_err(|e| oci::OciError::InternalError {
@@ -845,7 +846,7 @@ async fn run_oci_fuse_mount<ObjectID: FsVerityHashValue>(
845846
// task is fully detached (errors are silently ignored).
846847
let _detached = tokio::task::spawn_blocking(move || {
847848
let _mnt_fd = mnt_fd;
848-
serve_tree_fuse(dev_fuse, fs, repo, FuseConfig { passthrough })
849+
serve_tree_fuse_fd(dev_fuse, fs, repo, FuseConfig { passthrough })
849850
});
850851
Ok(())
851852
}
@@ -1243,7 +1244,7 @@ mod service_impl {
12431244
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply, MountParams,
12441245
MountReply, OpenRepo, OpenRepositoryReply, RepositoryError, run_compute_id, run_fsck,
12451246
run_fuse_serve, run_gc, run_image_objects, run_init_repository, run_inspect,
1246-
run_list_images, run_mount, run_oci_fsck, run_oci_fuse_mount, run_oci_mount, run_tag,
1247+
run_list_images, run_mount, run_oci_fsck, run_oci_fuse_mount, run_tag,
12471248
run_untag,
12481249
};
12491250
use composefs::fsverity::{Algorithm, Sha256HashValue, Sha512HashValue};
@@ -1614,9 +1615,10 @@ mod service_impl {
16141615
parse_local_fetch, pull_stream,
16151616
};
16161617
use super::{
1617-
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply, OpenRepo,
1618-
OpenRepositoryReply, RepositoryError, run_compute_id, run_fsck, run_gc, run_image_objects,
1619-
run_init_repository, run_inspect, run_list_images, run_oci_fsck, run_tag, run_untag,
1618+
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply, MountParams,
1619+
MountReply, OpenRepo, OpenRepositoryReply, RepositoryError, run_compute_id, run_fsck,
1620+
run_gc, run_image_objects, run_init_repository, run_inspect, run_list_images, run_oci_fsck,
1621+
run_oci_mount, run_tag, run_untag,
16201622
};
16211623
use composefs::fsverity::{Algorithm, Sha256HashValue, Sha512HashValue};
16221624

0 commit comments

Comments
 (0)