#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
use libc::{c_int, ENOSYS, EPERM};
use log::{debug, warn};
use mnt::mount_options::parse_options_from_args;
#[cfg(feature = "serializable")]
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::io;
use std::path::Path;
#[cfg(feature = "abi-7-23")]
use std::time::Duration;
use std::time::SystemTime;
use std::{convert::AsRef, io::ErrorKind};
use crate::ll::fuse_abi::consts::*;
pub use crate::ll::fuse_abi::FUSE_ROOT_ID;
pub use crate::ll::{fuse_abi::consts, TimeOrNow};
use crate::mnt::mount_options::check_option_conflicts;
use crate::session::MAX_WRITE_SIZE;
#[cfg(feature = "abi-7-16")]
pub use ll::fuse_abi::fuse_forget_one;
pub use mnt::mount_options::MountOption;
#[cfg(feature = "abi-7-11")]
pub use notify::Notifier;
#[cfg(feature = "abi-7-11")]
pub use reply::ReplyPoll;
#[cfg(target_os = "macos")]
pub use reply::ReplyXTimes;
pub use reply::ReplyXattr;
pub use reply::{Reply, ReplyAttr, ReplyData, ReplyEmpty, ReplyEntry, ReplyOpen};
pub use reply::{
ReplyBmap, ReplyCreate, ReplyDirectory, ReplyDirectoryPlus, ReplyIoctl, ReplyLock, ReplyLseek,
ReplyStatfs, ReplyWrite,
};
pub use request::Request;
pub use session::{BackgroundSession, Session, SessionUnmounter};
#[cfg(feature = "abi-7-28")]
use std::cmp::max;
#[cfg(feature = "abi-7-13")]
use std::cmp::min;
mod channel;
mod ll;
mod mnt;
#[cfg(feature = "abi-7-11")]
mod notify;
mod reply;
mod request;
mod session;
#[cfg(all(not(target_os = "macos"), not(feature = "abi-7-10")))]
const INIT_FLAGS: u32 = FUSE_ASYNC_READ;
#[cfg(all(not(target_os = "macos"), feature = "abi-7-10"))]
const INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_BIG_WRITES;
#[cfg(target_os = "macos")]
const INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_CASE_INSENSITIVE | FUSE_VOL_RENAME | FUSE_XTIMES;
const fn default_init_flags(#[allow(unused_variables)] capabilities: u32) -> u32 {
#[cfg(not(feature = "abi-7-28"))]
{
INIT_FLAGS
}
#[cfg(feature = "abi-7-28")]
{
let mut flags = INIT_FLAGS;
if capabilities & FUSE_MAX_PAGES != 0 {
flags |= 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,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct FileAttr {
pub ino: u64,
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: u32,
requested: u32,
max_readahead: u32,
max_max_readahead: u32,
#[cfg(feature = "abi-7-13")]
max_background: u16,
#[cfg(feature = "abi-7-13")]
congestion_threshold: Option<u16>,
max_write: u32,
#[cfg(feature = "abi-7-23")]
time_gran: Duration,
}
impl KernelConfig {
fn new(capabilities: u32, max_readahead: u32) -> Self {
Self {
capabilities,
requested: default_init_flags(capabilities),
max_readahead,
max_max_readahead: max_readahead,
#[cfg(feature = "abi-7-13")]
max_background: 16,
#[cfg(feature = "abi-7-13")]
congestion_threshold: None,
max_write: MAX_WRITE_SIZE as u32,
#[cfg(feature = "abi-7-23")]
time_gran: Duration::new(0, 1),
}
}
#[cfg(feature = "abi-7-23")]
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 add_capabilities(&mut self, capabilities_to_add: u32) -> Result<(), u32> {
if capabilities_to_add & self.capabilities != capabilities_to_add {
return Err(capabilities_to_add - (capabilities_to_add & self.capabilities));
}
self.requested |= capabilities_to_add;
Ok(())
}
#[cfg(feature = "abi-7-13")]
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)
}
#[cfg(feature = "abi-7-13")]
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)
}
#[cfg(feature = "abi-7-13")]
fn congestion_threshold(&self) -> u16 {
match self.congestion_threshold {
None => (self.max_background as u32 * 3 / 4) as u16,
Some(value) => min(value, self.max_background),
}
}
#[cfg(feature = "abi-7-28")]
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 {
fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), c_int> {
Ok(())
}
fn destroy(&mut self) {}
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
warn!(
"[Not Implemented] lookup(parent: {:#x?}, name {:?})",
parent, name
);
reply.error(ENOSYS);
}
fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {}
#[cfg(feature = "abi-7-16")]
fn batch_forget(&mut self, req: &Request<'_>, nodes: &[fuse_forget_one]) {
for node in nodes {
self.forget(req, node.nodeid, node.nlookup);
}
}
fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
warn!("[Not Implemented] getattr(ino: {:#x?})", ino);
reply.error(ENOSYS);
}
fn setattr(
&mut self,
_req: &Request<'_>,
ino: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
_atime: Option<TimeOrNow>,
_mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
fh: Option<u64>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
flags: Option<u32>,
reply: ReplyAttr,
) {
debug!(
"[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \
gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})",
ino, mode, uid, gid, size, fh, flags
);
reply.error(ENOSYS);
}
fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyData) {
debug!("[Not Implemented] readlink(ino: {:#x?})", ino);
reply.error(ENOSYS);
}
fn mknod(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
umask: u32,
rdev: u32,
reply: ReplyEntry,
) {
debug!(
"[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \
umask: {:#x?}, rdev: {})",
parent, name, mode, umask, rdev
);
reply.error(ENOSYS);
}
fn mkdir(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
umask: u32,
reply: ReplyEntry,
) {
debug!(
"[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})",
parent, name, mode, umask
);
reply.error(ENOSYS);
}
fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
debug!(
"[Not Implemented] unlink(parent: {:#x?}, name: {:?})",
parent, name,
);
reply.error(ENOSYS);
}
fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
debug!(
"[Not Implemented] rmdir(parent: {:#x?}, name: {:?})",
parent, name,
);
reply.error(ENOSYS);
}
fn symlink(
&mut self,
_req: &Request<'_>,
parent: u64,
link_name: &OsStr,
target: &Path,
reply: ReplyEntry,
) {
debug!(
"[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})",
parent, link_name, target,
);
reply.error(EPERM);
}
fn rename(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
newparent: u64,
newname: &OsStr,
flags: u32,
reply: ReplyEmpty,
) {
debug!(
"[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \
newname: {:?}, flags: {})",
parent, name, newparent, newname, flags,
);
reply.error(ENOSYS);
}
fn link(
&mut self,
_req: &Request<'_>,
ino: u64,
newparent: u64,
newname: &OsStr,
reply: ReplyEntry,
) {
debug!(
"[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})",
ino, newparent, newname
);
reply.error(EPERM);
}
fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) {
reply.opened(0, 0);
}
fn read(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
size: u32,
flags: i32,
lock_owner: Option<u64>,
reply: ReplyData,
) {
warn!(
"[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \
flags: {:#x?}, lock_owner: {:?})",
ino, fh, offset, size, flags, lock_owner
);
reply.error(ENOSYS);
}
fn write(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
data: &[u8],
write_flags: u32,
flags: i32,
lock_owner: Option<u64>,
reply: ReplyWrite,
) {
debug!(
"[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \
write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})",
ino,
fh,
offset,
data.len(),
write_flags,
flags,
lock_owner
);
reply.error(ENOSYS);
}
fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) {
debug!(
"[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})",
ino, fh, lock_owner
);
reply.error(ENOSYS);
}
fn release(
&mut self,
_req: &Request<'_>,
_ino: u64,
_fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
reply.ok();
}
fn fsync(&mut self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) {
debug!(
"[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})",
ino, fh, datasync
);
reply.error(ENOSYS);
}
fn opendir(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) {
reply.opened(0, 0);
}
fn readdir(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
reply: ReplyDirectory,
) {
warn!(
"[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})",
ino, fh, offset
);
reply.error(ENOSYS);
}
fn readdirplus(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
reply: ReplyDirectoryPlus,
) {
debug!(
"[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})",
ino, fh, offset
);
reply.error(ENOSYS);
}
fn releasedir(
&mut self,
_req: &Request<'_>,
_ino: u64,
_fh: u64,
_flags: i32,
reply: ReplyEmpty,
) {
reply.ok();
}
fn fsyncdir(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
datasync: bool,
reply: ReplyEmpty,
) {
debug!(
"[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})",
ino, fh, datasync
);
reply.error(ENOSYS);
}
fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
reply.statfs(0, 0, 0, 0, 0, 512, 255, 0);
}
fn setxattr(
&mut self,
_req: &Request<'_>,
ino: u64,
name: &OsStr,
_value: &[u8],
flags: i32,
position: u32,
reply: ReplyEmpty,
) {
debug!(
"[Not Implemented] setxattr(ino: {:#x?}, name: {:?}, flags: {:#x?}, position: {})",
ino, name, flags, position
);
reply.error(ENOSYS);
}
fn getxattr(
&mut self,
_req: &Request<'_>,
ino: u64,
name: &OsStr,
size: u32,
reply: ReplyXattr,
) {
debug!(
"[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})",
ino, name, size
);
reply.error(ENOSYS);
}
fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32, reply: ReplyXattr) {
debug!(
"[Not Implemented] listxattr(ino: {:#x?}, size: {})",
ino, size
);
reply.error(ENOSYS);
}
fn removexattr(&mut self, _req: &Request<'_>, ino: u64, name: &OsStr, reply: ReplyEmpty) {
debug!(
"[Not Implemented] removexattr(ino: {:#x?}, name: {:?})",
ino, name
);
reply.error(ENOSYS);
}
fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) {
debug!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask);
reply.error(ENOSYS);
}
fn create(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
umask: u32,
flags: i32,
reply: ReplyCreate,
) {
debug!(
"[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \
flags: {:#x?})",
parent, name, mode, umask, flags
);
reply.error(ENOSYS);
}
fn getlk(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
lock_owner: u64,
start: u64,
end: u64,
typ: i32,
pid: u32,
reply: ReplyLock,
) {
debug!(
"[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \
end: {}, typ: {}, pid: {})",
ino, fh, lock_owner, start, end, typ, pid
);
reply.error(ENOSYS);
}
fn setlk(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
lock_owner: u64,
start: u64,
end: u64,
typ: i32,
pid: u32,
sleep: bool,
reply: ReplyEmpty,
) {
debug!(
"[Not Implemented] setlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \
end: {}, typ: {}, pid: {}, sleep: {})",
ino, fh, lock_owner, start, end, typ, pid, sleep
);
reply.error(ENOSYS);
}
fn bmap(&mut self, _req: &Request<'_>, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) {
debug!(
"[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})",
ino, blocksize, idx,
);
reply.error(ENOSYS);
}
fn ioctl(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
flags: u32,
cmd: u32,
in_data: &[u8],
out_size: u32,
reply: ReplyIoctl,
) {
debug!(
"[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \
in_data.len(): {}, out_size: {})",
ino,
fh,
flags,
cmd,
in_data.len(),
out_size,
);
reply.error(ENOSYS);
}
#[cfg(feature = "abi-7-11")]
fn poll(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
kh: u64,
events: u32,
flags: u32,
reply: ReplyPoll,
) {
debug!(
"[Not Implemented] poll(ino: {:#x?}, fh: {}, kh: {}, events: {}, flags: {})",
ino, fh, kh, events, flags
);
reply.error(ENOSYS);
}
fn fallocate(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
length: i64,
mode: i32,
reply: ReplyEmpty,
) {
debug!(
"[Not Implemented] fallocate(ino: {:#x?}, fh: {}, offset: {}, \
length: {}, mode: {})",
ino, fh, offset, length, mode
);
reply.error(ENOSYS);
}
fn lseek(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
whence: i32,
reply: ReplyLseek,
) {
debug!(
"[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})",
ino, fh, offset, whence
);
reply.error(ENOSYS);
}
fn copy_file_range(
&mut self,
_req: &Request<'_>,
ino_in: u64,
fh_in: u64,
offset_in: i64,
ino_out: u64,
fh_out: u64,
offset_out: i64,
len: u64,
flags: u32,
reply: ReplyWrite,
) {
debug!(
"[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \
offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \
len: {}, flags: {})",
ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags
);
reply.error(ENOSYS);
}
#[cfg(target_os = "macos")]
fn setvolname(&mut self, _req: &Request<'_>, name: &OsStr, reply: ReplyEmpty) {
debug!("[Not Implemented] setvolname(name: {:?})", name);
reply.error(ENOSYS);
}
#[cfg(target_os = "macos")]
fn exchange(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
newparent: u64,
newname: &OsStr,
options: u64,
reply: ReplyEmpty,
) {
debug!(
"[Not Implemented] exchange(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \
newname: {:?}, options: {})",
parent, name, newparent, newname, options
);
reply.error(ENOSYS);
}
#[cfg(target_os = "macos")]
fn getxtimes(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyXTimes) {
debug!("[Not Implemented] getxtimes(ino: {:#x?})", ino);
reply.error(ENOSYS);
}
}
#[deprecated(note = "use mount2() instead")]
pub fn mount<FS: Filesystem, P: AsRef<Path>>(
filesystem: FS,
mountpoint: P,
options: &[&OsStr],
) -> io::Result<()> {
let options = parse_options_from_args(options)?;
mount2(filesystem, mountpoint, options.as_ref())
}
pub fn mount2<FS: Filesystem, P: AsRef<Path>>(
filesystem: FS,
mountpoint: P,
options: &[MountOption],
) -> io::Result<()> {
check_option_conflicts(options)?;
Session::new(filesystem, mountpoint.as_ref(), options).and_then(|mut se| se.run())
}
#[deprecated(note = "use spawn_mount2() instead")]
pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef<Path>>(
filesystem: FS,
mountpoint: P,
options: &[&OsStr],
) -> io::Result<BackgroundSession> {
let options: Option<Vec<_>> = options
.iter()
.map(|x| Some(MountOption::from_str(x.to_str()?)))
.collect();
let options = options.ok_or(ErrorKind::InvalidData)?;
Session::new(filesystem, mountpoint.as_ref(), options.as_ref()).and_then(|se| se.spawn())
}
pub fn spawn_mount2<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef<Path>>(
filesystem: FS,
mountpoint: P,
options: &[MountOption],
) -> io::Result<BackgroundSession> {
check_option_conflicts(options)?;
Session::new(filesystem, mountpoint.as_ref(), options).and_then(|se| se.spawn())
}