use libc::{EAGAIN, EINTR, ENODEV, ENOENT};
use log::{info, warn};
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::{io, ops::DerefMut};
use crate::ll::fuse_abi as abi;
use crate::request::Request;
use crate::Filesystem;
use crate::MountOption;
use crate::{channel::Channel, mnt::Mount};
#[cfg(feature = "abi-7-11")]
use crate::{channel::ChannelSender, notify::Notifier};
pub const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024;
const BUFFER_SIZE: usize = MAX_WRITE_SIZE + 4096;
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum SessionACL {
All,
RootAndOwner,
Owner,
}
#[derive(Debug)]
pub struct Session<FS: Filesystem> {
pub(crate) filesystem: FS,
ch: Channel,
mount: Arc<Mutex<Option<Mount>>>,
mountpoint: PathBuf,
pub(crate) allowed: SessionACL,
pub(crate) session_owner: u32,
pub(crate) proto_major: u32,
pub(crate) proto_minor: u32,
pub(crate) initialized: bool,
pub(crate) destroyed: bool,
}
impl<FS: Filesystem> Session<FS> {
pub fn new(
filesystem: FS,
mountpoint: &Path,
options: &[MountOption],
) -> io::Result<Session<FS>> {
info!("Mounting {}", mountpoint.display());
let (file, mount) = if options.contains(&MountOption::AutoUnmount)
&& !(options.contains(&MountOption::AllowRoot)
|| options.contains(&MountOption::AllowOther))
{
warn!("Given auto_unmount without allow_root or allow_other; adding allow_other, with userspace permission handling");
let mut modified_options = options.to_vec();
modified_options.push(MountOption::AllowOther);
Mount::new(mountpoint, &modified_options)?
} else {
Mount::new(mountpoint, options)?
};
let ch = Channel::new(file);
let allowed = if options.contains(&MountOption::AllowRoot) {
SessionACL::RootAndOwner
} else if options.contains(&MountOption::AllowOther) {
SessionACL::All
} else {
SessionACL::Owner
};
Ok(Session {
filesystem,
ch,
mount: Arc::new(Mutex::new(Some(mount))),
mountpoint: mountpoint.to_owned(),
allowed,
session_owner: unsafe { libc::geteuid() },
proto_major: 0,
proto_minor: 0,
initialized: false,
destroyed: false,
})
}
pub fn mountpoint(&self) -> &Path {
&self.mountpoint
}
pub fn run(&mut self) -> io::Result<()> {
let mut buffer = vec![0; BUFFER_SIZE];
let buf = aligned_sub_buf(
buffer.deref_mut(),
std::mem::align_of::<abi::fuse_in_header>(),
);
loop {
match self.ch.receive(buf) {
Ok(size) => match Request::new(self.ch.sender(), &buf[..size]) {
Some(req) => req.dispatch(self),
None => break,
},
Err(err) => match err.raw_os_error() {
Some(ENOENT) => continue,
Some(EINTR) => continue,
Some(EAGAIN) => continue,
Some(ENODEV) => break,
_ => return Err(err),
},
}
}
Ok(())
}
pub fn unmount(&mut self) {
drop(std::mem::take(&mut *self.mount.lock().unwrap()));
}
pub fn unmount_callable(&mut self) -> SessionUnmounter {
SessionUnmounter {
mount: self.mount.clone(),
}
}
#[cfg(feature = "abi-7-11")]
pub fn notifier(&self) -> Notifier {
Notifier::new(self.ch.sender())
}
}
#[derive(Debug)]
pub struct SessionUnmounter {
mount: Arc<Mutex<Option<Mount>>>,
}
impl SessionUnmounter {
pub fn unmount(&mut self) -> io::Result<()> {
drop(std::mem::take(&mut *self.mount.lock().unwrap()));
Ok(())
}
}
fn aligned_sub_buf(buf: &mut [u8], alignment: usize) -> &mut [u8] {
let off = alignment - (buf.as_ptr() as usize) % alignment;
if off == alignment {
buf
} else {
&mut buf[off..]
}
}
impl<FS: 'static + Filesystem + Send> Session<FS> {
pub fn spawn(self) -> io::Result<BackgroundSession> {
BackgroundSession::new(self)
}
}
impl<FS: Filesystem> Drop for Session<FS> {
fn drop(&mut self) {
if !self.destroyed {
self.filesystem.destroy();
self.destroyed = true;
}
info!("Unmounted {}", self.mountpoint().display());
}
}
pub struct BackgroundSession {
pub mountpoint: PathBuf,
pub guard: JoinHandle<io::Result<()>>,
#[cfg(feature = "abi-7-11")]
sender: ChannelSender,
_mount: Mount,
}
impl BackgroundSession {
pub fn new<FS: Filesystem + Send + 'static>(se: Session<FS>) -> io::Result<BackgroundSession> {
let mountpoint = se.mountpoint().to_path_buf();
#[cfg(feature = "abi-7-11")]
let sender = se.ch.sender();
let mount = std::mem::take(&mut *se.mount.lock().unwrap());
let mount = mount.ok_or_else(|| io::Error::from_raw_os_error(libc::ENODEV))?;
let guard = thread::spawn(move || {
let mut se = se;
se.run()
});
Ok(BackgroundSession {
mountpoint,
guard,
#[cfg(feature = "abi-7-11")]
sender,
_mount: mount,
})
}
pub fn join(self) {
let Self {
mountpoint: _,
guard,
#[cfg(feature = "abi-7-11")]
sender: _,
_mount,
} = self;
drop(_mount);
guard.join().unwrap().unwrap();
}
#[cfg(feature = "abi-7-11")]
pub fn notifier(&self) -> Notifier {
Notifier::new(self.sender.clone())
}
}
impl fmt::Debug for BackgroundSession {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"BackgroundSession {{ mountpoint: {:?}, guard: JoinGuard<()> }}",
self.mountpoint
)
}
}