use std::collections::HashSet;
use std::io;
use std::io::ErrorKind;
use crate::SessionACL;
#[derive(Debug, Clone, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct Config {
pub mount_options: Vec<MountOption>,
pub acl: SessionACL,
pub n_threads: Option<usize>,
pub clone_fd: bool,
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum MountOption {
FSName(String),
Subtype(String),
#[allow(clippy::upper_case_acronyms)]
CUSTOM(String),
AutoUnmount,
DefaultPermissions,
Dev,
NoDev,
Suid,
NoSuid,
RO,
RW,
Exec,
NoExec,
Atime,
NoAtime,
DirSync,
Sync,
Async,
}
impl MountOption {
#[cfg(test)]
fn from_str(s: &str) -> MountOption {
match s {
"auto_unmount" => MountOption::AutoUnmount,
"default_permissions" => MountOption::DefaultPermissions,
"dev" => MountOption::Dev,
"nodev" => MountOption::NoDev,
"suid" => MountOption::Suid,
"nosuid" => MountOption::NoSuid,
"ro" => MountOption::RO,
"rw" => MountOption::RW,
"exec" => MountOption::Exec,
"noexec" => MountOption::NoExec,
"atime" => MountOption::Atime,
"noatime" => MountOption::NoAtime,
"dirsync" => MountOption::DirSync,
"sync" => MountOption::Sync,
"async" => MountOption::Async,
x if x.starts_with("fsname=") => MountOption::FSName(x[7..].into()),
x if x.starts_with("subtype=") => MountOption::Subtype(x[8..].into()),
x => MountOption::CUSTOM(x.into()),
}
}
}
#[allow(dead_code)]
#[derive(PartialEq)]
pub(crate) enum MountOptionGroup {
KernelOption,
KernelFlag,
Fusermount,
}
pub(crate) fn check_option_conflicts(config: &Config) -> Result<(), io::Error> {
let options = &config.mount_options;
let mut options_set = HashSet::new();
options_set.extend(options.iter().cloned());
let conflicting: HashSet<MountOption> = options.iter().flat_map(conflicts_with).collect();
let intersection: Vec<MountOption> = conflicting.intersection(&options_set).cloned().collect();
if intersection.is_empty() {
Ok(())
} else {
Err(io::Error::new(
ErrorKind::InvalidInput,
format!("Conflicting mount options found: {intersection:?}"),
))
}
}
fn conflicts_with(option: &MountOption) -> Vec<MountOption> {
match option {
MountOption::FSName(_)
| MountOption::Subtype(_)
| MountOption::CUSTOM(_)
| MountOption::DirSync
| MountOption::AutoUnmount
| MountOption::DefaultPermissions => vec![],
MountOption::Dev => vec![MountOption::NoDev],
MountOption::NoDev => vec![MountOption::Dev],
MountOption::Suid => vec![MountOption::NoSuid],
MountOption::NoSuid => vec![MountOption::Suid],
MountOption::RO => vec![MountOption::RW],
MountOption::RW => vec![MountOption::RO],
MountOption::Exec => vec![MountOption::NoExec],
MountOption::NoExec => vec![MountOption::Exec],
MountOption::Atime => vec![MountOption::NoAtime],
MountOption::NoAtime => vec![MountOption::Atime],
MountOption::Sync => vec![MountOption::Async],
MountOption::Async => vec![MountOption::Sync],
}
}
#[allow(dead_code)]
pub(crate) fn option_to_string(option: &MountOption) -> String {
match option {
MountOption::FSName(name) => format!("fsname={name}"),
MountOption::Subtype(subtype) => format!("subtype={subtype}"),
MountOption::CUSTOM(value) => value.to_string(),
MountOption::AutoUnmount => "auto_unmount".to_string(),
MountOption::DefaultPermissions => "default_permissions".to_string(),
MountOption::Dev => "dev".to_string(),
MountOption::NoDev => "nodev".to_string(),
MountOption::Suid => "suid".to_string(),
MountOption::NoSuid => "nosuid".to_string(),
MountOption::RO => "ro".to_string(),
MountOption::RW => "rw".to_string(),
MountOption::Exec => "exec".to_string(),
MountOption::NoExec => "noexec".to_string(),
MountOption::Atime => "atime".to_string(),
MountOption::NoAtime => "noatime".to_string(),
MountOption::DirSync => "dirsync".to_string(),
MountOption::Sync => "sync".to_string(),
MountOption::Async => "async".to_string(),
}
}
#[allow(dead_code)]
pub(crate) fn option_group(option: &MountOption) -> MountOptionGroup {
match option {
MountOption::FSName(_) => MountOptionGroup::Fusermount,
MountOption::Subtype(_) => MountOptionGroup::Fusermount,
MountOption::CUSTOM(_) => MountOptionGroup::KernelOption,
MountOption::AutoUnmount => MountOptionGroup::Fusermount,
MountOption::Dev => MountOptionGroup::KernelFlag,
MountOption::NoDev => MountOptionGroup::KernelFlag,
MountOption::Suid => MountOptionGroup::KernelFlag,
MountOption::NoSuid => MountOptionGroup::KernelFlag,
MountOption::RO => MountOptionGroup::KernelFlag,
MountOption::RW => MountOptionGroup::KernelFlag,
MountOption::Exec => MountOptionGroup::KernelFlag,
MountOption::NoExec => MountOptionGroup::KernelFlag,
MountOption::Atime => MountOptionGroup::KernelFlag,
MountOption::NoAtime => MountOptionGroup::KernelFlag,
MountOption::DirSync => MountOptionGroup::KernelFlag,
MountOption::Sync => MountOptionGroup::KernelFlag,
MountOption::Async => MountOptionGroup::KernelFlag,
MountOption::DefaultPermissions => MountOptionGroup::KernelOption,
}
}
#[cfg(target_os = "linux")]
#[allow(dead_code)]
pub(crate) fn option_to_flag(option: &MountOption) -> io::Result<nix::mount::MsFlags> {
match option {
MountOption::Dev => Ok(nix::mount::MsFlags::empty()), MountOption::NoDev => Ok(nix::mount::MsFlags::MS_NODEV),
MountOption::Suid => Ok(nix::mount::MsFlags::empty()),
MountOption::NoSuid => Ok(nix::mount::MsFlags::MS_NOSUID),
MountOption::RW => Ok(nix::mount::MsFlags::empty()),
MountOption::RO => Ok(nix::mount::MsFlags::MS_RDONLY),
MountOption::Exec => Ok(nix::mount::MsFlags::empty()),
MountOption::NoExec => Ok(nix::mount::MsFlags::MS_NOEXEC),
MountOption::Atime => Ok(nix::mount::MsFlags::empty()),
MountOption::NoAtime => Ok(nix::mount::MsFlags::MS_NOATIME),
MountOption::Async => Ok(nix::mount::MsFlags::empty()),
MountOption::Sync => Ok(nix::mount::MsFlags::MS_SYNCHRONOUS),
MountOption::DirSync => Ok(nix::mount::MsFlags::MS_DIRSYNC),
option => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid mount option for flag conversion: {option:?}"),
)),
}
}
#[cfg(target_os = "macos")]
#[allow(dead_code)]
pub(crate) fn option_to_flag(option: &MountOption) -> io::Result<nix::mount::MntFlags> {
match option {
MountOption::Dev => Ok(nix::mount::MntFlags::empty()), MountOption::NoDev => Ok(nix::mount::MntFlags::MNT_NODEV),
MountOption::Suid => Ok(nix::mount::MntFlags::empty()),
MountOption::NoSuid => Ok(nix::mount::MntFlags::MNT_NOSUID),
MountOption::RW => Ok(nix::mount::MntFlags::empty()),
MountOption::RO => Ok(nix::mount::MntFlags::MNT_RDONLY),
MountOption::Exec => Ok(nix::mount::MntFlags::empty()),
MountOption::NoExec => Ok(nix::mount::MntFlags::MNT_NOEXEC),
MountOption::Atime => Ok(nix::mount::MntFlags::empty()),
MountOption::NoAtime => Ok(nix::mount::MntFlags::MNT_NOATIME),
MountOption::Async => Ok(nix::mount::MntFlags::empty()),
MountOption::Sync => Ok(nix::mount::MntFlags::MNT_SYNCHRONOUS),
option => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid mount option for flag conversion: {option:?}"),
)),
}
}
#[cfg_attr(
any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd"
),
allow(dead_code)
)]
#[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd"
))]
pub(crate) fn option_to_flag(option: &MountOption) -> io::Result<nix::mount::MntFlags> {
match option {
MountOption::Dev => Ok(nix::mount::MntFlags::empty()),
#[cfg(target_os = "freebsd")]
MountOption::NoDev => Err(io::Error::new(
io::ErrorKind::Unsupported,
"NoDev option is not supported on FreeBSD",
)),
#[cfg(not(target_os = "freebsd"))]
MountOption::NoDev => Ok(nix::mount::MntFlags::MNT_NODEV),
MountOption::Suid => Ok(nix::mount::MntFlags::empty()),
MountOption::NoSuid => Ok(nix::mount::MntFlags::MNT_NOSUID),
MountOption::RW => Ok(nix::mount::MntFlags::empty()),
MountOption::RO => Ok(nix::mount::MntFlags::MNT_RDONLY),
MountOption::Exec => Ok(nix::mount::MntFlags::empty()),
MountOption::NoExec => Ok(nix::mount::MntFlags::MNT_NOEXEC),
MountOption::Atime => Ok(nix::mount::MntFlags::empty()),
MountOption::NoAtime => Ok(nix::mount::MntFlags::MNT_NOATIME),
MountOption::Async => Ok(nix::mount::MntFlags::MNT_ASYNC),
MountOption::Sync => Ok(nix::mount::MntFlags::MNT_SYNCHRONOUS),
MountOption::DirSync => Ok(nix::mount::MntFlags::MNT_SYNCHRONOUS),
option => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid mount option for flag conversion: {option:?}"),
)),
}
}
#[cfg(test)]
mod test {
use crate::mnt::mount_options::*;
#[test]
fn option_checking() {
assert!(
check_option_conflicts(&Config {
mount_options: vec![MountOption::Suid, MountOption::NoSuid],
..Config::default()
})
.is_err()
);
assert!(
check_option_conflicts(&Config {
mount_options: vec![MountOption::Suid, MountOption::NoExec],
..Config::default()
})
.is_ok()
);
}
#[test]
fn option_round_trip() {
use crate::mnt::mount_options::MountOption::*;
for x in &[
FSName("Blah".to_owned()),
Subtype("Bloo".to_owned()),
CUSTOM("bongos".to_owned()),
AutoUnmount,
DefaultPermissions,
Dev,
NoDev,
Suid,
NoSuid,
RO,
RW,
Exec,
NoExec,
Atime,
NoAtime,
DirSync,
Sync,
Async,
] {
assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref()));
}
}
}