This bug may be encountered when a developer migrates or ports a passthrough (filter) filesystem from Linux (using procfs or /proc) to MacOS (using volfs or /.vol) using existing fuser code. So far, I have only tested the minimal lifecycle (1 mount and 1 unmount). The FUSE driver fires STATFS, GETATTR, LOOKUP and OPENDIR during the process, and opening a file from /.vol while handling the last 3 request causes a deadlock which can only be unblocked by a timeout.
This is a minimal demo of the quirk on MacFUSE.
use fuser::{AccessFlags, Config, FileAttr, Filesystem, INodeNo, MountOption, ReplyEmpty};
use rustix::fs::{FileType, Mode, OFlags};
use std::{
io,
os::fd::OwnedFd,
path::Path,
time::{Duration, SystemTime, UNIX_EPOCH},
};
struct TestFilesystem {
dir_fd: OwnedFd,
}
impl Filesystem for TestFilesystem {
fn access(
&self,
_req: &fuser::Request,
ino: fuser::INodeNo,
mask: AccessFlags,
reply: ReplyEmpty,
) {
reply.ok();
}
fn getattr(
&self,
_req: &fuser::Request,
ino: fuser::INodeNo,
fh: Option<fuser::FileHandle>,
reply: fuser::ReplyAttr,
) {
let handler = || -> Result<FileAttr, io::Error> {
let stat = rustix::fs::fstat(&self.dir_fd)?;
let stable_path = Path::new("/.vol")
.join(stat.st_dev.to_string())
.join(stat.st_ino.to_string());
// log::trace!("opening with stable path: {}", stable_path.display());
// let reopen_fd = rustix::fs::open(&stable_path, OFlags::NONBLOCK, Mode::empty())?;
// log::trace!("opened with stable path: {}", stable_path.display());
let metadata = rustix::fs::fstat(&self.dir_fd)?;
let transformed = FileAttr {
ino: INodeNo(metadata.st_ino),
size: metadata.st_size as u64,
blocks: metadata.st_blocks as u64,
atime: system_time_from_unix(metadata.st_atime, metadata.st_atime_nsec as u32),
mtime: system_time_from_unix(metadata.st_mtime, metadata.st_mtime_nsec as u32),
ctime: system_time_from_unix(metadata.st_ctime, metadata.st_ctime_nsec as u32),
crtime: system_time_from_unix(
metadata.st_birthtime,
metadata.st_birthtime_nsec as u32,
),
kind: rustix_file_type_to_file_type(&FileType::from_raw_mode(metadata.st_mode))
.ok_or_else(|| io::Error::other("unknown file type"))?,
perm: Mode::from_raw_mode(metadata.st_mode).bits(),
nlink: metadata.st_nlink as u32,
uid: metadata.st_uid,
gid: metadata.st_gid,
rdev: metadata.st_rdev as u32,
blksize: metadata.st_blksize as u32,
flags: metadata.st_flags,
};
Ok(transformed)
};
let result = handler();
match result {
Ok(transformed) => reply.attr(&Duration::from_secs(0), &transformed),
Err(e) => {
log::error!("error: {}", e);
reply.error(e.into());
}
}
}
}
fn system_time_from_unix(sec: i64, nsec: u32) -> SystemTime {
if sec >= 0 {
UNIX_EPOCH + Duration::new(sec as u64, nsec)
} else {
UNIX_EPOCH - Duration::new((-sec) as u64, nsec)
}
}
fn rustix_file_type_to_file_type(file_type: &FileType) -> Option<fuser::FileType> {
if file_type.is_dir() {
Some(fuser::FileType::Directory)
} else if file_type.is_symlink() {
Some(fuser::FileType::Symlink)
} else if file_type.is_socket() {
Some(fuser::FileType::Socket)
} else if file_type.is_fifo() {
Some(fuser::FileType::NamedPipe)
} else if file_type.is_char_device() {
Some(fuser::FileType::CharDevice)
} else if file_type.is_block_device() {
Some(fuser::FileType::BlockDevice)
} else if file_type.is_file() {
Some(fuser::FileType::RegularFile)
} else {
None
}
}
fn main() {
env_logger::init();
let temp_dir = tempdir::TempDir::new("test").unwrap();
let dir_fd_1 = rustix::fs::open(temp_dir.path(), OFlags::empty(), Mode::empty()).unwrap();
let fs = TestFilesystem { dir_fd: dir_fd_1 };
let mut config = Config::default();
config
.mount_options
.push(MountOption::CUSTOM("daemon_timeout=5".to_string()));
let session = fuser::spawn_mount(fs, temp_dir.path(), &fuser::Config::default()).unwrap();
session.umount_and_join().unwrap();
}
This bug may be encountered when a developer migrates or ports a passthrough (filter) filesystem from Linux (using procfs or /proc) to MacOS (using volfs or /.vol) using existing
fusercode. So far, I have only tested the minimal lifecycle (1 mount and 1 unmount). The FUSE driver fires STATFS, GETATTR, LOOKUP and OPENDIR during the process, and opening a file from /.vol while handling the last 3 request causes a deadlock which can only be unblocked by a timeout.This is a minimal demo of the quirk on MacFUSE.