#![forbid(unsafe_code)]
use std::path::{Path, PathBuf};
use fuser::{Config, MountOption, SessionACL, spawn_mount};
use crate::platform::linux::{fuse_available, path_contains};
use crate::store::Store;
use super::filesystem::EntropyFs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MountError {
Preflight(String),
RecursiveBacking(String),
Store(String),
Mount(String),
}
impl std::fmt::Display for MountError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MountError::Preflight(m) => write!(f, "preflight: {m}"),
MountError::RecursiveBacking(m) => write!(f, "recursive backing: {m}"),
MountError::Store(m) => write!(f, "store: {m}"),
MountError::Mount(m) => write!(f, "mount: {m}"),
}
}
}
impl std::error::Error for MountError {}
#[derive(Debug, Clone)]
pub struct MountParams {
pub store_dir: PathBuf,
pub mountpoint: PathBuf,
pub read_only: bool,
pub allow_other: bool,
pub threads: usize,
pub fs_name: String,
pub background_optimize: bool,
}
pub fn preflight(params: &MountParams) -> Result<(), MountError> {
let avail = fuse_available();
if !avail.ready() {
return Err(MountError::Preflight(avail.diagnose().join("; ")));
}
if !params.mountpoint.exists() {
return Err(MountError::Preflight(format!(
"mountpoint {} does not exist",
params.mountpoint.display()
)));
}
if !params.mountpoint.is_dir() {
return Err(MountError::Preflight(format!(
"mountpoint {} is not a directory",
params.mountpoint.display()
)));
}
if path_contains(¶ms.mountpoint, ¶ms.store_dir) {
return Err(MountError::RecursiveBacking(format!(
"the backing store {} is inside the mountpoint {}",
params.store_dir.display(),
params.mountpoint.display()
)));
}
if path_contains(¶ms.store_dir, ¶ms.mountpoint) {
return Err(MountError::RecursiveBacking(format!(
"the mountpoint {} is inside the backing store {}",
params.mountpoint.display(),
params.store_dir.display()
)));
}
Ok(())
}
pub fn mount(params: &MountParams, store: Store) -> Result<fuser::BackgroundSession, MountError> {
preflight(params)?;
let fs = EntropyFs::new(std::sync::Arc::new(store));
mount_fs(fs, params)
}
pub fn mount_fs(
fs: EntropyFs,
params: &MountParams,
) -> Result<fuser::BackgroundSession, MountError> {
let mut mount_options = vec![
MountOption::FSName(params.fs_name.clone()),
MountOption::Subtype("entropyfs".into()),
MountOption::DefaultPermissions,
MountOption::NoAtime,
];
if params.read_only {
mount_options.push(MountOption::RO);
}
let acl = if params.allow_other {
SessionACL::All
} else {
SessionACL::Owner
};
#[allow(clippy::field_reassign_with_default)]
let mut config = Config::default();
config.mount_options = mount_options;
config.acl = acl;
config.n_threads = Some(params.threads.max(1));
let notifier_slot = fs.notifier_slot();
let worker_store = fs.shared_store();
let worker_ops = fs.ops();
let worker_stop = fs.worker_stop();
let session = spawn_mount(fs, ¶ms.mountpoint, &config)
.map_err(|e| MountError::Mount(e.to_string()))?;
if let Ok(mut slot) = notifier_slot.lock() {
*slot = Some(session.notifier());
}
if params.background_optimize {
let _ = crate::optimizer::background::spawn_background_worker(
worker_store,
worker_ops,
worker_stop,
crate::optimizer::policy::OptimizeOptions::default(),
);
}
Ok(session)
}
pub fn unmount(mountpoint: &Path) -> Result<(), String> {
let status = std::process::Command::new("fusermount3")
.arg("-u")
.arg(mountpoint)
.status()
.map_err(|e| format!("fusermount3: {e}"))?;
if status.success() {
Ok(())
} else {
Err(format!("fusermount3 -u failed with {status}"))
}
}