use std::{
collections::BTreeMap,
fs::File,
io,
os::fd::{AsRawFd, FromRawFd},
path::PathBuf,
sync::{
RwLock,
atomic::{AtomicBool, AtomicU64},
},
time::Duration,
};
use super::{CachePolicy, PassthroughFs};
use crate::backends::shared::{
init_binary, inode_table::MultikeyBTreeMap, platform, stat_override,
};
pub struct PassthroughFsBuilder {
root_dir: Option<PathBuf>,
xattr: bool,
strict: bool,
entry_timeout: Duration,
attr_timeout: Duration,
cache_policy: CachePolicy,
writeback: bool,
inject_init: bool,
}
impl PassthroughFsBuilder {
pub(crate) fn new() -> Self {
Self {
root_dir: None,
xattr: true,
strict: true,
entry_timeout: Duration::from_secs(5),
attr_timeout: Duration::from_secs(5),
cache_policy: CachePolicy::Auto,
writeback: false,
inject_init: true,
}
}
pub fn root_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.root_dir = Some(path.into());
self
}
pub fn xattr(mut self, enabled: bool) -> Self {
self.xattr = enabled;
self
}
pub fn strict(mut self, enabled: bool) -> Self {
self.strict = enabled;
self
}
pub fn entry_timeout(mut self, timeout: Duration) -> Self {
self.entry_timeout = timeout;
self
}
pub fn attr_timeout(mut self, timeout: Duration) -> Self {
self.attr_timeout = timeout;
self
}
pub fn cache_policy(mut self, policy: CachePolicy) -> Self {
self.cache_policy = policy;
self
}
pub fn writeback(mut self, enabled: bool) -> Self {
self.writeback = enabled;
self
}
pub fn inject_init(mut self, enabled: bool) -> Self {
self.inject_init = enabled;
self
}
pub fn build(self) -> io::Result<PassthroughFs> {
let root_dir = self
.root_dir
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "root_dir not set"))?;
let root_path =
std::ffi::CString::new(root_dir.to_str().ok_or_else(platform::einval)?.as_bytes())
.map_err(|_| platform::einval())?;
let root_fd_raw = unsafe {
libc::open(
root_path.as_ptr(),
libc::O_RDONLY | libc::O_CLOEXEC | libc::O_DIRECTORY,
)
};
if root_fd_raw < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
let root_fd = unsafe { File::from_raw_fd(root_fd_raw) };
if self.strict && self.xattr {
let supported = stat_override::probe_xattr_support(root_fd.as_raw_fd())?;
if !supported {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"xattr not supported on root filesystem and strict mode is enabled",
));
}
}
let init_file = init_binary::create_init_file()?;
#[cfg(target_os = "linux")]
let has_openat2 = AtomicBool::new(platform::probe_openat2());
#[cfg(target_os = "linux")]
let proc_self_fd = {
let path = std::ffi::CString::new("/proc/self/fd").unwrap();
let fd = unsafe { libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if fd < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
unsafe { File::from_raw_fd(fd) }
};
let cfg = super::PassthroughConfig {
root_dir,
xattr: self.xattr,
strict: self.strict,
entry_timeout: self.entry_timeout,
attr_timeout: self.attr_timeout,
cache_policy: self.cache_policy,
writeback: self.writeback,
inject_init: self.inject_init,
};
Ok(PassthroughFs {
cfg,
root_fd,
inodes: RwLock::new(MultikeyBTreeMap::new()),
next_inode: AtomicU64::new(3), handles: RwLock::new(BTreeMap::new()),
dir_handles: RwLock::new(BTreeMap::new()),
next_handle: AtomicU64::new(1), writeback: AtomicBool::new(false),
init_file,
#[cfg(target_os = "linux")]
has_openat2,
#[cfg(target_os = "linux")]
proc_self_fd,
})
}
}