From f46396303c9617b17aed269f93ccb2881bbb9426 Mon Sep 17 00:00:00 2001 From: Alex Wied Date: Thu, 29 May 2025 20:38:55 -0400 Subject: [PATCH 1/2] chore: Bump versions of nix and libc --- Cargo.toml | 4 +-- src/api/server/sync_io.rs | 40 +++++++++++++++----------- src/transport/fusedev/linux_session.rs | 38 +++++++++++------------- src/transport/fusedev/mod.rs | 39 ++++++++++++------------- 4 files changed, 61 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd03c79f..bb1c56fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,10 @@ bitflags = "1.1" dbs-snapshot = { version = "1.5.2", optional = true } io-uring = { version = "0.5.8", optional = true } lazy_static = "1.4" -libc = "0.2.68" +libc = "0.2.172" log = "0.4.6" mio = { version = "0.8", features = ["os-poll", "os-ext"] } -nix = "0.24" +nix = { version = "0.30", features = ["event", "fs", "ioctl", "mount", "poll", "uio", "user"] } radix_trie = "0.2.1" tokio = { version = "1", optional = true } tokio-uring = { version = "0.4.0", optional = true } diff --git a/src/api/server/sync_io.rs b/src/api/server/sync_io.rs index 20268dba..a38d2804 100644 --- a/src/api/server/sync_io.rs +++ b/src/api/server/sync_io.rs @@ -1412,21 +1412,18 @@ mod tests { use crate::transport::FuseBuf; use std::fs::File; - use std::os::unix::io::AsRawFd; + use std::os::fd::AsFd; use vmm_sys_util::tempfile::TempFile; fn prepare_srvcontext<'a>( + file: &'a File, read_buf: &'a mut [u8], write_buf: &'a mut [u8], - ) -> (SrvContext<'a, PassthroughFs>, File) { - let file = TempFile::new().unwrap().into_file(); + ) -> SrvContext<'a, PassthroughFs> { let reader = Reader::<()>::from_fuse_buffer(FuseBuf::new(read_buf)).unwrap(); - let writer = FuseDevWriter::<()>::new(file.as_raw_fd(), write_buf).unwrap(); + let writer = FuseDevWriter::<()>::new(file.as_fd(), write_buf).unwrap(); let in_header = InHeader::default(); - ( - SrvContext::::new(in_header, reader, writer.into()), - file, - ) + SrvContext::::new(in_header, reader, writer.into()) } #[test] @@ -1441,7 +1438,8 @@ mod tests { 0x0, 0x0, 0x0, 0x0, // flags = 0x0000 ]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); let res = server.init(ctx).unwrap(); assert_eq!(res, 80); @@ -1453,7 +1451,8 @@ mod tests { 0x0, 0x0, 0x0, 0x0, // flags = 0x0000 ]; let mut write_buf1 = [0u8; 4096]; - let (ctx1, _file) = prepare_srvcontext(&mut read_buf1, &mut write_buf1); + let file = TempFile::new().unwrap().into_file(); + let ctx1 = prepare_srvcontext(&file, &mut read_buf1, &mut write_buf1); let res = server.init(ctx1).unwrap(); assert_eq!(res, 24); @@ -1466,7 +1465,8 @@ mod tests { let mut read_buf = [0u8; 4096]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); let res = server.write(ctx).unwrap(); assert_eq!(res, 16); @@ -1479,7 +1479,8 @@ mod tests { let mut read_buf = [0u8; 4096]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); let res = server.read(ctx).unwrap(); assert_eq!(res, 16); @@ -1492,7 +1493,8 @@ mod tests { let mut read_buf = [0u8; 4096]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); let res = server.do_readdir(ctx, true).unwrap(); assert_eq!(res, 16); @@ -1505,7 +1507,8 @@ mod tests { let mut read_buf = [0u8; 4096]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); let res = server.ioctl(ctx).unwrap(); assert!(res > 0); @@ -1520,7 +1523,8 @@ mod tests { 0x0, 0x0, 0x0, 0x0, //out_size = 0 ]; let mut write_buf_fail = [0u8; 48]; - let (ctx_fail, _file) = prepare_srvcontext(&mut read_buf_fail, &mut write_buf_fail); + let file = TempFile::new().unwrap().into_file(); + let ctx_fail = prepare_srvcontext(&file, &mut read_buf_fail, &mut write_buf_fail); let res = server.ioctl(ctx_fail).unwrap(); assert!(res > 0); } @@ -1532,7 +1536,8 @@ mod tests { let mut read_buf = [0u8; 4096]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); // forget should return 0 anyway assert_eq!(server.batch_forget(ctx).unwrap(), 0); } @@ -1544,7 +1549,8 @@ mod tests { let mut read_buf = [0x1u8, 0x2u8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; let mut write_buf = [0u8; 4096]; - let (ctx, _file) = prepare_srvcontext(&mut read_buf, &mut write_buf); + let file = TempFile::new().unwrap().into_file(); + let ctx = prepare_srvcontext(&file, &mut read_buf, &mut write_buf); assert_eq!(server.forget(ctx).unwrap(), 0); } diff --git a/src/transport/fusedev/linux_session.rs b/src/transport/fusedev/linux_session.rs index 0d3ba836..92fead76 100644 --- a/src/transport/fusedev/linux_session.rs +++ b/src/transport/fusedev/linux_session.rs @@ -10,6 +10,7 @@ use std::fs::{File, OpenOptions}; use std::ops::Deref; +use std::os::fd::AsFd; use std::os::unix::fs::PermissionsExt; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; @@ -20,8 +21,8 @@ use mio::{Events, Poll, Token, Waker}; use nix::errno::Errno; use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; use nix::mount::{mount, umount2, MntFlags, MsFlags}; -use nix::poll::{poll, PollFd, PollFlags}; -use nix::sys::epoll::{epoll_ctl, EpollEvent, EpollFlags, EpollOp}; +use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; +use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags}; use nix::unistd::{getgid, getuid, read}; use super::{ @@ -199,7 +200,7 @@ impl FuseSession { &self.fusermount, )?; - fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) + fcntl(file.as_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) .map_err(|e| SessionFailure(format!("set fd nonblocking: {e}")))?; self.file = Some(file); self.keep_alive = socket; @@ -244,7 +245,7 @@ impl FuseSession { F: FnOnce(FuseDevWriter), { if let Some(file) = &self.file { - let fd = file.as_raw_fd(); + let fd = file.as_fd(); let mut buf = vec![0x0u8; self.bufsize]; let writer = FuseDevWriter::new(fd, &mut buf).unwrap(); f(writer); @@ -301,15 +302,11 @@ impl FuseChannel { // mio default add EPOLLET to event flags, so epoll will use edge-triggered mode. // It may let poll miss some event, so manually register the fd with only EPOLLIN flag // to use level-triggered mode. - let epoll = poll.as_raw_fd(); - let mut event = EpollEvent::new(EpollFlags::EPOLLIN, usize::from(FUSE_DEV_EVENT) as u64); - epoll_ctl( - epoll, - EpollOp::EpollCtlAdd, - file.as_raw_fd(), - Some(&mut event), - ) - .map_err(|e| SessionFailure(format!("epoll register channel fd: {e}")))?; + let epoll = Epoll::new(EpollCreateFlags::empty()).unwrap(); + let event = EpollEvent::new(EpollFlags::EPOLLIN, usize::from(FUSE_DEV_EVENT) as u64); + epoll + .add(&file, event) + .map_err(|e| SessionFailure(format!("epoll register channel fd: {e}")))?; Ok(FuseChannel { file, @@ -366,7 +363,7 @@ impl FuseChannel { return Ok(None); } if fusereq_available { - let fd = self.file.as_raw_fd(); + let fd = self.file.as_fd(); match read(fd, &mut self.buf) { Ok(len) => { // ############################################### @@ -403,7 +400,7 @@ impl FuseChannel { return Ok(None); } e => { - warn! {"read fuse dev failed on fd {}: {}", fd, e}; + warn! {"read fuse dev failed on fd {}: {}", fd.as_raw_fd(), e}; return Err(SessionFailure(format!("read new request: {e:?}"))); } }, @@ -562,7 +559,7 @@ fn fuse_fusermount_mount( // When its partner recv closes, fusermount will unmount. // Remove the close-on-exec flag from the socket, so we can pass it to // fusermount. - fcntl(send.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty())) + fcntl(send.as_fd(), FcntlArg::F_SETFD(FdFlag::empty())) .map_err(|e| SessionFailure(format!("Failed to remove close-on-exec flag: {e}")))?; let mut cmd = match target_mntns { @@ -619,9 +616,9 @@ fn fuse_fusermount_mount( /// Umount a fuse file system fn fuse_kern_umount(mountpoint: &str, file: File, fusermount: &str) -> Result<()> { - let mut fds = [PollFd::new(file.as_raw_fd(), PollFlags::empty())]; + let mut fds = [PollFd::new(file.as_fd(), PollFlags::empty())]; - if poll(&mut fds, 0).is_ok() { + if poll(&mut fds, PollTimeout::ZERO).is_ok() { // POLLERR means the file system is already umounted, // or the connection has been aborted via /sys/fs/fuse/connections/NNN/abort if let Some(event) = fds[0].revents() { @@ -666,7 +663,6 @@ fn fuse_fusermount_umount(mountpoint: &str, fusermount: &str) -> Result<()> { mod tests { use super::*; use std::fs::File; - use std::os::unix::io::FromRawFd; use std::path::Path; use vmm_sys_util::tempdir::TempDir; @@ -682,8 +678,8 @@ mod tests { #[test] fn test_new_channel() { - let fd = nix::unistd::dup(std::io::stdout().as_raw_fd()).unwrap(); - let file = unsafe { File::from_raw_fd(fd) }; + let fd = nix::unistd::dup(std::io::stdout().as_fd()).unwrap(); + let file = File::from(fd); let _ = FuseChannel::new(file, 3).unwrap(); } diff --git a/src/transport/fusedev/mod.rs b/src/transport/fusedev/mod.rs index 0d8cd0e3..6f08f50f 100644 --- a/src/transport/fusedev/mod.rs +++ b/src/transport/fusedev/mod.rs @@ -11,7 +11,7 @@ use std::collections::VecDeque; use std::io::{self, IoSlice, Write}; use std::marker::PhantomData; use std::mem::ManuallyDrop; -use std::os::unix::io::RawFd; +use std::os::fd::{BorrowedFd, AsFd}; use nix::sys::uio::writev; use nix::unistd::write; @@ -82,9 +82,9 @@ impl<'a, S: BitmapSlice + Default> Reader<'a, S> { /// 2. If the writer is split, a final commit() MUST be called to issue the /// device write operation. /// 3. Concurrency, caller should not write to the writer concurrently. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub struct FuseDevWriter<'a, S: BitmapSlice = ()> { - fd: RawFd, + fd: BorrowedFd<'a>, buffered: bool, buf: ManuallyDrop>, bitmapslice: S, @@ -93,7 +93,7 @@ pub struct FuseDevWriter<'a, S: BitmapSlice = ()> { impl<'a, S: BitmapSlice + Default> FuseDevWriter<'a, S> { /// Construct a new [Writer]. - pub fn new(fd: RawFd, data_buf: &'a mut [u8]) -> Result> { + pub fn new(fd: BorrowedFd<'a>, data_buf: &'a mut [u8]) -> Result> { let buf = unsafe { Vec::from_raw_parts(data_buf.as_mut_ptr(), 0, data_buf.len()) }; Ok(FuseDevWriter { fd, @@ -280,9 +280,9 @@ impl<'a, S: BitmapSlice> FuseDevWriter<'a, S> { } } - fn do_write(fd: RawFd, data: &[u8]) -> io::Result { - write(fd, data).map_err(|e| { - error! {"fail to write to fuse device fd {}: {}, {:?}", fd, e, data}; + fn do_write(fd: Fd, data: &[u8]) -> io::Result { + write(fd.as_fd(), data).map_err(|e| { + error! {"fail to write to fuse device fd {:?}: {}, {:?}", fd.as_fd(), e, data}; io::Error::new(io::ErrorKind::Other, format!("{e}")) }) } @@ -514,7 +514,6 @@ mod async_io { mod tests { use super::*; use std::io::{Read, Seek, SeekFrom, Write}; - use std::os::unix::io::AsRawFd; use vmm_sys_util::tempfile::TempFile; #[test] @@ -546,7 +545,7 @@ mod tests { fn writer_test_simple_chain() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 106]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); writer.buffered = true; assert_eq!(writer.available_bytes(), 106); @@ -574,7 +573,7 @@ mod tests { fn writer_test_split_chain() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 108]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); let writer2 = writer.split_at(106).unwrap(); assert_eq!(writer.available_bytes(), 106); @@ -641,7 +640,7 @@ mod tests { fn writer_simple_commit_header() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 106]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); writer.buffered = true; assert_eq!(writer.available_bytes(), 106); @@ -671,7 +670,7 @@ mod tests { fn writer_split_commit_header() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 106]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); let mut other = writer.split_at(4).expect("failed to split Writer"); assert_eq!(writer.available_bytes(), 4); @@ -702,7 +701,7 @@ mod tests { fn writer_split_commit_all() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 106]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); let mut other = writer.split_at(4).expect("failed to split Writer"); assert_eq!(writer.available_bytes(), 4); @@ -744,7 +743,7 @@ mod tests { fn write_full() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 48]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); let buf = vec![0xdeu8; 64]; writer.write(&buf[..]).unwrap_err(); @@ -760,7 +759,7 @@ mod tests { fn write_vectored() { let file = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 48]; - let mut writer = FuseDevWriter::<()>::new(file.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file.as_fd(), &mut buf).unwrap(); let buf = vec![0xdeu8; 48]; let slices = [ @@ -822,7 +821,7 @@ mod tests { fn write_obj() { let file1 = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 48]; - let mut writer = FuseDevWriter::<()>::new(file1.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file1.as_fd(), &mut buf).unwrap(); let _writer2 = writer.split_at(40).unwrap(); let val = 0x1u64; @@ -834,7 +833,7 @@ mod tests { fn write_all_from() { let file1 = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 48]; - let mut writer = FuseDevWriter::<()>::new(file1.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file1.as_fd(), &mut buf).unwrap(); let mut file = TempFile::new().unwrap().into_file(); let buf = vec![0xdeu8; 64]; @@ -858,7 +857,7 @@ mod tests { fn write_all_from_split() { let file1 = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 58]; - let mut writer = FuseDevWriter::<()>::new(file1.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file1.as_fd(), &mut buf).unwrap(); let _other = writer.split_at(48).unwrap(); let mut file = TempFile::new().unwrap().into_file(); let buf = vec![0xdeu8; 64]; @@ -881,7 +880,7 @@ mod tests { fn write_from_at() { let file1 = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 48]; - let mut writer = FuseDevWriter::<()>::new(file1.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file1.as_fd(), &mut buf).unwrap(); let mut file = TempFile::new().unwrap().into_file(); let buf = vec![0xdeu8; 64]; @@ -908,7 +907,7 @@ mod tests { fn write_from_at_split() { let file1 = TempFile::new().unwrap().into_file(); let mut buf = vec![0x0u8; 58]; - let mut writer = FuseDevWriter::<()>::new(file1.as_raw_fd(), &mut buf).unwrap(); + let mut writer = FuseDevWriter::<()>::new(file1.as_fd(), &mut buf).unwrap(); let _other = writer.split_at(48).unwrap(); let mut file = TempFile::new().unwrap().into_file(); let buf = vec![0xdeu8; 64]; From 6fbced8135399a8f905b4952059c95ea2f4084cd Mon Sep 17 00:00:00 2001 From: Alex Wied Date: Fri, 30 May 2025 09:27:38 -0400 Subject: [PATCH 2/2] freebsd: Initial commit of FreeBSD support Note that this requires the environment variable RUST_LIBC_UNSTABLE_FREEBSD_VERSION to be set. --- Cargo.toml | 42 +- src/abi/fuse_abi_freebsd.rs | 1371 ++++++++++++++++++++++ src/abi/mod.rs | 5 + src/api/server/mod.rs | 5 + src/api/server/sync_io.rs | 2 + src/api/vfs/mod.rs | 5 + src/common/file_traits.rs | 2 +- src/transport/fusedev/freebsd_session.rs | 891 ++++++++++++++ src/transport/fusedev/mod.rs | 38 +- 9 files changed, 2322 insertions(+), 39 deletions(-) create mode 100644 src/abi/fuse_abi_freebsd.rs create mode 100644 src/transport/fusedev/freebsd_session.rs diff --git a/Cargo.toml b/Cargo.toml index bb1c56fb..cafedf4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,44 +11,44 @@ authors = [ ] readme = "README.md" license = "Apache-2.0 AND BSD-3-Clause" -edition = "2018" +edition = "2024" repository = "https://github.com/cloud-hypervisor/fuse-backend-rs" homepage = "https://github.com/cloud-hypervisor/" build = "build.rs" [dependencies] -arc-swap = "1.5" -async-trait = { version = "0.1.42", optional = true } -bitflags = "1.1" -dbs-snapshot = { version = "1.5.2", optional = true } -io-uring = { version = "0.5.8", optional = true } -lazy_static = "1.4" +arc-swap = "1.7" +async-trait = { version = "0.1.88", optional = true } +bitflags = "2.9" +dbs-snapshot = { version = "1.5", optional = true } +io-uring = { version = "0.7", optional = true } +lazy_static = "1.5" libc = "0.2.172" -log = "0.4.6" -mio = { version = "0.8", features = ["os-poll", "os-ext"] } +log = "0.4" +mio = { version = "1.0", features = ["os-poll", "os-ext"] } nix = { version = "0.30", features = ["event", "fs", "ioctl", "mount", "poll", "uio", "user"] } -radix_trie = "0.2.1" +radix_trie = "0.2" tokio = { version = "1", optional = true } -tokio-uring = { version = "0.4.0", optional = true } -vmm-sys-util = { version = "0.12.1", optional = true } -vm-memory = { version = "0.14.1", features = ["backend-mmap"] } +tokio-uring = { version = "0.5", optional = true } +vmm-sys-util = { version = "0.14", optional = true } +vm-memory = { version = "0.16", features = ["backend-mmap"] } virtio-bindings = { version = "=0.2.4", optional = true } -virtio-queue = { version = "0.12.0", optional = true } -vhost = { version = "0.11.0", features = ["vhost-user","vhost-user-backend"], optional = true } -versionize_derive = { version = "0.1.6", optional = true } -versionize = { version = "0.2.0", optional = true } +virtio-queue = { version = "0.14", optional = true } +vhost = { version = "0.13", features = ["vhost-user","vhost-user-backend"], optional = true } +versionize_derive = { version = "0.1", optional = true } +versionize = { version = "0.2", optional = true } [target.'cfg(target_os = "macos")'.dependencies] core-foundation-sys = { version = ">=0.8", optional = true } [target.'cfg(target_os = "linux")'.dependencies] caps = { version = "0.5", optional = true } -tokio-uring = { version = "0.4.0", optional = true } +tokio-uring = { version = "0.4", optional = true } [dev-dependencies] -tokio-test = "0.4.2" -vmm-sys-util = "0.12.1" -vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-bitmap"] } +tokio-test = "0.4" +vmm-sys-util = "0.14" +vm-memory = { version = "0.16", features = ["backend-mmap", "backend-bitmap"] } [features] default = ["fusedev"] diff --git a/src/abi/fuse_abi_freebsd.rs b/src/abi/fuse_abi_freebsd.rs new file mode 100644 index 00000000..24533e57 --- /dev/null +++ b/src/abi/fuse_abi_freebsd.rs @@ -0,0 +1,1371 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. + +//! Linux Fuse Application Binary Interfaces, Version 7.31. + +#![allow(missing_docs)] + +use std::fmt::{Debug, Formatter}; +use std::mem; + +use bitflags::bitflags; +use vm_memory::ByteValued; + +pub use libc::{ + blksize_t, dev_t, ino_t as ino64_t, mode_t, nlink_t, off_t as off64_t, pread as pread64, preadv as preadv64, pwrite as pwrite64, pwritev as pwritev64, + stat as stat64, statvfs as statvfs64, +}; + +/// Version number of this interface. +pub const KERNEL_VERSION: u32 = 7; + +/// Minor version number of this interface. +pub const KERNEL_MINOR_VERSION: u32 = 33; + +/// Init reply size is FUSE_COMPAT_INIT_OUT_SIZE +pub const KERNEL_MINOR_VERSION_INIT_OUT_SIZE: u32 = 5; + +/// Init reply size is FUSE_COMPAT_22_INIT_OUT_SIZE +pub const KERNEL_MINOR_VERSION_INIT_22_OUT_SIZE: u32 = 23; + +/// Lookup negative dentry using inode number 0 +pub const KERNEL_MINOR_VERSION_LOOKUP_NEGATIVE_ENTRY_ZERO: u32 = 4; + +/// The ID of the inode corresponding to the root directory of the file system. +pub const ROOT_ID: u64 = 1; + +// Bitmasks for `fuse_setattr_in.valid`. +const FATTR_MODE: u32 = 0x1; +const FATTR_UID: u32 = 0x2; +const FATTR_GID: u32 = 0x4; +const FATTR_SIZE: u32 = 0x8; +const FATTR_ATIME: u32 = 0x10; +const FATTR_MTIME: u32 = 0x20; +pub const FATTR_FH: u32 = 0x40; +const FATTR_ATIME_NOW: u32 = 0x80; +const FATTR_MTIME_NOW: u32 = 0x100; +pub const FATTR_LOCKOWNER: u32 = 0x200; +const FATTR_CTIME: u32 = 0x400; +const FATTR_KILL_SUIDGID: u32 = 0x800; + +bitflags! { + pub struct SetattrValid: u32 { + const MODE = FATTR_MODE; + const UID = FATTR_UID; + const GID = FATTR_GID; + const SIZE = FATTR_SIZE; + const ATIME = FATTR_ATIME; + const MTIME = FATTR_MTIME; + const ATIME_NOW = FATTR_ATIME_NOW; + const MTIME_NOW = FATTR_MTIME_NOW; + const CTIME = FATTR_CTIME; + const KILL_SUIDGID = FATTR_KILL_SUIDGID; + } +} + +// Flags use by the OPEN request/reply. + +/// Kill suid and sgid if executable +pub const FOPEN_IN_KILL_SUIDGID: u32 = 1; + +/// Bypass page cache for this open file. +const FOPEN_DIRECT_IO: u32 = 1; + +/// Don't invalidate the data cache on open. +const FOPEN_KEEP_CACHE: u32 = 2; + +/// The file is not seekable. +const FOPEN_NONSEEKABLE: u32 = 4; + +/// allow caching this directory +const FOPEN_CACHE_DIR: u32 = 8; + +/// the file is stream-like (no file position at all) +const FOPEN_STREAM: u32 = 16; + +bitflags! { + /// Options controlling the behavior of files opened by the server in response + /// to an open or create request. + pub struct OpenOptions: u32 { + /// Bypass page cache for this open file. + const DIRECT_IO = FOPEN_DIRECT_IO; + /// Don't invalidate the data cache on open. + const KEEP_CACHE = FOPEN_KEEP_CACHE; + /// The file is not seekable. + const NONSEEKABLE = FOPEN_NONSEEKABLE; + /// allow caching this directory + const CACHE_DIR = FOPEN_CACHE_DIR; + /// the file is stream-like (no file position at all) + const STREAM = FOPEN_STREAM; + } +} + +// INIT request/reply flags. + +/// Asynchronous read requests. +const ASYNC_READ: u64 = 0x1; + +/// Remote locking for POSIX file locks. +const POSIX_LOCKS: u64 = 0x2; + +/// Kernel sends file handle for fstat, etc... (not yet supported). +const FILE_OPS: u64 = 0x4; + +/// Handles the O_TRUNC open flag in the filesystem. +const ATOMIC_O_TRUNC: u64 = 0x8; + +/// FileSystem handles lookups of "." and "..". +const EXPORT_SUPPORT: u64 = 0x10; + +/// FileSystem can handle write size larger than 4kB. +const BIG_WRITES: u64 = 0x20; + +/// Don't apply umask to file mode on create operations. +const DONT_MASK: u64 = 0x40; + +/// Kernel supports splice write on the device. +const SPLICE_WRITE: u64 = 0x80; + +/// Kernel supports splice move on the device. +const SPLICE_MOVE: u64 = 0x100; + +/// Kernel supports splice read on the device. +const SPLICE_READ: u64 = 0x200; + +/// Remote locking for BSD style file locks. +const FLOCK_LOCKS: u64 = 0x400; + +/// Kernel supports ioctl on directories. +const HAS_IOCTL_DIR: u64 = 0x800; + +/// Automatically invalidate cached pages. +const AUTO_INVAL_DATA: u64 = 0x1000; + +/// Do READDIRPLUS (READDIR+LOOKUP in one). +const DO_READDIRPLUS: u64 = 0x2000; + +/// Adaptive readdirplus. +const READDIRPLUS_AUTO: u64 = 0x4000; + +/// Asynchronous direct I/O submission. +const ASYNC_DIO: u64 = 0x8000; + +/// Use writeback cache for buffered writes. +const WRITEBACK_CACHE: u64 = 0x1_0000; + +/// Kernel supports zero-message opens. +const NO_OPEN_SUPPORT: u64 = 0x2_0000; + +/// Allow parallel lookups and readdir. +const PARALLEL_DIROPS: u64 = 0x4_0000; + +/// Fs handles killing suid/sgid/cap on write/chown/trunc. +const HANDLE_KILLPRIV: u64 = 0x8_0000; + +/// FileSystem supports posix acls. +const POSIX_ACL: u64 = 0x10_0000; + +// Reading the fuse device after abort returns ECONNABORTED +const ABORT_ERROR: u64 = 0x20_0000; + +// INIT response init_out.max_pages contains the max number of req pages +const MAX_PAGES: u64 = 0x40_0000; + +// Kernel caches READLINK responses +const CACHE_SYMLINKS: u64 = 0x80_0000; + +// Kernel supports zero-message opendir +const NO_OPENDIR_SUPPORT: u64 = 0x100_0000; + +// Only invalidate cached pages on explicit request +const EXPLICIT_INVAL_DATA: u64 = 0x200_0000; + +// INIT response init_out.map_alignment contains byte alignment for foffset and +// moffset fields in struct fuse_setupmapping_out and fuse_removemapping_one. +const MAP_ALIGNMENT: u64 = 0x400_0000; + +// Kernel supports auto-mounting directory submounts +const SUBMOUNTS: u64 = 0x800_0000; + +// Filesystem responsible for clearing security.capability xattr and setuid/setgid bits. +const HANDLE_KILLPRIV_V2: u64 = 0x1000_0000; + +// This flag indicates whether the fuse_init_in is extended +const INIT_EXT: u64 = 0x4000_0000; + +// This flag indicates whether the guest kernel enable per-file dax +const PERFILE_DAX: u64 = 0x2_0000_0000; + +// this flag indicates whether the guest kernel enable resend +const HAS_RESEND: u64 = 1_u64 << 39; + +// This flag indicates whether to enable fd-passthrough. It was defined in the +// Anolis kernel but not in the upstream kernel. To avoid collision, we'll set +// it to the most significant bit. +const FD_PASSTHROUGH: u64 = 0x8000_0000_0000_0000; + +/** + * + * fuse_attr flags + * + * upstream kernel use (1 << 0) as FUSE_ATTR_SUBMOUNT, + * so FUSE_ATTR_DAX will use (1 << 1) + * + */ +/// This attribute indicates whether the file supports dax in per-file DAX mode +pub const FUSE_ATTR_DAX: u32 = 1 << 1; + +bitflags! { + /// A bitfield passed in as a parameter to and returned from the `init` method of the + /// `FileSystem` trait. + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub struct FsOptions: u64 { + /// Indicates that the filesystem supports asynchronous read requests. + /// + /// If this capability is not requested/available, the kernel will ensure that there is at + /// most one pending read request per file-handle at any time, and will attempt to order + /// read requests by increasing offset. + /// + /// This feature is enabled by default when supported by the kernel. + const ASYNC_READ = ASYNC_READ; + + /// Indicates that the filesystem supports "remote" locking. + /// + /// This feature is not enabled by default and should only be set if the filesystem + /// implements the `getlk` and `setlk` methods of the `FileSystem` trait. + const POSIX_LOCKS = POSIX_LOCKS; + + /// Kernel sends file handle for fstat, etc... (not yet supported). + const FILE_OPS = FILE_OPS; + + /// Indicates that the filesystem supports the `O_TRUNC` open flag. If disabled, and an + /// application specifies `O_TRUNC`, fuse first calls `setattr` to truncate the file and + /// then calls `open` with `O_TRUNC` filtered out. + /// + /// This feature is enabled by default when supported by the kernel. + const ATOMIC_O_TRUNC = ATOMIC_O_TRUNC; + + /// Indicates that the filesystem supports lookups of "." and "..". + /// + /// This feature is disabled by default. + const EXPORT_SUPPORT = EXPORT_SUPPORT; + + /// FileSystem can handle write size larger than 4kB. + const BIG_WRITES = BIG_WRITES; + + /// Indicates that the kernel should not apply the umask to the file mode on create + /// operations. + /// + /// This feature is disabled by default. + const DONT_MASK = DONT_MASK; + + /// Indicates that the server should try to use `splice(2)` when writing to the fuse device. + /// This may improve performance. + /// + /// This feature is not currently supported. + const SPLICE_WRITE = SPLICE_WRITE; + + /// Indicates that the server should try to move pages instead of copying when writing to / + /// reading from the fuse device. This may improve performance. + /// + /// This feature is not currently supported. + const SPLICE_MOVE = SPLICE_MOVE; + + /// Indicates that the server should try to use `splice(2)` when reading from the fuse + /// device. This may improve performance. + /// + /// This feature is not currently supported. + const SPLICE_READ = SPLICE_READ; + + /// If set, then calls to `flock` will be emulated using POSIX locks and must + /// then be handled by the filesystem's `setlock()` handler. + /// + /// If not set, `flock` calls will be handled by the FUSE kernel module internally (so any + /// access that does not go through the kernel cannot be taken into account). + /// + /// This feature is disabled by default. + const FLOCK_LOCKS = FLOCK_LOCKS; + + /// Indicates that the filesystem supports ioctl's on directories. + /// + /// This feature is enabled by default when supported by the kernel. + const HAS_IOCTL_DIR = HAS_IOCTL_DIR; + + /// Traditionally, while a file is open the FUSE kernel module only asks the filesystem for + /// an update of the file's attributes when a client attempts to read beyond EOF. This is + /// unsuitable for e.g. network filesystems, where the file contents may change without the + /// kernel knowing about it. + /// + /// If this flag is set, FUSE will check the validity of the attributes on every read. If + /// the attributes are no longer valid (i.e., if the *attribute* timeout has expired) then + /// FUSE will first send another `getattr` request. If the new mtime differs from the + /// previous value, any cached file *contents* will be invalidated as well. + /// + /// This flag should always be set when available. If all file changes go through the + /// kernel, *attribute* validity should be set to a very large number to avoid unnecessary + /// `getattr()` calls. + /// + /// This feature is enabled by default when supported by the kernel. + const AUTO_INVAL_DATA = AUTO_INVAL_DATA; + + /// Indicates that the filesystem supports readdirplus. + /// + /// The feature is not enabled by default and should only be set if the filesystem + /// implements the `readdirplus` method of the `FileSystem` trait. + const DO_READDIRPLUS = DO_READDIRPLUS; + + /// Indicates that the filesystem supports adaptive readdirplus. + /// + /// If `DO_READDIRPLUS` is not set, this flag has no effect. + /// + /// If `DO_READDIRPLUS` is set and this flag is not set, the kernel will always issue + /// `readdirplus()` requests to retrieve directory contents. + /// + /// If `DO_READDIRPLUS` is set and this flag is set, the kernel will issue both `readdir()` + /// and `readdirplus()` requests, depending on how much information is expected to be + /// required. + /// + /// This feature is not enabled by default and should only be set if the file system + /// implements both the `readdir` and `readdirplus` methods of the `FileSystem` trait. + const READDIRPLUS_AUTO = READDIRPLUS_AUTO; + + /// Indicates that the filesystem supports asynchronous direct I/O submission. + /// + /// If this capability is not requested/available, the kernel will ensure that there is at + /// most one pending read and one pending write request per direct I/O file-handle at any + /// time. + /// + /// This feature is enabled by default when supported by the kernel. + const ASYNC_DIO = ASYNC_DIO; + + /// Indicates that writeback caching should be enabled. This means that individual write + /// request may be buffered and merged in the kernel before they are sent to the file + /// system. + /// + /// This feature is disabled by default. + const WRITEBACK_CACHE = WRITEBACK_CACHE; + + /// Indicates support for zero-message opens. If this flag is set in the `capable` parameter + /// of the `init` trait method, then the file system may return `ENOSYS` from the open() handler + /// to indicate success. Further attempts to open files will be handled in the kernel. (If + /// this flag is not set, returning ENOSYS will be treated as an error and signaled to the + /// caller). + /// + /// Setting (or not setting) the field in the `FsOptions` returned from the `init` method + /// has no effect. + const ZERO_MESSAGE_OPEN = NO_OPEN_SUPPORT; + + /// Indicates support for parallel directory operations. If this flag is unset, the FUSE + /// kernel module will ensure that lookup() and readdir() requests are never issued + /// concurrently for the same directory. + /// + /// This feature is enabled by default when supported by the kernel. + const PARALLEL_DIROPS = PARALLEL_DIROPS; + + /// Indicates that the file system is responsible for unsetting setuid and setgid bits when a + /// file is written, truncated, or its owner is changed. + /// + /// This feature is enabled by default when supported by the kernel. + const HANDLE_KILLPRIV = HANDLE_KILLPRIV; + + /// Indicates support for POSIX ACLs. + /// + /// If this feature is enabled, the kernel will cache and have responsibility for enforcing + /// ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for + /// updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and + /// ensuring inheritance of default ACLs when new filesystem nodes are created. Note that + /// this requires that the file system is able to parse and interpret the xattr + /// representation of ACLs. + /// + /// Enabling this feature implicitly turns on the `default_permissions` mount option (even + /// if it was not passed to mount(2)). + /// + /// This feature is disabled by default. + const POSIX_ACL = POSIX_ACL; + + /// Indicates support for fuse device abort error. + /// + /// If this feature is enabled, the kernel will return ECONNABORTED to daemon when a fuse + /// connection is aborted. Otherwise, ENODEV is returned. + /// + /// This feature is enabled by default. + const ABORT_ERROR = ABORT_ERROR; + + /// Indicate support for max number of req pages negotiation during INIT request handling. + /// + /// If this feature is enabled, FUSE INIT response init_out.max_pages will contain the max + /// number of req pages. + /// + /// This feature is enabled by default. + const MAX_PAGES = MAX_PAGES; + + /// Indicate support for kernel caching symlinks. + /// + /// If this feature is enabled, the kernel will cache symlink contents. + /// + /// This feature is enabled by default. + const CACHE_SYMLINKS = CACHE_SYMLINKS; + + /// Indicates support for zero-message opens. If this flag is set in the `capable` parameter + /// of the `init` trait method, then the file system may return `ENOSYS` from the opendir() + /// handler to indicate success. Further attempts to open files will be handled in the kernel + /// (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the + /// caller). + /// + /// Setting (or not setting) the field in the `FsOptions` returned from the `init` method + /// has no effect. + /// + /// This feature is enabled by default. + const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT; + + /// Indicates to kernel that it is fully responsible for data cache + /// invalidation, then the kernel won't invalidate files data cache on size + /// change and only truncate that cache to new size in case the size decreased. + /// + /// If this feature is enabled, FileSystem should notify kernel when a file's data is changed + /// outside of fuse. + /// + /// This feature is enabled by default. + const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA; + + /// Indicate support for byte alignment negotiation during INIT request handling. + /// + /// If this feature is enabled, the INIT response init_out.map_alignment contains byte alignment for + /// foffset and moffset fields in fuse_setupmapping_out and fuse_removemapping_one. + /// + /// This feature is enabled by default. + const MAP_ALIGNMENT = MAP_ALIGNMENT; + + /// Kernel supports the ATTR_SUBMOUNT flag. + const SUBMOUNTS = SUBMOUNTS; + + /// Filesystem responsible for clearing security.capability xattr and setuid/setgid bits. + /// 1. clear "security.capability" on write, truncate and chown unconditionally + /// 2. sgid is cleared only if group executable bit is set + /// 3. clear suid/sgid when one of the following is true: + /// -. setattr has FATTR_SIZE and FATTR_KILL_SUIDGID set. + /// -. setattr has FATTR_UID or FATTR_GID + /// -. open has O_TRUNC and FOPEN_IN_KILL_SUIDGID + /// -. create has O_TRUNC and FOPEN_IN_KILL_SUIDGID flag set. + /// -. write has WRITE_KILL_PRIV + const HANDLE_KILLPRIV_V2 = HANDLE_KILLPRIV_V2; + + /// Indicates the kernel support fuse fd passthrough. + const FD_PASSTHROUGH = FD_PASSTHROUGH; + + /// The fuse_init_in is extended. + const INIT_EXT = INIT_EXT; + + /// Indicates whether the guest kernel enable per-file dax + /// + /// If this feature is enabled, filesystem will notify guest kernel whether file + /// enable DAX by EntryOut.Attr.flags of inode when lookup + const PERFILE_DAX = PERFILE_DAX; + + /// indicates whether the kernel support resend inflight request + const HAS_RESEND = HAS_RESEND; + } +} + +// Release flags. +pub const RELEASE_FLUSH: u32 = 1; +pub const RELEASE_FLOCK_UNLOCK: u32 = 2; + +// Getattr flags. +pub const GETATTR_FH: u32 = 1; + +// Lock flags. +pub const LK_FLOCK: u32 = 1; + +// Write flags. + +/// Delayed write from page cache, file handle is guessed. +pub const WRITE_CACHE: u32 = 1; + +/// `lock_owner` field is valid. +pub const WRITE_LOCKOWNER: u32 = 2; + +/// kill suid and sgid bits +pub const WRITE_KILL_PRIV: u32 = 4; + +// Read flags. +pub const READ_LOCKOWNER: u32 = 2; + +// Ioctl flags. + +/// 32bit compat ioctl on 64bit machine +const IOCTL_COMPAT: u32 = 1; + +/// Not restricted to well-formed ioctls, retry allowed +const IOCTL_UNRESTRICTED: u32 = 2; + +/// Retry with new iovecs +const IOCTL_RETRY: u32 = 4; + +/// 32bit ioctl +const IOCTL_32BIT: u32 = 8; + +/// Is a directory +const IOCTL_DIR: u32 = 16; + +/// x32 compat ioctl on 64bit machine (64bit time_t) +const IOCTL_COMPAT_X32: u32 = 32; + +/// Maximum of in_iovecs + out_iovecs +const IOCTL_MAX_IOV: u32 = 256; + +bitflags! { + pub struct IoctlFlags: u32 { + /// 32bit compat ioctl on 64bit machine + const IOCTL_COMPAT = IOCTL_COMPAT; + + /// Not restricted to well-formed ioctls, retry allowed + const IOCTL_UNRESTRICTED = IOCTL_UNRESTRICTED; + + /// Retry with new iovecs + const IOCTL_RETRY = IOCTL_RETRY; + + /// 32bit ioctl + const IOCTL_32BIT = IOCTL_32BIT; + + /// Is a directory + const IOCTL_DIR = IOCTL_DIR; + + /// x32 compat ioctl on 64bit machine (64bit time_t) + const IOCTL_COMPAT_X32 = IOCTL_COMPAT_X32; + + /// Maximum of in_iovecs + out_iovecs + const IOCTL_MAX_IOV = IOCTL_MAX_IOV; + } +} + +/// EntryOut flags +/// Entry is a submount root +pub const ATTR_SUBMOUNT: u32 = 1; + +/// Request poll notify. +pub const POLL_SCHEDULE_NOTIFY: u32 = 1; + +/// Fsync flags +/// +/// Sync data only, not metadata +pub const FSYNC_FDATASYNC: u32 = 1; + +/// The read buffer is required to be at least 8k, but may be much larger. +pub const FUSE_MIN_READ_BUFFER: u32 = 8192; + +pub const FUSE_COMPAT_ENTRY_OUT_SIZE: usize = 120; +pub const FUSE_COMPAT_ATTR_OUT_SIZE: usize = 96; +pub const FUSE_COMPAT_MKNOD_IN_SIZE: usize = 8; +pub const FUSE_COMPAT_WRITE_IN_SIZE: usize = 24; +pub const FUSE_COMPAT_STATFS_SIZE: usize = 48; +pub const FUSE_COMPAT_INIT_OUT_SIZE: usize = 8; +pub const FUSE_COMPAT_22_INIT_OUT_SIZE: usize = 24; + +// Message definitions follow. It is safe to implement ByteValued for all of these +// because they are POD types. + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Attr { + pub ino: u64, + pub size: u64, + pub blocks: u64, + pub atime: u64, + pub mtime: u64, + pub ctime: u64, + pub atimensec: u32, + pub mtimensec: u32, + pub ctimensec: u32, + pub mode: u32, + pub nlink: u32, + pub uid: u32, + pub gid: u32, + pub rdev: u32, + pub blksize: u32, + pub flags: u32, +} +unsafe impl ByteValued for Attr {} + +impl From for Attr { + fn from(st: stat64) -> Attr { + Attr::with_flags(st, 0) + } +} + +impl Attr { + pub fn with_flags(st: stat64, flags: u32) -> Attr { + Attr { + ino: st.st_ino, + size: st.st_size as u64, + blocks: st.st_blocks as u64, + atime: st.st_atime as u64, + mtime: st.st_mtime as u64, + ctime: st.st_ctime as u64, + atimensec: st.st_atime_nsec as u32, + mtimensec: st.st_mtime_nsec as u32, + ctimensec: st.st_ctime_nsec as u32, + mode: st.st_mode as u32, + // In Linux st.st_nlink is u64 on x86_64 and powerpc64, and u32 on other architectures + // ref: + // linux: https://github.com/rust-lang/rust/blob/1.69.0/library/std/src/os/linux/raw.rs#L333 + #[allow(clippy::unnecessary_cast)] + nlink: st.st_nlink as u32, + uid: st.st_uid, + gid: st.st_gid, + rdev: st.st_rdev as u32, + blksize: st.st_blksize as u32, + flags, + } + } +} + +impl From for stat64 { + fn from(attr: Attr) -> stat64 { + // Safe because we are zero-initializing a struct + let mut out: stat64 = unsafe { mem::zeroed() }; + out.st_ino = attr.ino; + out.st_size = attr.size as i64; + out.st_blocks = attr.blocks as i64; + out.st_atime = attr.atime as i64; + out.st_mtime = attr.mtime as i64; + out.st_ctime = attr.ctime as i64; + out.st_atime_nsec = attr.atimensec as i64; + out.st_mtime_nsec = attr.mtimensec as i64; + out.st_ctime_nsec = attr.ctimensec as i64; + out.st_mode = attr.mode as mode_t; + out.st_nlink = attr.nlink as nlink_t; + out.st_uid = attr.uid; + out.st_gid = attr.gid; + out.st_rdev = attr.rdev as dev_t; + out.st_blksize = attr.blksize as blksize_t; + + out + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Kstatfs { + pub blocks: u64, + pub bfree: u64, + pub bavail: u64, + pub files: u64, + pub ffree: u64, + pub bsize: u32, + pub namelen: u32, + pub frsize: u32, + pub padding: u32, + pub spare: [u32; 6], +} +unsafe impl ByteValued for Kstatfs {} + +impl From for Kstatfs { + fn from(st: statvfs64) -> Self { + Kstatfs { + blocks: st.f_blocks, + bfree: st.f_bfree, + bavail: st.f_bavail, + files: st.f_files, + ffree: st.f_ffree, + bsize: st.f_bsize as u32, + namelen: st.f_namemax as u32, + frsize: st.f_frsize as u32, + ..Default::default() + } + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FileLock { + pub start: u64, + pub end: u64, + pub type_: u32, + pub pid: u32, /* tgid */ +} +unsafe impl ByteValued for FileLock {} + +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum Opcode { + Lookup = 1, + Forget = 2, /* No Reply */ + Getattr = 3, + Setattr = 4, + Readlink = 5, + Symlink = 6, + Mknod = 8, + Mkdir = 9, + Unlink = 10, + Rmdir = 11, + Rename = 12, + Link = 13, + Open = 14, + Read = 15, + Write = 16, + Statfs = 17, + Release = 18, + Fsync = 20, + Setxattr = 21, + Getxattr = 22, + Listxattr = 23, + Removexattr = 24, + Flush = 25, + Init = 26, + Opendir = 27, + Readdir = 28, + Releasedir = 29, + Fsyncdir = 30, + Getlk = 31, + Setlk = 32, + Setlkw = 33, + Access = 34, + Create = 35, + Interrupt = 36, + Bmap = 37, + Destroy = 38, + Ioctl = 39, + Poll = 40, + NotifyReply = 41, + BatchForget = 42, + Fallocate = 43, + Readdirplus = 44, + Rename2 = 45, + Lseek = 46, + CopyFileRange = 47, + SetupMapping = 48, + RemoveMapping = 49, + MaxOpcode = 50, + + /* Reserved opcodes: helpful to detect structure endian-ness in case of e.g. virtiofs */ + CuseInitBswapReserved = 1_048_576, /* CUSE_INIT << 8 */ + InitBswapReserved = 436_207_616, /* FUSE_INIT << 24 */ +} + +impl From for Opcode { + fn from(op: u32) -> Opcode { + if op >= Opcode::MaxOpcode as u32 { + return Opcode::MaxOpcode; + } + unsafe { mem::transmute(op) } + } +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum NotifyOpcode { + Poll = 1, + InvalInode = 2, + InvalEntry = 3, + Store = 4, + Retrieve = 5, + Delete = 6, + Resend = 7, + CodeMax = 8, +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct EntryOut { + pub nodeid: u64, /* Inode ID */ + pub generation: u64, /* Inode generation: nodeid:gen must be unique for the fs's lifetime */ + pub entry_valid: u64, /* Cache timeout for the name */ + pub attr_valid: u64, /* Cache timeout for the attributes */ + pub entry_valid_nsec: u32, + pub attr_valid_nsec: u32, + pub attr: Attr, +} +unsafe impl ByteValued for EntryOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ForgetIn { + pub nlookup: u64, +} +unsafe impl ByteValued for ForgetIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ForgetOne { + pub nodeid: u64, + pub nlookup: u64, +} +unsafe impl ByteValued for ForgetOne {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct BatchForgetIn { + pub count: u32, + pub dummy: u32, +} +unsafe impl ByteValued for BatchForgetIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct GetattrIn { + pub flags: u32, + pub dummy: u32, + pub fh: u64, +} +unsafe impl ByteValued for GetattrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct AttrOut { + pub attr_valid: u64, /* Cache timeout for the attributes */ + pub attr_valid_nsec: u32, + pub dummy: u32, + pub attr: Attr, +} +unsafe impl ByteValued for AttrOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct MknodIn { + pub mode: u32, + pub rdev: u32, + pub umask: u32, + pub padding: u32, +} +unsafe impl ByteValued for MknodIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct MkdirIn { + pub mode: u32, + pub umask: u32, +} +unsafe impl ByteValued for MkdirIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct RenameIn { + pub newdir: u64, +} +unsafe impl ByteValued for RenameIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Rename2In { + pub newdir: u64, + pub flags: u32, + pub padding: u32, +} +unsafe impl ByteValued for Rename2In {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct LinkIn { + pub oldnodeid: u64, +} +unsafe impl ByteValued for LinkIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct SetattrIn { + pub valid: u32, + pub padding: u32, + pub fh: u64, + pub size: u64, + pub lock_owner: u64, + pub atime: u64, + pub mtime: u64, + pub ctime: u64, + pub atimensec: u32, + pub mtimensec: u32, + pub ctimensec: u32, + pub mode: u32, + pub unused4: u32, + pub uid: u32, + pub gid: u32, + pub unused5: u32, +} +unsafe impl ByteValued for SetattrIn {} + +impl From for stat64 { + fn from(setattr: SetattrIn) -> stat64 { + // Safe because we are zero-initializing a struct with only POD fields. + let mut out: stat64 = unsafe { mem::zeroed() }; + out.st_mode = setattr.mode as mode_t; + out.st_uid = setattr.uid; + out.st_gid = setattr.gid; + out.st_size = setattr.size as i64; + out.st_atime = setattr.atime as i64; + out.st_mtime = setattr.mtime as i64; + out.st_ctime = setattr.ctime as i64; + out.st_atime_nsec = i64::from(setattr.atimensec); + out.st_mtime_nsec = i64::from(setattr.mtimensec); + out.st_ctime_nsec = i64::from(setattr.ctimensec); + + out + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct OpenIn { + pub flags: u32, + pub fuse_flags: u32, +} +unsafe impl ByteValued for OpenIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct CreateIn { + pub flags: u32, + pub mode: u32, + pub umask: u32, + pub fuse_flags: u32, +} +unsafe impl ByteValued for CreateIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct OpenOut { + pub fh: u64, + pub open_flags: u32, + pub passthrough: u32, +} +unsafe impl ByteValued for OpenOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ReleaseIn { + pub fh: u64, + pub flags: u32, + pub release_flags: u32, + pub lock_owner: u64, +} +unsafe impl ByteValued for ReleaseIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FlushIn { + pub fh: u64, + pub unused: u32, + pub padding: u32, + pub lock_owner: u64, +} +unsafe impl ByteValued for FlushIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ReadIn { + pub fh: u64, + pub offset: u64, + pub size: u32, + pub read_flags: u32, + pub lock_owner: u64, + pub flags: u32, + pub padding: u32, +} +unsafe impl ByteValued for ReadIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct WriteIn { + pub fh: u64, + pub offset: u64, + pub size: u32, + pub fuse_flags: u32, + pub lock_owner: u64, + pub flags: u32, + pub padding: u32, +} +unsafe impl ByteValued for WriteIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct WriteOut { + pub size: u32, + pub padding: u32, +} +unsafe impl ByteValued for WriteOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct StatfsOut { + pub st: Kstatfs, +} +unsafe impl ByteValued for StatfsOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FsyncIn { + pub fh: u64, + pub fsync_flags: u32, + pub padding: u32, +} +unsafe impl ByteValued for FsyncIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct SetxattrIn { + pub size: u32, + pub flags: u32, +} +unsafe impl ByteValued for SetxattrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct GetxattrIn { + pub size: u32, + pub padding: u32, +} +unsafe impl ByteValued for GetxattrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct GetxattrOut { + pub size: u32, + pub padding: u32, +} +unsafe impl ByteValued for GetxattrOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct LkIn { + pub fh: u64, + pub owner: u64, + pub lk: FileLock, + pub lk_flags: u32, + pub padding: u32, +} +unsafe impl ByteValued for LkIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct LkOut { + pub lk: FileLock, +} +unsafe impl ByteValued for LkOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct AccessIn { + pub mask: u32, + pub padding: u32, +} +unsafe impl ByteValued for AccessIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct InitIn { + pub major: u32, + pub minor: u32, + pub max_readahead: u32, + pub flags: u32, +} +unsafe impl ByteValued for InitIn {} + +//The flag has been extended to 64 bit since fuse 7.36 +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct InitIn2 { + pub flags2: u32, + pub unused: [u32; 11], +} +unsafe impl ByteValued for InitIn2 {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct InitOut { + pub major: u32, + pub minor: u32, + pub max_readahead: u32, + pub flags: u32, + pub max_background: u16, + pub congestion_threshold: u16, + pub max_write: u32, + pub time_gran: u32, + pub max_pages: u16, + pub map_alignment: u16, + pub flags2: u32, + pub unused: [u32; 7], +} +unsafe impl ByteValued for InitOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct InterruptIn { + pub unique: u64, +} +unsafe impl ByteValued for InterruptIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct BmapIn { + pub block: u64, + pub blocksize: u32, + pub padding: u32, +} +unsafe impl ByteValued for BmapIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct BmapOut { + pub block: u64, +} +unsafe impl ByteValued for BmapOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct IoctlIn { + pub fh: u64, + pub flags: u32, + pub cmd: u32, + pub arg: u64, + pub in_size: u32, + pub out_size: u32, +} +unsafe impl ByteValued for IoctlIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct IoctlIovec { + pub base: u64, + pub len: u64, +} +unsafe impl ByteValued for IoctlIovec {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct IoctlOut { + pub result: i32, + pub flags: u32, + pub in_iovs: u32, + pub out_iovs: u32, +} +unsafe impl ByteValued for IoctlOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct PollIn { + pub fh: u64, + pub kh: u64, + pub flags: u32, + pub events: u32, +} +unsafe impl ByteValued for PollIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct PollOut { + pub revents: u32, + pub padding: u32, +} +unsafe impl ByteValued for PollOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct NotifyPollWakeupOut { + pub kh: u64, +} +unsafe impl ByteValued for NotifyPollWakeupOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FallocateIn { + pub fh: u64, + pub offset: u64, + pub length: u64, + pub mode: u32, + pub padding: u32, +} +unsafe impl ByteValued for FallocateIn {} + +#[repr(C)] +#[derive(Default, Copy, Clone)] +pub struct InHeader { + pub len: u32, + pub opcode: u32, + pub unique: u64, + pub nodeid: u64, + pub uid: u32, + pub gid: u32, + pub pid: u32, + pub padding: u32, +} +unsafe impl ByteValued for InHeader {} + +impl Debug for InHeader { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "InHeader {{ len: {}, opcode: {}, unique: {}, nodeid: 0x{:x}, uid: {}, gid: {}, pid: {}, padding: {} }}", + self.len, self.opcode, self.unique, self.nodeid, self.uid, self.gid, self.pid, self.padding + ) + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct OutHeader { + pub len: u32, + pub error: i32, + pub unique: u64, +} +unsafe impl ByteValued for OutHeader {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Dirent { + pub ino: u64, + pub off: u64, + pub namelen: u32, + pub type_: u32, + // char name[]; +} +unsafe impl ByteValued for Dirent {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Direntplus { + pub entry_out: EntryOut, + pub dirent: Dirent, +} +unsafe impl ByteValued for Direntplus {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct NotifyInvalInodeOut { + pub ino: u64, + pub off: i64, + pub len: i64, +} +unsafe impl ByteValued for NotifyInvalInodeOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct NotifyInvalEntryOut { + pub parent: u64, + pub namelen: u32, + pub padding: u32, +} +unsafe impl ByteValued for NotifyInvalEntryOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct NotifyDeleteOut { + pub parent: u64, + pub child: u64, + pub namelen: u32, + pub padding: u32, +} +unsafe impl ByteValued for NotifyDeleteOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct NotifyStoreOut { + pub nodeid: u64, + pub offset: u64, + pub size: u32, + pub padding: u32, +} +unsafe impl ByteValued for NotifyStoreOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct Notify_Retrieve_Out { + pub notify_unique: u64, + pub nodeid: u64, + pub offset: u64, + pub size: u32, + pub padding: u32, +} +unsafe impl ByteValued for Notify_Retrieve_Out {} + +/* Matches the size of fuse_write_in */ +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct NotifyRetrieveIn { + pub dummy1: u64, + pub offset: u64, + pub size: u32, + pub dummy2: u32, + pub dummy3: u64, + pub dummy4: u64, +} +unsafe impl ByteValued for NotifyRetrieveIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct LseekIn { + pub fh: u64, + pub offset: u64, + pub whence: u32, + pub padding: u32, +} +unsafe impl ByteValued for LseekIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct LseekOut { + pub offset: u64, +} +unsafe impl ByteValued for LseekOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +// Returns WriteOut +pub struct CopyFileRangeIn { + pub fh_in: u64, + pub offset_in: u64, + pub nodeid_out: u64, + pub fh_out: u64, + pub offset_out: u64, + pub len: u64, + pub flags: u64, +} +unsafe impl ByteValued for CopyFileRangeIn {} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_struct_size() { + assert_eq!(std::mem::size_of::(), 88); + assert_eq!(std::mem::size_of::(), 80); + assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::(), 128); + assert_eq!(std::mem::size_of::(), 8); + assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 8); + assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 104); + assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 8); + assert_eq!(std::mem::size_of::(), 40); + assert_eq!(std::mem::size_of::(), 16); + } + + #[test] + fn test_byte_valued() { + let buf = [ + 0x1u8, 0x2u8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5u8, 0x6u8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + let forget = match ForgetOne::from_slice(&buf) { + Some(f) => f, + None => { + panic!("Failed to parse ForgetOne from buffer: {:?}", buf) + } + }; + + assert_eq!(forget.nodeid, 0x201u64); + assert_eq!(forget.nlookup, 0x605u64); + + let forget = ForgetOne { + nodeid: 0x201u64, + nlookup: 0x605u64, + }; + let buf = forget.as_slice(); + assert_eq!(buf[0], 0x1u8); + assert_eq!(buf[1], 0x2u8); + assert_eq!(buf[8], 0x5u8); + assert_eq!(buf[9], 0x6u8); + } +} diff --git a/src/abi/mod.rs b/src/abi/mod.rs index e5bfae81..b7e3c5d2 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -8,6 +8,11 @@ #[path = "fuse_abi_linux.rs"] pub mod fuse_abi; +/// FreeBSD Fuse Application Binary Interfaces. +#[cfg(target_os = "freebsd")] +#[path = "fuse_abi_freebsd.rs"] +pub mod fuse_abi; + /// MacOS Fuse Application Binary Interfaces. #[cfg(target_os = "macos")] #[path = "fuse_abi_macos.rs"] diff --git a/src/api/server/mod.rs b/src/api/server/mod.rs index 31fa27c6..03cb6a98 100644 --- a/src/api/server/mod.rs +++ b/src/api/server/mod.rs @@ -37,6 +37,11 @@ mod sync_io; /// Maximum buffer size of FUSE requests. #[cfg(target_os = "linux")] pub const MAX_BUFFER_SIZE: u32 = 1 << 20; + +/// Maximum buffer size of FUSE requests. +#[cfg(target_os = "freebsd")] +pub const MAX_BUFFER_SIZE: u32 = 1 << 20; + /// Maximum buffer size of FUSE requests. #[cfg(target_os = "macos")] pub const MAX_BUFFER_SIZE: u32 = 1 << 25; diff --git a/src/api/server/sync_io.rs b/src/api/server/sync_io.rs index a38d2804..1f5b6edc 100644 --- a/src/api/server/sync_io.rs +++ b/src/api/server/sync_io.rs @@ -705,6 +705,8 @@ impl Server { #[cfg(target_os = "macos")] let flags_u64 = flags as u64; + #[cfg(target_os = "freebsd")] + let flags_u64 = flags as u64; #[cfg(target_os = "linux")] let mut flags_u64 = flags as u64; #[cfg(target_os = "linux")] diff --git a/src/api/vfs/mod.rs b/src/api/vfs/mod.rs index a373e3ec..31cf01ca 100644 --- a/src/api/vfs/mod.rs +++ b/src/api/vfs/mod.rs @@ -298,6 +298,9 @@ impl Default for VfsOptions { } } + #[cfg(target_os = "freebsd")] + fn default() -> Self { todo!() } + #[cfg(target_os = "macos")] fn default() -> Self { let out_opts = FsOptions::ASYNC_READ | FsOptions::BIG_WRITES | FsOptions::ATOMIC_O_TRUNC; @@ -1489,6 +1492,8 @@ mod tests { #[cfg(target_os = "linux")] let in_opts = FsOptions::ASYNC_READ | FsOptions::ZERO_MESSAGE_OPEN | FsOptions::ZERO_MESSAGE_OPENDIR; + #[cfg(target_os = "freebsd")] + let in_opts = FsOptions::ASYNC_READ; #[cfg(target_os = "macos")] let in_opts = FsOptions::ASYNC_READ; vfs.init(in_opts).unwrap(); diff --git a/src/common/file_traits.rs b/src/common/file_traits.rs index 965925c5..1de6255f 100644 --- a/src/common/file_traits.rs +++ b/src/common/file_traits.rs @@ -21,7 +21,7 @@ use std::os::unix::io::AsRawFd; use libc::{c_int, c_void, read, readv, size_t, write, writev}; use crate::file_buf::FileVolatileSlice; -use crate::{off64_t, pread64, preadv64, pwrite64, pwritev64}; +use crate::abi::fuse_abi::{off64_t, pread64, preadv64, pwrite64, pwritev64}; /// A trait for setting the size of a file. /// diff --git a/src/transport/fusedev/freebsd_session.rs b/src/transport/fusedev/freebsd_session.rs new file mode 100644 index 00000000..61b25b69 --- /dev/null +++ b/src/transport/fusedev/freebsd_session.rs @@ -0,0 +1,891 @@ +//! FUSE session management. +//! +//! A FUSE channel is a FUSE request handling context that takes care of handling FUSE requests +//! sequentially. A FUSE session is a connection from a FUSE mountpoint to a FUSE server daemon. +//! A FUSE session can have multiple FUSE channels so that FUSE requests are handled in parallel. + +use std::fs::{File, OpenOptions}; +use std::os::fd::AsFd; +use std::os::unix::fs::PermissionsExt; +use std::os::unix::io::AsRawFd; +use std::os::unix::net::UnixStream; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use mio::unix::SourceFd; +use mio::{Events, Interest, Poll, Token, Waker}; +use nix::errno::Errno; +use nix::fcntl::{FcntlArg, FdFlag, fcntl}; +use nix::mount::{MntFlags, Nmount, unmount}; +use nix::poll::{PollFd, PollFlags, PollTimeout, poll}; +use nix::unistd::{getgid, getuid, read}; + +use super::{ + super::pagesize, + Error::{IoError, SessionFailure}, + FUSE_HEADER_SIZE, FUSE_KERN_BUF_PAGES, FuseBuf, FuseDevWriter, Reader, Result, +}; + +// These follows definition from libfuse. +const POLL_EVENTS_CAPACITY: usize = 1024; + +const FUSE_DEVICE: &str = "/dev/fuse"; +const FUSE_FSTYPE: &str = "fuse"; +const FUSERMOUNT_BIN: &str = "fusermount3"; + +const EXIT_FUSE_EVENT: Token = Token(0); +const FUSE_DEV_EVENT: Token = Token(1); + +/// A fuse session manager to manage the connection with the in kernel fuse driver. +pub struct FuseSession { + mountpoint: PathBuf, + fsname: String, + subtype: String, + file: Option, + // Socket to keep alive / drop for fusermount's auto_unmount. + keep_alive: Option, + bufsize: usize, + readonly: bool, + wakers: Mutex>>, + auto_unmount: bool, + allow_other: bool, + target_mntns: Option, + // fusermount binary, default to fusermount3 + fusermount: String, +} + +impl FuseSession { + /// Create a new fuse session, without mounting/connecting to the in kernel fuse driver. + pub fn new( + mountpoint: &Path, + fsname: &str, + subtype: &str, + readonly: bool, + ) -> Result { + FuseSession::new_with_autounmount(mountpoint, fsname, subtype, readonly, false) + } + + /// Create a new fuse session, without mounting/connecting to the in kernel fuse driver. + pub fn new_with_autounmount( + mountpoint: &Path, + fsname: &str, + subtype: &str, + readonly: bool, + auto_unmount: bool, + ) -> Result { + let dest = mountpoint + .canonicalize() + .map_err(|_| SessionFailure(format!("invalid mountpoint {mountpoint:?}")))?; + if !dest.is_dir() { + return Err(SessionFailure(format!("{dest:?} is not a directory"))); + } + + Ok(FuseSession { + mountpoint: dest, + fsname: fsname.to_owned(), + subtype: subtype.to_owned(), + file: None, + keep_alive: None, + bufsize: FUSE_KERN_BUF_PAGES * pagesize() + FUSE_HEADER_SIZE, + readonly, + wakers: Mutex::new(Vec::new()), + auto_unmount, + target_mntns: None, + fusermount: FUSERMOUNT_BIN.to_string(), + allow_other: true, + }) + } + + /// Set the target pid of mount namespace of the fuse session mount, the fuse will be mounted + /// under the given mnt ns. + pub fn set_target_mntns(&mut self, pid: Option) { + self.target_mntns = pid; + } + + /// Set fusermount binary, default to fusermount3. + pub fn set_fusermount(&mut self, bin: &str) { + self.fusermount = bin.to_string(); + } + + /// Set the allow_other mount option. This allows other users than the one mounting the + /// filesystem to access the filesystem. However, this option is usually restricted to the root + /// user unless configured otherwise. + pub fn set_allow_other(&mut self, allow_other: bool) { + self.allow_other = allow_other; + } + + /// Get current fusermount binary. + pub fn get_fusermount(&self) -> &str { + self.fusermount.as_str() + } + + /// Expose the associated FUSE session file. + pub fn get_fuse_file(&self) -> Option<&File> { + self.file.as_ref() + } + + /// Force setting the associated FUSE session file. + pub fn set_fuse_file(&mut self, file: File) { + self.file = Some(file); + } + + /// Clone fuse file using ioctl FUSE_DEV_IOC_CLONE. + pub fn clone_fuse_file(&self) -> Result { + let mut old_fd = self + .file + .as_ref() + .ok_or(SessionFailure( + "fuse session file doesn't exist".to_string(), + ))? + .as_raw_fd(); + + let cloned_file = OpenOptions::new() + .create(false) + .read(true) + .write(true) + .open(FUSE_DEVICE) + .map_err(|e| SessionFailure(format!("open {FUSE_DEVICE}: {e}")))?; + + // define the function which invokes "ioctl FUSE_DEV_IOC_CLONE" + // refer: https://github.com/torvalds/linux/blob/c42d9eeef8e5ba9292eda36fd8e3c11f35ee065c/include/uapi/linux/fuse.h#L1051-L1052 + // #define FUSE_DEV_IOC_MAGIC 229 + // #define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) + nix::ioctl_read!(clone_fuse_fd, 229, 0, i32); + + unsafe { clone_fuse_fd(cloned_file.as_raw_fd(), (&mut old_fd) as *mut i32) } + .map_err(|e| SessionFailure(format!("failed to clone fuse file: {:?}", e)))?; + + Ok(cloned_file) + } + + /// Get the mountpoint of the session. + pub fn mountpoint(&self) -> &Path { + &self.mountpoint + } + + /// Get the file system name of the session. + pub fn fsname(&self) -> &str { + &self.fsname + } + + /// Get the subtype of the session. + pub fn subtype(&self) -> &str { + &self.subtype + } + + /// Get the default buffer size of the session. + pub fn bufsize(&self) -> usize { + self.bufsize + } + + /// Mount the fuse mountpoint, building connection with the in kernel fuse driver. + pub fn mount(&mut self) -> Result<()> { + let mut flags = MntFlags::MNT_NOSUID | MntFlags::MNT_NOATIME; + if self.readonly { + flags |= MntFlags::MNT_RDONLY; + } + let (file, socket) = fuse_kern_mount( + &self.mountpoint, + &self.fsname, + &self.subtype, + flags, + self.auto_unmount, + self.allow_other, + self.target_mntns, + &self.fusermount, + )?; + + //fcntl(file.as_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) + // .map_err(|e| SessionFailure(format!("set fd nonblocking: {e}")))?; + self.file = Some(file); + self.keep_alive = socket; + + Ok(()) + } + + /// Destroy a fuse session. + pub fn umount(&mut self) -> Result<()> { + // If we have a keep_alive socket, just drop it, + // and let fusermount do the unmount. + if let (None, Some(file)) = (self.keep_alive.take(), self.file.take()) { + if let Some(mountpoint) = self.mountpoint.to_str() { + fuse_kern_umount(mountpoint, file, self.fusermount.as_str()) + } else { + Err(SessionFailure("invalid mountpoint".to_string())) + } + } else { + Ok(()) + } + } + + /// Create a new fuse message channel. + pub fn new_channel(&self) -> Result { + if let Some(file) = &self.file { + let file = file + .try_clone() + .map_err(|e| SessionFailure(format!("dup fd: {e}")))?; + let channel = FuseChannel::new(file, self.bufsize)?; + let waker = channel.get_waker(); + self.add_waker(waker)?; + + Ok(channel) + } else { + Err(SessionFailure("invalid fuse session".to_string())) + } + } + + /// Create a new fuse message channel with a specific buffer size. + pub fn with_writer(&mut self, f: F) + where + F: FnOnce(FuseDevWriter), + { + if let Some(file) = &self.file { + let fd = file.as_fd(); + let mut buf = vec![0x0u8; self.bufsize]; + let writer = FuseDevWriter::new(fd, &mut buf).unwrap(); + f(writer); + } + } + + /// Wake channel loop and exit + pub fn wake(&self) -> Result<()> { + let wakers = self + .wakers + .lock() + .map_err(|e| SessionFailure(format!("lock wakers: {e}")))?; + for waker in wakers.iter() { + waker + .wake() + .map_err(|e| SessionFailure(format!("wake channel: {e}")))?; + } + Ok(()) + } + + fn add_waker(&self, waker: Arc) -> Result<()> { + let mut wakers = self + .wakers + .lock() + .map_err(|e| SessionFailure(format!("lock wakers: {e}")))?; + wakers.push(waker); + Ok(()) + } +} + +impl Drop for FuseSession { + fn drop(&mut self) { + let _ = self.umount(); + } +} + +/// A fuse channel abstraction. +/// +/// Each session can hold multiple channels. +pub struct FuseChannel { + file: File, + poll: Poll, + waker: Arc, + buf: Vec, +} + +impl FuseChannel { + fn new(file: File, bufsize: usize) -> Result { + let poll = Poll::new().map_err(|e| SessionFailure(format!("poll create: {e}")))?; + let registry = poll.registry(); + registry + .register( + &mut SourceFd(&file.as_raw_fd()), + FUSE_DEV_EVENT, + Interest::READABLE, + ) + .unwrap(); + let waker = Waker::new(registry, EXIT_FUSE_EVENT) + .map_err(|e| SessionFailure(format!("poll register session fd: {e}")))?; + let waker = Arc::new(waker); + + //let kq = Kqueue::new().unwrap(); + + Ok(FuseChannel { + file, + poll, + waker, + buf: vec![0x0u8; bufsize], + }) + } + + fn get_waker(&self) -> Arc { + self.waker.clone() + } + + /// Get next available FUSE request from the underlying fuse device file. + /// + /// Returns: + /// - Ok(None): signal has pending on the exiting event channel + /// - Ok(Some((reader, writer))): reader to receive request and writer to send reply + /// - Err(e): error message + pub fn get_request(&mut self) -> Result> { + let mut events = Events::with_capacity(POLL_EVENTS_CAPACITY); + let mut need_exit = false; + loop { + let mut fusereq_available = false; + match self.poll.poll(&mut events, None) { + Ok(_) => {} + Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + Err(e) => return Err(SessionFailure(format!("epoll wait: {e}"))), + } + + for event in events.iter() { + if event.is_readable() { + match event.token() { + EXIT_FUSE_EVENT => need_exit = true, + FUSE_DEV_EVENT => fusereq_available = true, + x => { + error!("unexpected epoll event"); + return Err(SessionFailure(format!("unexpected epoll event: {}", x.0))); + } + } + } else if event.is_error() { + info!("FUSE channel already closed!"); + return Err(SessionFailure("epoll error".to_string())); + } else { + // We should not step into this branch as other event is not registered. + panic!("unknown epoll result events"); + } + } + + // Handle wake up event first. We don't read the event fd so that a LEVEL triggered + // event can still be delivered to other threads/daemons. + if need_exit { + info!("Will exit from fuse service"); + return Ok(None); + } + if fusereq_available { + let fd = self.file.as_fd(); + match read(fd, &mut self.buf) { + Ok(len) => { + // ############################################### + // Note: it's a heavy hack to reuse the same underlying data + // buffer for both Reader and Writer, in order to reduce memory + // consumption. Here we assume Reader won't be used anymore once + // we start to write to the Writer. To get rid of this hack, + // just allocate a dedicated data buffer for Writer. + let buf = unsafe { + std::slice::from_raw_parts_mut(self.buf.as_mut_ptr(), self.buf.len()) + }; + // Reader::new() and Writer::new() should always return success. + let reader = + Reader::from_fuse_buffer(FuseBuf::new(&mut self.buf[..len])).unwrap(); + let writer = FuseDevWriter::new(fd, buf).unwrap(); + return Ok(Some((reader, writer))); + } + Err(e) => match e { + Errno::ENOENT => { + // ENOENT means the operation was interrupted, it's safe to restart + trace!("restart reading due to ENOENT"); + continue; + } + Errno::EAGAIN => { + trace!("restart reading due to EAGAIN"); + continue; + } + Errno::EINTR => { + trace!("syscall interrupted"); + continue; + } + Errno::ENODEV => { + info!("fuse filesystem umounted"); + return Ok(None); + } + e => { + warn! {"read fuse dev failed on fd {}: {}", fd.as_raw_fd(), e}; + return Err(SessionFailure(format!("read new request: {e:?}"))); + } + }, + } + } + } + } +} + +/// Mount a fuse file system +#[allow(clippy::too_many_arguments)] +fn fuse_kern_mount( + mountpoint: &Path, + fsname: &str, + subtype: &str, + flags: MntFlags, + auto_unmount: bool, + allow_other: bool, + target_mntns: Option, + fusermount: &str, +) -> Result<(File, Option)> { + let file = OpenOptions::new() + .create(false) + .read(true) + .write(true) + .open(FUSE_DEVICE) + .map_err(|e| SessionFailure(format!("open {FUSE_DEVICE}: {e}")))?; + let meta = mountpoint + .metadata() + .map_err(|e| SessionFailure(format!("stat {mountpoint:?}: {e}")))?; + // the current implementation of fuse-backend-rs uses a fixed buffer to store the fuse response, + // the default value of this buffer is as follows, but in fact, the kernel in the direct io path, + // the size of the request may be larger than the length of this buffer (this is determined by + // the max_read option to determine the maximum size of kernel requests, the default value is + // a very large number), which leads to the buffer is not enough to fill the read content, + // resulting in read failure. so here we limit the size of max_read to the length of our buffer, + // so that the fuse kernel will not send requests that exceed the length of the buffer. + // in virtiofs scene max_read can't be adjusted, his default is UINT_MAX, but we don't have to + // worry about it, because the buffer is allocated by the kernel driver, we just use this buffer + // to fill the response, so we don't need to do any adjustment. + let max_read = FUSE_KERN_BUF_PAGES * pagesize() + FUSE_HEADER_SIZE; + + let mut opts = format!( + "default_permissions,fd={},rootmode={:o},user_id={},group_id={},max_read={}", + file.as_raw_fd(), + meta.permissions().mode() & libc::S_IFMT as u32, + getuid(), + getgid(), + max_read + ); + if allow_other { + opts.push_str(",allow_other"); + } + let mut fstype = String::from(FUSE_FSTYPE); + if !subtype.is_empty() { + fstype.push('.'); + fstype.push_str(subtype); + } + + if let Some(mountpoint) = mountpoint.to_str() { + info!( + "mount source {} dest {} with fstype {} opts {} fd {}", + fsname, + mountpoint, + fstype, + opts, + file.as_raw_fd(), + ); + } + + // mount in another mntns requires mounting with fusermount, which is a new process, as + // multithreaded program is not allowed to join to another mntns, and the process running fuse + // session might be multithreaded. + if auto_unmount || target_mntns.is_some() { + fuse_fusermount_mount( + mountpoint, + fsname, + subtype, + opts, + flags, + auto_unmount, + target_mntns, + fusermount, + ) + } else { + let fd = format!("{}", file.as_raw_fd()); + Nmount::new() + .str_opt_owned("fstype", "fusefs") + .str_opt_owned("fspath", mountpoint) + .str_opt_owned("from", FUSE_DEVICE) + .str_opt_owned("fd", fd.as_str()) + .nmount(MntFlags::empty()) + .unwrap(); + Ok((file, None)) + /*match mount( + Some(fsname), + mountpoint, + Some(fstype.deref()), + flags, + Some(opts.deref()), + ) { + Ok(()) => Ok((file, None)), + Err(Errno::EPERM) => fuse_fusermount_mount( + mountpoint, + fsname, + subtype, + opts, + flags, + auto_unmount, + target_mntns, + fusermount, + ), + Err(e) => Err(SessionFailure(format!( + "failed to mount {mountpoint:?}: {e}" + ))), + }*/ + } +} + +fn msflags_to_string(flags: MntFlags) -> String { + [ + (MntFlags::MNT_RDONLY, ("rw", "ro")), + (MntFlags::MNT_NOSUID, ("suid", "nosuid")), + (MntFlags::MNT_NOEXEC, ("exec", "noexec")), + (MntFlags::MNT_SYNCHRONOUS, ("async", "sync")), + (MntFlags::MNT_NOATIME, ("atime", "noatime")), + ] + .map( + |(flag, (neg, pos))| { + if flags.contains(flag) { pos } else { neg } + }, + ) + .join(",") +} + +/// Mount a fuse file system with fusermount +#[allow(clippy::too_many_arguments)] +fn fuse_fusermount_mount( + mountpoint: &Path, + fsname: &str, + subtype: &str, + opts: String, + flags: MntFlags, + auto_unmount: bool, + target_mntns: Option, + fusermount: &str, +) -> Result<(File, Option)> { + let mut opts = vec![format!("fsname={fsname}"), opts, msflags_to_string(flags)]; + if !subtype.is_empty() { + opts.push(format!("subtype={subtype}")); + } + if auto_unmount { + opts.push("auto_unmount".to_owned()); + } + let opts = opts.join(","); + + let (send, _recv) = UnixStream::pair().unwrap(); + + // Keep the sending socket around after exec to pass to fusermount. + // When its partner recv closes, fusermount will unmount. + // Remove the close-on-exec flag from the socket, so we can pass it to + // fusermount. + fcntl(send.as_fd(), FcntlArg::F_SETFD(FdFlag::empty())) + .map_err(|e| SessionFailure(format!("Failed to remove close-on-exec flag: {e}")))?; + + let mut cmd = match target_mntns { + Some(pid) => { + let mut c = std::process::Command::new("nsenter"); + c.arg("-t") + .arg(format!("{}", pid)) + .arg("-m") + .arg(fusermount); + c + } + None => std::process::Command::new(fusermount), + }; + // Old version of fusermount doesn't support long --options, yet. + let mut proc = cmd + .env("_FUSE_COMMFD", format!("{}", send.as_raw_fd())) + .arg("-o") + .arg(opts) + .arg("--") + .arg(mountpoint) + .spawn() + .map_err(IoError)?; + + if auto_unmount { + std::thread::spawn(move || { + let _ = proc.wait(); + }); + } else { + match proc.wait().map_err(IoError)?.code() { + Some(0) => {} + exit_code => { + return Err(SessionFailure(format!( + "Unexpected exit code when running fusermount: {exit_code:?}" + ))); + } + } + } + drop(send); + todo!(); + + /*match vmm_sys_util::sock_ctrl_msg::ScmSocket::recv_with_fd(&recv, &mut [0u8; 8]).map_err( + |e| { + SessionFailure(format!( + "Unexpected error when receiving fuse file descriptor from fusermount: {}", + e + )) + }, + )? { + (_recv_bytes, Some(file)) => Ok((file, if auto_unmount { Some(recv) } else { None })), + (recv_bytes, None) => Err(SessionFailure(format!( + "fusermount did not send a file descriptor. We received {recv_bytes} bytes." + ))), + }*/ +} + +/// Umount a fuse file system +fn fuse_kern_umount(mountpoint: &str, file: File, fusermount: &str) -> Result<()> { + let mut fds = [PollFd::new(file.as_fd(), PollFlags::empty())]; + + if poll(&mut fds, PollTimeout::ZERO).is_ok() { + // POLLERR means the file system is already umounted, + // or the connection has been aborted via /sys/fs/fuse/connections/NNN/abort + if let Some(event) = fds[0].revents() { + if event == PollFlags::POLLERR { + return Ok(()); + } + } + } + + // Drop to close fuse session fd, otherwise synchronous umount can recurse into filesystem and + // cause deadlock. + drop(file); + match unmount(mountpoint, MntFlags::empty()) { + Ok(()) => Ok(()), + Err(Errno::EPERM) => fuse_fusermount_umount(mountpoint, fusermount), + Err(e) => Err(SessionFailure(format!( + "failed to umount {mountpoint}: {e}" + ))), + } +} + +/// Umount a fuse file system by fusermount helper +fn fuse_fusermount_umount(mountpoint: &str, fusermount: &str) -> Result<()> { + match std::process::Command::new(fusermount) + .arg("--unmount") + .arg("--quiet") + .arg("--lazy") + .arg("--") + .arg(mountpoint) + .status() + .map_err(IoError)? + .code() + { + Some(0) => Ok(()), + exit_code => Err(SessionFailure(format!( + "Unexpected exit code when unmounting via running fusermount: {exit_code:?}" + ))), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::path::Path; + use vmm_sys_util::tempdir::TempDir; + + #[test] + fn test_new_session() { + let se = FuseSession::new(Path::new("haha"), "foo", "bar", true); + assert!(se.is_err()); + + let dir = TempDir::new().unwrap(); + let se = FuseSession::new(dir.as_path(), "foo", "bar", false); + assert!(se.is_ok()); + } + + #[test] + fn test_new_channel() { + let fd = nix::unistd::dup(std::io::stdout()).unwrap(); + let file = File::from(fd); + let _ = FuseChannel::new(file, 3).unwrap(); + } + + #[test] + fn test_fusermount() { + let dir = TempDir::new().unwrap(); + let se = FuseSession::new(dir.as_path(), "foo", "bar", true); + assert!(se.is_ok()); + let mut se = se.unwrap(); + assert_eq!(se.get_fusermount(), FUSERMOUNT_BIN); + + se.set_fusermount("fusermount"); + assert_eq!(se.get_fusermount(), "fusermount"); + } + + #[test] + fn test_clone_fuse_file() { + let dir = TempDir::new().unwrap(); + let mut se = FuseSession::new(dir.as_path(), "foo", "bar", true).unwrap(); + se.mount().unwrap(); + + let cloned_file = se.clone_fuse_file().unwrap(); + assert!(cloned_file.as_raw_fd() > 0); + + se.umount().unwrap(); + se.set_fuse_file(cloned_file); + se.mount().unwrap(); + } +} + +#[cfg(feature = "async_io")] +pub use asyncio::FuseDevTask; + +#[cfg(feature = "async_io")] +/// Task context to handle fuse request in asynchronous mode. +mod asyncio { + use std::os::unix::io::RawFd; + use std::sync::Arc; + + use crate::api::filesystem::AsyncFileSystem; + use crate::api::server::Server; + use crate::transport::{FuseBuf, Reader, Writer}; + + /// Task context to handle fuse request in asynchronous mode. + /// + /// This structure provides a context to handle fuse request in asynchronous mode, including + /// the fuse fd, a internal buffer and a `Server` instance to serve requests. + /// + /// ## Examples + /// ```ignore + /// let buf_size = 0x1_0000; + /// let state = AsyncExecutorState::new(); + /// let mut task = FuseDevTask::new(buf_size, fuse_dev_fd, fs_server, state.clone()); + /// + /// // Run the task + /// executor.spawn(async move { task.poll_handler().await }); + /// + /// // Stop the task + /// state.quiesce(); + /// ``` + pub struct FuseDevTask { + fd: RawFd, + buf: Vec, + state: AsyncExecutorState, + server: Arc>, + } + + impl FuseDevTask { + /// Create a new fuse task context for asynchronous IO. + /// + /// # Parameters + /// - buf_size: size of buffer to receive requests from/send reply to the fuse fd + /// - fd: fuse device file descriptor + /// - server: `Server` instance to serve requests from the fuse fd + /// - state: shared state object to control the task object + /// + /// # Safety + /// The caller must ensure `fd` is valid during the lifetime of the returned task object. + pub fn new( + buf_size: usize, + fd: RawFd, + server: Arc>, + state: AsyncExecutorState, + ) -> Self { + FuseDevTask { + fd, + server, + state, + buf: vec![0x0u8; buf_size], + } + } + + /// Handler to process fuse requests in asynchronous mode. + /// + /// An async fn to handle requests from the fuse fd. It works in asynchronous IO mode when: + /// - receiving request from fuse fd + /// - handling requests by calling Server::async_handle_requests() + /// - sending reply to fuse fd + /// + /// The async fn repeatedly return Poll::Pending when polled until the state has been set + /// to quiesce mode. + pub async fn poll_handler(&mut self) { + // TODO: register self.buf as io uring buffers. + let drive = AsyncDriver::default(); + + while !self.state.quiescing() { + let result = AsyncUtil::read(drive.clone(), self.fd, &mut self.buf, 0).await; + match result { + Ok(len) => { + // ############################################### + // Note: it's a heavy hack to reuse the same underlying data + // buffer for both Reader and Writer, in order to reduce memory + // consumption. Here we assume Reader won't be used anymore once + // we start to write to the Writer. To get rid of this hack, + // just allocate a dedicated data buffer for Writer. + let buf = unsafe { + std::slice::from_raw_parts_mut(self.buf.as_mut_ptr(), self.buf.len()) + }; + // Reader::new() and Writer::new() should always return success. + let reader = + Reader::<()>::new(FuseBuf::new(&mut self.buf[0..len])).unwrap(); + let writer = Writer::new(self.fd, buf).unwrap(); + let result = unsafe { + self.server + .async_handle_message(drive.clone(), reader, writer, None, None) + .await + }; + + if let Err(e) = result { + // TODO: error handling + error!("failed to handle fuse request, {}", e); + } + } + Err(e) => { + // TODO: error handling + error!("failed to read request from fuse device fd, {}", e); + } + } + } + + // TODO: unregister self.buf as io uring buffers. + + // Report that the task has been quiesced. + self.state.report(); + } + } + + impl Clone for FuseDevTask { + fn clone(&self) -> Self { + FuseDevTask { + fd: self.fd, + server: self.server.clone(), + state: self.state.clone(), + buf: vec![0x0u8; self.buf.capacity()], + } + } + } + + #[cfg(test)] + mod tests { + use std::os::unix::io::AsRawFd; + + use super::*; + use crate::api::{Vfs, VfsOptions}; + use crate::async_util::{AsyncDriver, AsyncExecutor}; + + #[test] + fn test_fuse_task() { + let state = AsyncExecutorState::new(); + let fs = Vfs::::new(VfsOptions::default()); + let _server = Arc::new(Server::, AsyncDriver, ()>::new(fs)); + let file = vmm_sys_util::tempfile::TempFile::new().unwrap(); + let _fd = file.as_file().as_raw_fd(); + + let mut executor = AsyncExecutor::new(32); + executor.setup().unwrap(); + + /* + // Create three tasks, which could handle three concurrent fuse requests. + let mut task = FuseDevTask::new(0x1000, fd, server.clone(), state.clone()); + executor + .spawn(async move { task.poll_handler().await }) + .unwrap(); + let mut task = FuseDevTask::new(0x1000, fd, server.clone(), state.clone()); + executor + .spawn(async move { task.poll_handler().await }) + .unwrap(); + let mut task = FuseDevTask::new(0x1000, fd, server.clone(), state.clone()); + executor + .spawn(async move { task.poll_handler().await }) + .unwrap(); + */ + + for _i in 0..10 { + executor.run_once(false).unwrap(); + } + + // Set existing flag + state.quiesce(); + // Close the fusedev fd, so all pending async io requests will be aborted. + drop(file); + + for _i in 0..10 { + executor.run_once(false).unwrap(); + } + } + } +} diff --git a/src/transport/fusedev/mod.rs b/src/transport/fusedev/mod.rs index 6f08f50f..d31b43e3 100644 --- a/src/transport/fusedev/mod.rs +++ b/src/transport/fusedev/mod.rs @@ -26,6 +26,11 @@ mod linux_session; #[cfg(target_os = "linux")] pub use linux_session::*; +#[cfg(target_os = "freebsd")] +mod freebsd_session; +#[cfg(target_os = "freebsd")] +pub use freebsd_session::*; + #[cfg(all(target_os = "macos", not(feature = "fuse-t")))] mod macos_session; #[cfg(all(target_os = "macos", not(feature = "fuse-t")))] @@ -192,15 +197,15 @@ impl<'a, S: BitmapSlice> FuseDevWriter<'a, S> { ) -> io::Result { self.check_available_space(count)?; - let cnt = src.read_vectored_volatile( + let bufs = unsafe { // Safe because we have made sure buf has at least count capacity above - unsafe { - &[FileVolatileSlice::from_raw_ptr( - self.buf.as_mut_ptr().add(self.buf.len()), - count, - )] - }, - )?; + [FileVolatileSlice::from_raw_ptr( + self.buf.as_mut_ptr().add(self.buf.len()), + count, + )] + }; + + let cnt = src.read_vectored_volatile(&bufs)?; self.account_written(cnt); if self.buffered { @@ -220,16 +225,15 @@ impl<'a, S: BitmapSlice> FuseDevWriter<'a, S> { ) -> io::Result { self.check_available_space(count)?; - let cnt = src.read_vectored_at_volatile( + let bufs = unsafe { // Safe because we have made sure buf has at least count capacity above - unsafe { - &[FileVolatileSlice::from_raw_ptr( - self.buf.as_mut_ptr().add(self.buf.len()), - count, - )] - }, - off, - )?; + [FileVolatileSlice::from_raw_ptr( + self.buf.as_mut_ptr().add(self.buf.len()), + count, + )] + }; + + let cnt = src.read_vectored_at_volatile(&bufs, off)?; self.account_written(cnt); if self.buffered {