#![warn(
missing_docs,
missing_debug_implementations,
rust_2018_idioms,
unreachable_pub
)]
use std::cmp::max;
use std::cmp::min;
use std::convert::AsRef;
use std::ffi::OsStr;
use std::io;
use std::os::unix::fs::FileTypeExt;
use std::path::Path;
use std::time::Duration;
use std::time::SystemTime;
use log::warn;
#[cfg(target_os = "macos")]
pub use reply::ReplyXTimes;
#[cfg(feature = "serializable")]
use serde::Deserialize;
#[cfg(feature = "serializable")]
use serde::Serialize;
pub use crate::access_flags::AccessFlags;
pub use crate::bsd_file_flags::BsdFileFlags;
use crate::forget_one::ForgetOne;
pub use crate::ll::Errno;
pub use crate::ll::Generation;
pub use crate::ll::RequestId;
pub use crate::ll::TimeOrNow;
pub use crate::ll::flags::copy_file_range_flags::CopyFileRangeFlags;
pub use crate::ll::flags::fopen_flags::FopenFlags;
pub use crate::ll::flags::init_flags::InitFlags;
pub use crate::ll::flags::ioctl_flags::IoctlFlags;
pub use crate::ll::flags::poll_flags::PollFlags;
pub use crate::ll::flags::write_flags::WriteFlags;
pub use crate::ll::fuse_abi::consts;
pub use crate::ll::request::FileHandle;
pub use crate::ll::request::INodeNo;
pub use crate::ll::request::LockOwner;
pub use crate::ll::request::Version;
pub use crate::mnt::mount_options::Config;
pub use crate::mnt::mount_options::MountOption;
use crate::mnt::mount_options::check_option_conflicts;
pub use crate::notify::Notifier;
pub use crate::notify::PollHandle;
pub use crate::notify::PollNotifier;
pub use crate::open_flags::OpenAccMode;
pub use crate::open_flags::OpenFlags;
pub use crate::passthrough::BackingId;
pub use crate::poll_events::PollEvents;
pub use crate::rename_flags::RenameFlags;
pub use crate::reply::ReplyAttr;
pub use crate::reply::ReplyBmap;
pub use crate::reply::ReplyCreate;
pub use crate::reply::ReplyData;
pub use crate::reply::ReplyDirectory;
pub use crate::reply::ReplyDirectoryPlus;
pub use crate::reply::ReplyEmpty;
pub use crate::reply::ReplyEntry;
pub use crate::reply::ReplyIoctl;
pub use crate::reply::ReplyLock;
pub use crate::reply::ReplyLseek;
pub use crate::reply::ReplyOpen;
pub use crate::reply::ReplyPoll;
pub use crate::reply::ReplyStatfs;
pub use crate::reply::ReplyWrite;
pub use crate::reply::ReplyXattr;
pub use crate::request_param::Request;
pub use crate::session::BackgroundSession;
use crate::session::MAX_WRITE_SIZE;
pub use crate::session::Session;
pub use crate::session::SessionACL;
pub use crate::session::SessionUnmounter;
mod access_flags;
mod bsd_file_flags;
mod channel;
mod dev_fuse;
#[cfg(feature = "experimental")]
pub mod experimental;
mod forget_one;
mod ll;
mod mnt;
mod notify;
mod open_flags;
mod passthrough;
mod poll_events;
mod read_buf;
mod rename_flags;
mod reply;
mod request;
mod request_param;
mod session;
mod time;
#[cfg(not(target_os = "macos"))]
const INIT_FLAGS: InitFlags = InitFlags::FUSE_ASYNC_READ.union(InitFlags::FUSE_BIG_WRITES);
#[cfg(target_os = "macos")]
const INIT_FLAGS: InitFlags = InitFlags::FUSE_ASYNC_READ
.union(InitFlags::FUSE_CASE_INSENSITIVE)
.union(InitFlags::FUSE_VOL_RENAME)
.union(InitFlags::FUSE_XTIMES);
fn default_init_flags(capabilities: InitFlags) -> InitFlags {
let mut flags = INIT_FLAGS;
if capabilities.contains(InitFlags::FUSE_MAX_PAGES) {
flags |= InitFlags::FUSE_MAX_PAGES;
}
flags
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub enum FileType {
NamedPipe,
CharDevice,
BlockDevice,
Directory,
RegularFile,
Symlink,
Socket,
}
impl FileType {
pub fn from_std(file_type: std::fs::FileType) -> Option<Self> {
if file_type.is_file() {
Some(FileType::RegularFile)
} else if file_type.is_dir() {
Some(FileType::Directory)
} else if file_type.is_symlink() {
Some(FileType::Symlink)
} else if file_type.is_fifo() {
Some(FileType::NamedPipe)
} else if file_type.is_socket() {
Some(FileType::Socket)
} else if file_type.is_char_device() {
Some(FileType::CharDevice)
} else if file_type.is_block_device() {
Some(FileType::BlockDevice)
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct FileAttr {
pub ino: INodeNo,
pub size: u64,
pub blocks: u64,
pub atime: SystemTime,
pub mtime: SystemTime,
pub ctime: SystemTime,
pub crtime: SystemTime,
pub kind: FileType,
pub perm: u16,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub rdev: u32,
pub blksize: u32,
pub flags: u32,
}
#[derive(Debug)]
pub struct KernelConfig {
capabilities: InitFlags,
requested: InitFlags,
max_readahead: u32,
max_max_readahead: u32,
max_background: u16,
congestion_threshold: Option<u16>,
max_write: u32,
time_gran: Duration,
max_stack_depth: u32,
kernel_abi: Version,
}
impl KernelConfig {
fn new(capabilities: InitFlags, max_readahead: u32, kernel_abi: Version) -> Self {
Self {
capabilities,
requested: default_init_flags(capabilities),
max_readahead,
max_max_readahead: max_readahead,
max_background: 16,
congestion_threshold: None,
max_write: MAX_WRITE_SIZE as u32,
time_gran: Duration::new(0, 1),
max_stack_depth: 0,
kernel_abi,
}
}
pub fn set_max_stack_depth(&mut self, value: u32) -> Result<u32, u32> {
const FILESYSTEM_MAX_STACK_DEPTH: u32 = 2;
if value > FILESYSTEM_MAX_STACK_DEPTH {
return Err(FILESYSTEM_MAX_STACK_DEPTH);
}
let previous = self.max_stack_depth;
self.max_stack_depth = value;
Ok(previous)
}
pub fn set_time_granularity(&mut self, value: Duration) -> Result<Duration, Duration> {
if value.as_nanos() == 0 {
return Err(Duration::new(0, 1));
}
if value.as_secs() > 1 || (value.as_secs() == 1 && value.subsec_nanos() > 0) {
return Err(Duration::new(1, 0));
}
let mut power_of_10 = 1;
while power_of_10 < value.as_nanos() {
if value.as_nanos() < power_of_10 * 10 {
return Err(Duration::new(0, power_of_10 as u32));
}
power_of_10 *= 10;
}
let previous = self.time_gran;
self.time_gran = value;
Ok(previous)
}
pub fn set_max_write(&mut self, value: u32) -> Result<u32, u32> {
if value == 0 {
return Err(1);
}
if value > MAX_WRITE_SIZE as u32 {
return Err(MAX_WRITE_SIZE as u32);
}
let previous = self.max_write;
self.max_write = value;
Ok(previous)
}
pub fn set_max_readahead(&mut self, value: u32) -> Result<u32, u32> {
if value == 0 {
return Err(1);
}
if value > self.max_max_readahead {
return Err(self.max_max_readahead);
}
let previous = self.max_readahead;
self.max_readahead = value;
Ok(previous)
}
pub fn capabilities(&self) -> InitFlags {
self.capabilities & !InitFlags::FUSE_INIT_EXT
}
pub fn kernel_abi(&self) -> Version {
self.kernel_abi
}
pub fn add_capabilities(&mut self, capabilities_to_add: InitFlags) -> Result<(), InitFlags> {
if !self.capabilities.contains(capabilities_to_add) {
let unsupported = capabilities_to_add & !self.capabilities;
return Err(unsupported);
}
self.requested |= capabilities_to_add;
Ok(())
}
pub fn set_max_background(&mut self, value: u16) -> Result<u16, u16> {
if value == 0 {
return Err(1);
}
let previous = self.max_background;
self.max_background = value;
Ok(previous)
}
pub fn set_congestion_threshold(&mut self, value: u16) -> Result<u16, u16> {
if value == 0 {
return Err(1);
}
let previous = self.congestion_threshold();
self.congestion_threshold = Some(value);
Ok(previous)
}
fn congestion_threshold(&self) -> u16 {
match self.congestion_threshold {
None => (u32::from(self.max_background) * 3 / 4) as u16,
Some(value) => min(value, self.max_background),
}
}
fn max_pages(&self) -> u16 {
((max(self.max_write, self.max_readahead) - 1) / page_size::get() as u32) as u16 + 1
}
}
#[allow(clippy::too_many_arguments)]
pub trait Filesystem: Send + Sync + 'static {
fn init(&mut self, _req: &Request, _config: &mut KernelConfig) -> io::Result<()> {
Ok(())
}
fn destroy(&mut self) {}
fn lookup(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) {
warn!("[Not Implemented] lookup(parent: {parent:#x?}, name {name:?})");
reply.error(Errno::ENOSYS);
}
fn forget(&self, _req: &Request, _ino: INodeNo, _nlookup: u64) {}
fn batch_forget(&self, req: &Request, nodes: &[ForgetOne]) {
for node in nodes {
self.forget(req, node.nodeid(), node.nlookup());
}
}
fn getattr(&self, _req: &Request, ino: INodeNo, fh: Option<FileHandle>, reply: ReplyAttr) {
warn!("[Not Implemented] getattr(ino: {ino:#x?}, fh: {fh:#x?})");
reply.error(Errno::ENOSYS);
}
fn setattr(
&self,
_req: &Request,
ino: INodeNo,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
_atime: Option<TimeOrNow>,
_mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
fh: Option<FileHandle>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
flags: Option<BsdFileFlags>,
reply: ReplyAttr,
) {
warn!(
"[Not Implemented] setattr(ino: {ino:#x?}, mode: {mode:?}, uid: {uid:?}, \
gid: {gid:?}, size: {size:?}, fh: {fh:?}, flags: {flags:?})"
);
reply.error(Errno::ENOSYS);
}
fn readlink(&self, _req: &Request, ino: INodeNo, reply: ReplyData) {
warn!("[Not Implemented] readlink(ino: {ino:#x?})");
reply.error(Errno::ENOSYS);
}
fn mknod(
&self,
_req: &Request,
parent: INodeNo,
name: &OsStr,
mode: u32,
umask: u32,
rdev: u32,
reply: ReplyEntry,
) {
warn!(
"[Not Implemented] mknod(parent: {parent:#x?}, name: {name:?}, \
mode: {mode}, umask: {umask:#x?}, rdev: {rdev})"
);
reply.error(Errno::ENOSYS);
}
fn mkdir(
&self,
_req: &Request,
parent: INodeNo,
name: &OsStr,
mode: u32,
umask: u32,
reply: ReplyEntry,
) {
warn!(
"[Not Implemented] mkdir(parent: {parent:#x?}, name: {name:?}, mode: {mode}, umask: {umask:#x?})"
);
reply.error(Errno::ENOSYS);
}
fn unlink(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
warn!("[Not Implemented] unlink(parent: {parent:#x?}, name: {name:?})",);
reply.error(Errno::ENOSYS);
}
fn rmdir(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
warn!("[Not Implemented] rmdir(parent: {parent:#x?}, name: {name:?})",);
reply.error(Errno::ENOSYS);
}
fn symlink(
&self,
_req: &Request,
parent: INodeNo,
link_name: &OsStr,
target: &Path,
reply: ReplyEntry,
) {
warn!(
"[Not Implemented] symlink(parent: {parent:#x?}, link_name: {link_name:?}, target: {target:?})",
);
reply.error(Errno::EPERM);
}
fn rename(
&self,
_req: &Request,
parent: INodeNo,
name: &OsStr,
newparent: INodeNo,
newname: &OsStr,
flags: RenameFlags,
reply: ReplyEmpty,
) {
warn!(
"[Not Implemented] rename(parent: {parent:#x?}, name: {name:?}, \
newparent: {newparent:#x?}, newname: {newname:?}, flags: {flags})",
);
reply.error(Errno::ENOSYS);
}
fn link(
&self,
_req: &Request,
ino: INodeNo,
newparent: INodeNo,
newname: &OsStr,
reply: ReplyEntry,
) {
warn!(
"[Not Implemented] link(ino: {ino:#x?}, newparent: {newparent:#x?}, newname: {newname:?})"
);
reply.error(Errno::EPERM);
}
fn open(&self, _req: &Request, _ino: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
reply.opened(FileHandle(0), FopenFlags::empty());
}
fn read(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: u64,
size: u32,
flags: OpenFlags,
lock_owner: Option<LockOwner>,
reply: ReplyData,
) {
warn!(
"[Not Implemented] read(ino: {ino:#x?}, fh: {fh}, offset: {offset}, \
size: {size}, flags: {flags:#x?}, lock_owner: {lock_owner:?})"
);
reply.error(Errno::ENOSYS);
}
fn write(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: u64,
data: &[u8],
write_flags: WriteFlags,
flags: OpenFlags,
lock_owner: Option<LockOwner>,
reply: ReplyWrite,
) {
warn!(
"[Not Implemented] write(ino: {ino:#x?}, fh: {fh}, offset: {offset}, \
data.len(): {}, write_flags: {write_flags:#x?}, flags: {flags:#x?}, \
lock_owner: {lock_owner:?})",
data.len()
);
reply.error(Errno::ENOSYS);
}
fn flush(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
lock_owner: LockOwner,
reply: ReplyEmpty,
) {
warn!("[Not Implemented] flush(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner:?})");
reply.error(Errno::ENOSYS);
}
fn release(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_flags: OpenFlags,
_lock_owner: Option<LockOwner>,
_flush: bool,
reply: ReplyEmpty,
) {
reply.ok();
}
fn fsync(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
datasync: bool,
reply: ReplyEmpty,
) {
warn!("[Not Implemented] fsync(ino: {ino:#x?}, fh: {fh}, datasync: {datasync})");
reply.error(Errno::ENOSYS);
}
fn opendir(&self, _req: &Request, _ino: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
reply.opened(FileHandle(0), FopenFlags::empty());
}
fn readdir(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: u64,
reply: ReplyDirectory,
) {
warn!("[Not Implemented] readdir(ino: {ino:#x?}, fh: {fh}, offset: {offset})");
reply.error(Errno::ENOSYS);
}
fn readdirplus(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: u64,
reply: ReplyDirectoryPlus,
) {
warn!("[Not Implemented] readdirplus(ino: {ino:#x?}, fh: {fh}, offset: {offset})");
reply.error(Errno::ENOSYS);
}
fn releasedir(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_flags: OpenFlags,
reply: ReplyEmpty,
) {
reply.ok();
}
fn fsyncdir(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
datasync: bool,
reply: ReplyEmpty,
) {
warn!("[Not Implemented] fsyncdir(ino: {ino:#x?}, fh: {fh}, datasync: {datasync})");
reply.error(Errno::ENOSYS);
}
fn statfs(&self, _req: &Request, _ino: INodeNo, reply: ReplyStatfs) {
reply.statfs(0, 0, 0, 0, 0, 512, 255, 0);
}
fn setxattr(
&self,
_req: &Request,
ino: INodeNo,
name: &OsStr,
_value: &[u8],
flags: i32,
position: u32,
reply: ReplyEmpty,
) {
warn!(
"[Not Implemented] setxattr(ino: {ino:#x?}, name: {name:?}, \
flags: {flags:#x?}, position: {position})"
);
reply.error(Errno::ENOSYS);
}
fn getxattr(&self, _req: &Request, ino: INodeNo, name: &OsStr, size: u32, reply: ReplyXattr) {
warn!("[Not Implemented] getxattr(ino: {ino:#x?}, name: {name:?}, size: {size})");
reply.error(Errno::ENOSYS);
}
fn listxattr(&self, _req: &Request, ino: INodeNo, size: u32, reply: ReplyXattr) {
warn!("[Not Implemented] listxattr(ino: {ino:#x?}, size: {size})");
reply.error(Errno::ENOSYS);
}
fn removexattr(&self, _req: &Request, ino: INodeNo, name: &OsStr, reply: ReplyEmpty) {
warn!("[Not Implemented] removexattr(ino: {ino:#x?}, name: {name:?})");
reply.error(Errno::ENOSYS);
}
fn access(&self, _req: &Request, ino: INodeNo, mask: AccessFlags, reply: ReplyEmpty) {
warn!("[Not Implemented] access(ino: {ino:#x?}, mask: {mask})");
reply.error(Errno::ENOSYS);
}
fn create(
&self,
_req: &Request,
parent: INodeNo,
name: &OsStr,
mode: u32,
umask: u32,
flags: i32,
reply: ReplyCreate,
) {
warn!(
"[Not Implemented] create(parent: {parent:#x?}, name: {name:?}, mode: {mode}, \
umask: {umask:#x?}, flags: {flags:#x?})"
);
reply.error(Errno::ENOSYS);
}
fn getlk(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
lock_owner: LockOwner,
start: u64,
end: u64,
typ: i32,
pid: u32,
reply: ReplyLock,
) {
warn!(
"[Not Implemented] getlk(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner}, \
start: {start}, end: {end}, typ: {typ}, pid: {pid})"
);
reply.error(Errno::ENOSYS);
}
fn setlk(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
lock_owner: LockOwner,
start: u64,
end: u64,
typ: i32,
pid: u32,
sleep: bool,
reply: ReplyEmpty,
) {
warn!(
"[Not Implemented] setlk(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner}, \
start: {start}, end: {end}, typ: {typ}, pid: {pid}, sleep: {sleep})"
);
reply.error(Errno::ENOSYS);
}
fn bmap(&self, _req: &Request, ino: INodeNo, blocksize: u32, idx: u64, reply: ReplyBmap) {
warn!("[Not Implemented] bmap(ino: {ino:#x?}, blocksize: {blocksize}, idx: {idx})",);
reply.error(Errno::ENOSYS);
}
fn ioctl(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
flags: IoctlFlags,
cmd: u32,
in_data: &[u8],
out_size: u32,
reply: ReplyIoctl,
) {
warn!(
"[Not Implemented] ioctl(ino: {ino:#x?}, fh: {fh}, flags: {flags}, \
cmd: {cmd}, in_data.len(): {}, out_size: {out_size})",
in_data.len()
);
reply.error(Errno::ENOSYS);
}
fn poll(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
ph: PollNotifier,
events: PollEvents,
flags: PollFlags,
reply: ReplyPoll,
) {
warn!(
"[Not Implemented] poll(ino: {ino:#x?}, fh: {fh}, \
ph: {ph:?}, events: {events}, flags: {flags})"
);
reply.error(Errno::ENOSYS);
}
fn fallocate(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: u64,
length: u64,
mode: i32,
reply: ReplyEmpty,
) {
warn!(
"[Not Implemented] fallocate(ino: {ino:#x?}, fh: {fh}, \
offset: {offset}, length: {length}, mode: {mode})"
);
reply.error(Errno::ENOSYS);
}
fn lseek(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: i64,
whence: i32,
reply: ReplyLseek,
) {
warn!(
"[Not Implemented] lseek(ino: {ino:#x?}, fh: {fh}, \
offset: {offset}, whence: {whence})"
);
reply.error(Errno::ENOSYS);
}
fn copy_file_range(
&self,
_req: &Request,
ino_in: INodeNo,
fh_in: FileHandle,
offset_in: u64,
ino_out: INodeNo,
fh_out: FileHandle,
offset_out: u64,
len: u64,
flags: CopyFileRangeFlags,
reply: ReplyWrite,
) {
warn!(
"[Not Implemented] copy_file_range(ino_in: {ino_in:#x?}, fh_in: {fh_in}, \
offset_in: {offset_in}, ino_out: {ino_out:#x?}, fh_out: {fh_out}, \
offset_out: {offset_out}, len: {len}, flags: {flags:?})"
);
reply.error(Errno::ENOSYS);
}
#[cfg(target_os = "macos")]
fn setvolname(&self, _req: &Request, name: &OsStr, reply: ReplyEmpty) {
warn!("[Not Implemented] setvolname(name: {name:?})");
reply.error(Errno::ENOSYS);
}
#[cfg(target_os = "macos")]
fn exchange(
&self,
_req: &Request,
parent: INodeNo,
name: &OsStr,
newparent: INodeNo,
newname: &OsStr,
options: u64,
reply: ReplyEmpty,
) {
warn!(
"[Not Implemented] exchange(parent: {parent:#x?}, name: {name:?}, \
newparent: {newparent:#x?}, newname: {newname:?}, options: {options})"
);
reply.error(Errno::ENOSYS);
}
#[cfg(target_os = "macos")]
fn getxtimes(&self, _req: &Request, ino: INodeNo, reply: ReplyXTimes) {
warn!("[Not Implemented] getxtimes(ino: {ino:#x?})");
reply.error(Errno::ENOSYS);
}
}
pub fn mount2<FS: Filesystem, P: AsRef<Path>>(
filesystem: FS,
mountpoint: P,
options: &Config,
) -> io::Result<()> {
check_option_conflicts(options)?;
Session::new(filesystem, mountpoint.as_ref(), options).and_then(|se| se.run())
}
pub fn spawn_mount2<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef<Path>>(
filesystem: FS,
mountpoint: P,
options: &Config,
) -> io::Result<BackgroundSession> {
check_option_conflicts(options)?;
Session::new(filesystem, mountpoint.as_ref(), options).and_then(session::Session::spawn)
}