From 518e28697532810361c17e8fd9a5a05d57357dae Mon Sep 17 00:00:00 2001 From: Popax21 Date: Wed, 25 Feb 2026 15:14:25 +0100 Subject: [PATCH 1/4] Allow for raw marshalling of BackingIds Adds methods to convert BackingIds from/to their raw `backing_id` values, which allows for marshalling of backing file references across process boundaries. --- src/channel.rs | 4 ++++ src/passthrough.rs | 40 ++++++++++++++++++++++++++++++++++++--- src/reply.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index 8651199f..0bedc118 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -95,4 +95,8 @@ impl ChannelSender { pub(crate) fn open_backing(&self, fd: BorrowedFd<'_>) -> std::io::Result { BackingId::create(&self.0, fd) } + + pub(crate) unsafe fn wrap_backing(&self, id: u32) -> BackingId { + unsafe { BackingId::wrap_raw(&self.0, id) } + } } diff --git a/src/passthrough.rs b/src/passthrough.rs index c6651256..77c3dce2 100644 --- a/src/passthrough.rs +++ b/src/passthrough.rs @@ -35,7 +35,15 @@ pub struct BackingId { } impl BackingId { - pub(crate) fn create(channel: &Arc, fd: impl AsFd) -> std::io::Result { + /// Creates a new backing file reference for the given file descriptor. + /// + /// Usually, you will want to use [`ReplyOpen::open_backing()`](crate::ReplyOpen::open_backing) + /// instead, since this method will return a raw `backing_id` value instead of a managed + /// `BackingId` wrapper. As such you must manage the lifetime of the backing file yourself. + /// + /// This method is useful if you want to open a backing file reference without access to a reply + /// object. + pub fn create_raw(fuse_dev: impl AsFd, fd: impl AsFd) -> std::io::Result { if !cfg!(target_os = "linux") { return Err(std::io::Error::new( std::io::ErrorKind::Other, @@ -48,12 +56,38 @@ impl BackingId { flags: 0, padding: 0, }; - let id = unsafe { fuse_dev_ioc_backing_open(channel.as_raw_fd(), &map) }?; + let id = unsafe { fuse_dev_ioc_backing_open(fuse_dev.as_fd().as_raw_fd(), &map) }?; + + Ok(id as u32) + } + + pub(crate) fn create(channel: &Arc, fd: impl AsFd) -> std::io::Result { Ok(Self { channel: Arc::downgrade(channel), - backing_id: id as u32, + backing_id: Self::create_raw(channel, fd)?, }) } + + pub(crate) unsafe fn wrap_raw(channel: &Arc, id: u32) -> Self { + Self { + channel: Arc::downgrade(channel), + backing_id: id, + } + } + + /// Converts this backing file reference into the raw `backing_id` value as returned by the kernel. + /// + /// This method transfers ownership of the backing file to the caller, who must invoke the + /// `FUSE_DEV_IOC_BACKING_CLOSE` themselves once they wish to close the backing file. + /// + /// The returned ID may subsequently be reopened using + /// [`ReplyOpen::wrap_backing()`](crate::ReplyOpen::wrap_backing). + pub fn into_raw(mut self) -> u32 { + let id = self.backing_id; + drop(std::mem::take(&mut self.channel)); + std::mem::forget(self); + id + } } impl Drop for BackingId { diff --git a/src/reply.rs b/src/reply.rs index 9c0eeff6..7d249f75 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -69,6 +69,17 @@ impl ReplySender { ReplySender::Sync(_) => unreachable!(), } } + + /// Wraps a raw backing file ID + pub(crate) unsafe fn wrap_backing(&self, id: u32) -> BackingId { + match self { + ReplySender::Channel(sender) => unsafe { sender.wrap_backing(id) }, + #[cfg(test)] + ReplySender::Assert(_) => unreachable!(), + #[cfg(test)] + ReplySender::Sync(_) => unreachable!(), + } + } } #[cfg(test)] @@ -335,6 +346,24 @@ impl ReplyOpen { self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) } + /// Wraps a raw FUSE `backing_id` value, returning a `BackingId`. Once you have the backing ID, + /// you can pass it as the 3rd parameter of [`ReplyOpen::opened_passthrough()`]. This is done in + /// two separate steps because it may make sense to reuse backing IDs (to avoid having to + /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// + /// This function takes ownership of the given backing ID value, automatically closing it once + /// the returned `BackingId` is dropped. You may reobtain ownership of the backing ID by calling + /// [`BackingId::into_raw()`]. + /// + /// # Safety + /// + /// The given ID must be open and belong to this FUSE session, and may not be closed while the + /// returned `BackingId` instance is still live. + pub unsafe fn wrap_backing(&self, id: u32) -> BackingId { + // TODO: assert passthrough capability is enabled. + unsafe { self.reply.sender.as_ref().unwrap().wrap_backing(id) } + } + /// Reply to a request with an opened backing id. Call [`ReplyOpen::open_backing()`] /// to get one of these. pub fn opened_passthrough(self, fh: ll::FileHandle, flags: FopenFlags, backing_id: &BackingId) { @@ -474,6 +503,24 @@ impl ReplyCreate { self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) } + /// Wraps a raw FUSE `backing_id` value, returning a `BackingId`. Once you have the backing ID, + /// you can pass it as the 3rd parameter of [`ReplyOpen::opened_passthrough()`]. This is done in + /// two separate steps because it may make sense to reuse backing IDs (to avoid having to + /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// + /// This function takes ownership of the given backing ID value, automatically closing it once + /// the returned `BackingId` is dropped. You may reobtain ownership of the backing ID by calling + /// [`BackingId::into_raw()`]. + /// + /// # Safety + /// + /// The given ID must be open and belong to this FUSE session, and may not be closed while the + /// returned `BackingId` instance is still live. + pub unsafe fn wrap_backing(&self, id: u32) -> BackingId { + // TODO: assert passthrough capability is enabled. + unsafe { self.reply.sender.as_ref().unwrap().wrap_backing(id) } + } + /// Reply to a request with an opened backing id. Call `ReplyCreate::open_backing()` to get one of /// these. pub fn created_passthrough( From 4a803f8e289aa087be0d3d951dcf66cc7b857135 Mon Sep 17 00:00:00 2001 From: Popax21 Date: Sat, 28 Feb 2026 00:05:27 +0100 Subject: [PATCH 2/4] Clarify BackingId caching requirement 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. --- src/reply.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/reply.rs b/src/reply.rs index 7d249f75..094bf014 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -339,8 +339,7 @@ impl ReplyOpen { /// Registers a fd for passthrough, returning a `BackingId`. Once you have the backing ID, /// you can pass it as the 3rd parameter of [`ReplyOpen::opened_passthrough()`]. This is done in - /// two separate steps because it may make sense to reuse backing IDs (to avoid having to - /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// two separate steps because you must reuse backing IDs for the same inode for all open file handles. pub fn open_backing(&self, fd: impl std::os::fd::AsFd) -> std::io::Result { // TODO: assert passthrough capability is enabled. self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) @@ -348,8 +347,7 @@ impl ReplyOpen { /// Wraps a raw FUSE `backing_id` value, returning a `BackingId`. Once you have the backing ID, /// you can pass it as the 3rd parameter of [`ReplyOpen::opened_passthrough()`]. This is done in - /// two separate steps because it may make sense to reuse backing IDs (to avoid having to - /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// two separate steps because you must reuse backing IDs for the same inode for all open file handles. /// /// This function takes ownership of the given backing ID value, automatically closing it once /// the returned `BackingId` is dropped. You may reobtain ownership of the backing ID by calling @@ -366,6 +364,9 @@ impl ReplyOpen { /// Reply to a request with an opened backing id. Call [`ReplyOpen::open_backing()`] /// to get one of these. + /// + /// Note that you must reuse the given `BackingId` for all future [`ReplyOpen::opened_passthrough()`] + /// invocations as long as the passed file handle stays open! pub fn opened_passthrough(self, fh: ll::FileHandle, flags: FopenFlags, backing_id: &BackingId) { // TODO: assert passthrough capability is enabled. let flags = flags | FopenFlags::FOPEN_PASSTHROUGH; @@ -497,16 +498,14 @@ impl ReplyCreate { /// Registers a fd for passthrough, returning a `BackingId`. Once you have the backing ID, /// you can pass it as the 6th parameter of `ReplyCreate::created_passthrough()`. This is done in - /// two separate steps because it may make sense to reuse backing IDs (to avoid having to - /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// two separate steps because you must reuse backing IDs for the same inode for all open file handles. pub fn open_backing(&self, fd: impl std::os::fd::AsFd) -> std::io::Result { self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) } /// Wraps a raw FUSE `backing_id` value, returning a `BackingId`. Once you have the backing ID, - /// you can pass it as the 3rd parameter of [`ReplyOpen::opened_passthrough()`]. This is done in - /// two separate steps because it may make sense to reuse backing IDs (to avoid having to - /// repeatedly reopen the underlying file or potentially keep thousands of fds open). + /// you can pass it as the 3rd parameter of [`ReplyCreate::created_passthrough()`]. This is done in + /// two separate steps because you must reuse backing IDs for the same inode for all open file handles. /// /// This function takes ownership of the given backing ID value, automatically closing it once /// the returned `BackingId` is dropped. You may reobtain ownership of the backing ID by calling @@ -521,8 +520,11 @@ impl ReplyCreate { unsafe { self.reply.sender.as_ref().unwrap().wrap_backing(id) } } - /// Reply to a request with an opened backing id. Call `ReplyCreate::open_backing()` to get one of + /// Reply to a request with an opened backing id. Call [`ReplyCreate::open_backing()`] to get one of /// these. + /// + /// Note that you must reuse the given `BackingId` for all future [`ReplyOpen::opened_passthrough()`] + /// invocations as long as the passed file handle stays open! pub fn created_passthrough( self, ttl: &Duration, From b9b492a29803509c5f80792b2632325950a84ce9 Mon Sep 17 00:00:00 2001 From: Popax21 Date: Sat, 28 Feb 2026 00:12:46 +0100 Subject: [PATCH 3/4] Minor session improvements Expose the the `run` method while fixing an options check blindspot. --- src/lib.rs | 5 +---- src/session.rs | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f1860e34..6fb6cb12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,6 @@ pub use crate::ll::request::LockOwner; pub use crate::ll::request::Version; pub use crate::mnt::mount_options::Config; pub use crate::mnt::mount_options::MountOption; -use crate::mnt::mount_options::check_option_conflicts; pub use crate::notify::Notifier; pub use crate::notify::PollHandle; pub use crate::notify::PollNotifier; @@ -1054,8 +1053,7 @@ pub fn mount>( mountpoint: P, options: &Config, ) -> io::Result<()> { - check_option_conflicts(options)?; - Session::new(filesystem, mountpoint.as_ref(), options).and_then(|se| se.run()) + Session::new(filesystem, mountpoint.as_ref(), options).and_then(session::Session::run) } /// Mount the given filesystem to the given mountpoint. This function spawns @@ -1072,6 +1070,5 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( mountpoint: P, options: &Config, ) -> io::Result { - check_option_conflicts(options)?; Session::new(filesystem, mountpoint.as_ref(), options).and_then(session::Session::spawn) } diff --git a/src/session.rs b/src/session.rs index 3778ebca..76261db3 100644 --- a/src/session.rs +++ b/src/session.rs @@ -41,6 +41,7 @@ use crate::ll::flags::init_flags::InitFlags; use crate::ll::fuse_abi as abi; use crate::mnt::Mount; use crate::mnt::mount_options::Config; +use crate::mnt::mount_options::check_option_conflicts; use crate::notify::Notifier; use crate::read_buf::FuseReadBuf; use crate::reply::Reply; @@ -155,6 +156,8 @@ impl Session { mountpoint: P, options: &Config, ) -> io::Result> { + check_option_conflicts(options)?; + let mountpoint = mountpoint.as_ref(); info!("Mounting {}", mountpoint.display()); // If AutoUnmount is requested, but not AllowRoot or AllowOther, return an error @@ -240,7 +243,7 @@ impl Session { /// may run concurrent by spawning threads. /// # Errors /// Returns any final error when the session comes to an end. - pub(crate) fn run(self) -> io::Result<()> { + pub fn run(self) -> io::Result<()> { let Session { filesystem, ch, From c64407b229e5c98dfe6efd9a481cbc1d519ea65c Mon Sep 17 00:00:00 2001 From: Popax21 Date: Sat, 28 Feb 2026 00:25:04 +0100 Subject: [PATCH 4/4] Add forking passthrough example --- Cargo.toml | 3 + Makefile | 7 +- examples/passthrough_fork.rs | 335 +++++++++++++++++++++++++++++++++++ tests/test_passthrough.sh | 6 +- 4 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 examples/passthrough_fork.rs diff --git a/Cargo.toml b/Cargo.toml index 7c32bbbf..59c391fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,3 +85,6 @@ name = "ioctl" [[example]] name = "passthrough" + +[[example]] +name = "passthrough_fork" diff --git a/Makefile b/Makefile index 9a76bacc..01836ebc 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,10 @@ build: pre cargo build --examples --features=experimental format: - cargo fmt --all + cargo +nightly fmt --all pre: - cargo fmt --all -- --check + cargo +nightly fmt --all -- --check cargo deny check licenses cargo clippy --all-targets cargo clippy --all-targets --no-default-features @@ -63,8 +63,9 @@ mount_tests: fuser:mount_tests_libfuse3 bash -c "cd /code/fuser && cargo run -p fuser-tests -- linux-mount-libfuse3" test_passthrough: - cargo build --example passthrough + cargo build --example passthrough --example passthrough_fork sudo tests/test_passthrough.sh target/debug/examples/passthrough + sudo tests/test_passthrough.sh target/debug/examples/passthrough_fork test: pre mount_tests pjdfs_tests xfstests cargo test diff --git a/examples/passthrough_fork.rs b/examples/passthrough_fork.rs new file mode 100644 index 00000000..f5be9572 --- /dev/null +++ b/examples/passthrough_fork.rs @@ -0,0 +1,335 @@ +// This example requires fuse 7.40 or later. Run with: +// +// cargo run --example passthrough_fork /tmp/foobar + +mod common; + +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs::File; +use std::os::fd::AsFd; +use std::os::fd::AsRawFd; +use std::os::fd::FromRawFd; +use std::os::fd::OwnedFd; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::net::UnixDatagram; +use std::sync::Arc; +use std::sync::Weak; +use std::time::Duration; +use std::time::UNIX_EPOCH; + +use clap::Parser; +use fuser::BackingId; +use fuser::Errno; +use fuser::FileAttr; +use fuser::FileHandle; +use fuser::FileType; +use fuser::Filesystem; +use fuser::FopenFlags; +use fuser::INodeNo; +use fuser::InitFlags; +use fuser::KernelConfig; +use fuser::LockOwner; +use fuser::MountOption; +use fuser::OpenFlags; +use fuser::ReplyAttr; +use fuser::ReplyDirectory; +use fuser::ReplyEmpty; +use fuser::ReplyEntry; +use fuser::ReplyOpen; +use fuser::Request; +use nix::sys::socket; +use nix::sys::socket::MsgFlags; +use parking_lot::Mutex; + +#[derive(Parser)] +#[command(version)] +struct Args { + #[clap(flatten)] + common_args: CommonArgs, +} + +const TTL: Duration = Duration::from_secs(1); // 1 second + +use crate::common::args::CommonArgs; + +// See [./passthrough.rs] +#[derive(Debug, Default)] +struct BackingCache { + by_handle: HashMap>, + by_inode: HashMap>, + next_fh: u64, +} + +impl BackingCache { + fn next_fh(&mut self) -> u64 { + self.next_fh += 1; + self.next_fh + } + + fn get_or( + &mut self, + ino: INodeNo, + callback: impl Fn() -> std::io::Result, + ) -> std::io::Result<(u64, Arc)> { + let fh = self.next_fh(); + + let id = if let Some(id) = self.by_inode.get(&ino).and_then(Weak::upgrade) { + eprintln!("HIT! reusing {id:?}"); + id + } else { + let id = Arc::new(callback()?); + self.by_inode.insert(ino, Arc::downgrade(&id)); + eprintln!("MISS! new {id:?}"); + id + }; + + self.by_handle.insert(fh, Arc::clone(&id)); + Ok((fh, id)) + } + + fn put(&mut self, fh: u64) { + eprintln!("Put fh {fh}"); + match self.by_handle.remove(&fh) { + None => eprintln!("ERROR: Put fh {fh} but it wasn't found in cache!!\n"), + Some(id) => eprintln!("Put fh {fh}, was {id:?}\n"), + } + } +} + +#[derive(Debug)] +struct ForkPassthroughFs { + root_attr: FileAttr, + passthrough_file_attr: FileAttr, + socket: UnixDatagram, + backing_cache: Mutex, +} + +impl ForkPassthroughFs { + fn new(socket: UnixDatagram) -> Self { + let uid = nix::unistd::getuid().into(); + let gid = nix::unistd::getgid().into(); + + let root_attr = FileAttr { + ino: INodeNo::ROOT, + size: 0, + blocks: 0, + atime: UNIX_EPOCH, // 1970-01-01 00:00:00 + mtime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + crtime: UNIX_EPOCH, + kind: FileType::Directory, + perm: 0o755, + nlink: 2, + uid, + gid, + rdev: 0, + flags: 0, + blksize: 512, + }; + + let passthrough_file_attr = FileAttr { + ino: INodeNo(2), + size: 123_456, + blocks: 1, + atime: UNIX_EPOCH, // 1970-01-01 00:00:00 + mtime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + crtime: UNIX_EPOCH, + kind: FileType::RegularFile, + perm: 0o644, + nlink: 1, + uid: 333, + gid: 333, + rdev: 0, + flags: 0, + blksize: 512, + }; + + Self { + root_attr, + passthrough_file_attr, + socket, + backing_cache: Mutex::default(), + } + } +} + +impl Filesystem for ForkPassthroughFs { + fn init(&mut self, _req: &Request, config: &mut KernelConfig) -> std::io::Result<()> { + config + .add_capabilities(InitFlags::FUSE_PASSTHROUGH) + .unwrap(); + config.set_max_stack_depth(2).unwrap(); + Ok(()) + } + + fn destroy(&mut self) { + // Tell the parent process to shut down + self.socket.send(&[]).unwrap(); + } + + fn lookup(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) { + if parent == INodeNo::ROOT && name.to_str() == Some("passthrough") { + reply.entry(&TTL, &self.passthrough_file_attr, fuser::Generation(0)); + } else { + reply.error(Errno::ENOENT); + } + } + + fn getattr(&self, _req: &Request, ino: INodeNo, _fh: Option, reply: ReplyAttr) { + match ino.0 { + 1 => reply.attr(&TTL, &self.root_attr), + 2 => reply.attr(&TTL, &self.passthrough_file_attr), + _ => reply.error(Errno::ENOENT), + } + } + + fn open(&self, _req: &Request, ino: INodeNo, _flags: OpenFlags, reply: ReplyOpen) { + if ino != INodeNo(2) { + reply.error(Errno::ENOENT); + return; + } + + let (fh, id) = self + .backing_cache + .lock() + .get_or(ino, || { + // Ask the parent process to open a backing ID for us (concurrency is left as an exercise for the reader) + const FILE: &str = "/etc/profile"; + eprintln!("Asking server to open backing file for {FILE:?}"); + + let mut buf = [0u8; 4]; + self.socket.send(FILE.as_bytes())?; + self.socket.recv(&mut buf)?; + Ok(unsafe { reply.wrap_backing(u32::from_ne_bytes(buf)) }) + }) + .unwrap(); + + eprintln!(" -> opened_passthrough({fh:?}, 0, {id:?});\n"); + reply.opened_passthrough(FileHandle(fh), FopenFlags::empty(), &id); + } + + fn release( + &self, + _req: &Request, + _ino: INodeNo, + fh: FileHandle, + _flags: OpenFlags, + _lock_owner: Option, + _flush: bool, + reply: ReplyEmpty, + ) { + self.backing_cache.lock().put(fh.into()); + reply.ok(); + } + + fn readdir( + &self, + _req: &Request, + ino: INodeNo, + _fh: FileHandle, + offset: u64, + mut reply: ReplyDirectory, + ) { + if ino != INodeNo::ROOT { + reply.error(Errno::ENOENT); + return; + } + + let entries = vec![ + (1, FileType::Directory, "."), + (1, FileType::Directory, ".."), + (2, FileType::RegularFile, "passthrough"), + ]; + + for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { + // i + 1 means the index of the next entry + if reply.add(INodeNo(entry.0), (i + 1) as u64, entry.1, entry.2) { + break; + } + } + reply.ok(); + } +} + +fn main() { + // Fork and handle opening backing IDs in the parent process + // Do this early since forking isn't guaranteed to be a safe operation once e.g. libraries get involved + // You may also choose to use `std::process::Command` / etc instead + let (parent_sock, child_sock) = UnixDatagram::pair().unwrap(); + match unsafe { nix::unistd::fork().unwrap() } { + nix::unistd::ForkResult::Parent { .. } => { + drop(child_sock); + backing_id_server(parent_sock); + return; + } + nix::unistd::ForkResult::Child => { + drop(parent_sock); + } + } + + // You may wish to drop privileges (i.e. CAP_SYS_ADMIN / root) here + + // Mount the FS as usual + let args = Args::parse(); + env_logger::init(); + + let mut cfg = args.common_args.config(); + cfg.mount_options + .extend([MountOption::FSName("passthrough".to_string())]); + let fs = ForkPassthroughFs::new(child_sock.try_clone().unwrap()); + let session = fuser::Session::new(fs, &args.common_args.mount_point, &cfg).unwrap(); + + // Send the FUSE FD to the parent process so it may open backing files + let fds = [session.as_fd().as_raw_fd()]; + + socket::sendmsg::<()>( + child_sock.as_raw_fd(), + &[], + &[socket::ControlMessage::ScmRights(&fds)], + MsgFlags::empty(), + None, + ) + .unwrap(); + + // Run the FS + session.run().unwrap(); +} + +fn backing_id_server(socket: UnixDatagram) { + let mut buf = [0u8; 1024]; + + // Receive the FUSE FD over the Unix socket + let msg = socket::recvmsg::<()>( + socket.as_fd().as_raw_fd(), + &mut [], + Some(&mut buf), + MsgFlags::empty(), + ) + .unwrap(); + + let fuse_fd = 'fd: { + for cmsg in msg.cmsgs().unwrap() { + if let socket::ControlMessageOwned::ScmRights(fds) = cmsg { + let fd = unsafe { OwnedFd::from_raw_fd(fds[0]) }; + break 'fd fd; + } + } + unreachable!(); + }; + + // Handle backing ID requests (in real world scenarios, you may wish to perform input validation / etc here) + loop { + let sz = socket.recv(&mut buf).unwrap(); + if sz == 0 { + return; + } + + let path = OsStr::from_bytes(&buf[..sz]); + eprintln!("[SERVER] opening backing file for {path:?}"); + + let id = BackingId::create_raw(&fuse_fd, File::open(path).unwrap()).unwrap(); + socket.send(&u32::to_le_bytes(id)).unwrap(); + } +} diff --git a/tests/test_passthrough.sh b/tests/test_passthrough.sh index 6de159b4..c652f2f9 100755 --- a/tests/test_passthrough.sh +++ b/tests/test_passthrough.sh @@ -2,8 +2,8 @@ # Run like: # -# cargo build --example passthrough -# sudo tests/test_passthrough.sh target/debug/examples/passthrough +# cargo build --example passthrough[_fork] +# sudo tests/test_passthrough.sh target/debug/examples/passthrough[_fork] set -eux @@ -24,7 +24,7 @@ if [ "$(id -u)" != 0 ]; then fi mnt="$(mktemp -d)" -trap 'set +e; umount "${mnt}"; wait %1; rmdir "${mnt}"' TERM INT EXIT +trap 'set +e; umount "${mnt}" && wait %1 && rmdir "${mnt}"' TERM INT EXIT sudo "${examples_passthrough}" --auto-unmount "${mnt}" & for x in $(seq 10); do