use itertools::Itertools; use std::{ convert::Infallible, error::Error, fmt::Display, ops::{Deref, DerefMut}, path::PathBuf, process::Command, str::FromStr, }; use super::{ id_mapping::{IdRange, ParseIdRangeError}, permission::Permissions, }; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParseKeyValuePairError; impl Display for ParseKeyValuePairError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "could not parse key value pair") } } impl Error for ParseKeyValuePairError {} #[derive(Debug)] pub struct KeyValuePair { name: String, value: Option, } impl KeyValuePair { pub fn new(name: impl Into, value: Option>) -> Self { Self { name: name.into(), value: value.map(Into::into), } } pub fn into_tuple(self) -> (String, Option) { (self.name, self.value) } } impl From for (String, Option) { fn from(kvp: KeyValuePair) -> Self { kvp.into_tuple() } } impl FromStr for KeyValuePair { type Err = ParseKeyValuePairError; fn from_str(s: &str) -> Result { match s.split_once('=') { Some((name, value)) => Ok(Self::new(name, Some(value))), None => Ok(Self::new(s, None::<&str>)), } } } impl Display for KeyValuePair { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.value { Some(value) => write!(f, "{}={}", self.name, value), None => write!(f, "{}", self.name), } } } #[derive(Debug, Default)] pub struct MountOptions(Vec); impl Deref for MountOptions { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for MountOptions { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl FromStr for MountOptions { type Err = ParseMountOptionError; fn from_str(s: &str) -> Result { s.split(',') .map(MountOption::from_str) .process_results(|it| MountOptions(it.collect())) } } impl Display for MountOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let options = self.0.iter().map(|x| x.to_string()).join(","); write!(f, "{options}") } } #[derive(Debug, Default)] pub enum Access { ReadOnly, #[default] ReadWrite, } impl Display for Access { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Access::ReadOnly => "read-only", Access::ReadWrite => "read-write", } ) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParseDeviceIdTypeError(String); impl Display for ParseDeviceIdTypeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} is not a valid device type", self.0) } } impl Error for ParseDeviceIdTypeError {} #[derive(Debug)] pub enum DeviceId { Label(String), Uuid(String), PartLabel(String), PartUuid(String), Id(String), Other(String), } impl FromStr for DeviceId { type Err = ParseDeviceIdTypeError; fn from_str(s: &str) -> Result { match s.split_once('=') { Some((key, value)) => match key { "LABEL" => Ok(Self::Label(value.to_string())), "UUID" => Ok(Self::Uuid(value.to_string())), "PARTLABEL" => Ok(Self::PartLabel(value.to_string())), "PARTUUID" => Ok(Self::PartUuid(value.to_string())), "ID" => Ok(Self::Id(value.to_string())), key => Err(ParseDeviceIdTypeError(key.to_string())), }, None => Ok(Self::Other(s.to_string())), } } } impl Display for DeviceId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { DeviceId::Label(label) => format!("LABEL={label}"), DeviceId::Uuid(uuid) => format!("UUID={uuid}"), DeviceId::PartLabel(label) => format!("PARTLABEL={label}"), DeviceId::PartUuid(uuid) => format!("PARTUUID={uuid}"), DeviceId::Id(id) => format!("ID={id}"), DeviceId::Other(other) => other.to_string(), } ) } } impl From for DeviceId { fn from(value: String) -> Self { Self::from_str(&value).unwrap_or_else(|_v| Self::Other(value)) } } impl From<&str> for DeviceId { fn from(value: &str) -> Self { Self::from_str(value).unwrap_or_else(|_v| Self::Other(value.to_string())) } } #[derive(Debug, PartialEq, Eq)] pub struct ParseOptionsModeError; impl Display for ParseOptionsModeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "could not parse options mode") } } impl Error for ParseOptionsModeError {} #[derive(Debug)] pub enum OptionsMode { Ignore, Append, Prepend, Replace, } impl FromStr for OptionsMode { type Err = ParseOptionsModeError; fn from_str(s: &str) -> Result { match s.trim() { "ignore" => Ok(Self::Ignore), "append" => Ok(Self::Append), "prepend" => Ok(Self::Prepend), "replace" => Ok(Self::Replace), _ => Err(ParseOptionsModeError), } } } impl Display for OptionsMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { OptionsMode::Ignore => "ignore", OptionsMode::Append => "append", OptionsMode::Prepend => "prepend", OptionsMode::Replace => "replace", } ) } } #[derive(Clone, Debug)] pub struct ParseOptionsSourceError(String); impl Display for ParseOptionsSourceError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "received an invalid option source: {}", self.0) } } impl Error for ParseOptionsSourceError {} #[derive(Debug)] pub enum OptionsSource { Mtab, Fstab, Disable, } impl FromStr for OptionsSource { type Err = ParseOptionsSourceError; fn from_str(s: &str) -> Result { match s { "mtab" => Ok(Self::Mtab), "fstab" => Ok(Self::Fstab), "disable" => Ok(Self::Disable), e => Err(ParseOptionsSourceError(e.to_string())), } } } impl Display for OptionsSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { OptionsSource::Mtab => "mtab", OptionsSource::Fstab => "fstab", OptionsSource::Disable => "disable", } ) } } #[derive(Debug, PartialEq, Eq)] pub struct ParseUserMapError; impl Display for ParseUserMapError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "could not parse user-map arguments") } } impl Error for ParseUserMapError {} #[derive(Debug)] pub enum MapUsers { Uid(IdRange), Namespace(PathBuf), } impl From for MapUsers { fn from(value: IdRange) -> Self { Self::Uid(value) } } impl From for MapUsers { fn from(value: PathBuf) -> Self { Self::Namespace(value) } } impl FromStr for MapUsers { type Err = ParseUserMapError; fn from_str(s: &str) -> Result { match IdRange::from_str(s) { Ok(range) => Ok(Self::from(range)), Err(_) => match PathBuf::from_str(s) { Ok(path) => Ok(Self::from(path)), Err(_) => Err(ParseUserMapError), }, } } } impl Display for MapUsers { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { MapUsers::Uid(range) => range.to_string(), MapUsers::Namespace(path) => path.to_string_lossy().to_string(), } ) } } #[derive(Debug)] pub struct ParseGidRangeError; impl Display for ParseGidRangeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "could not parse gid map") } } impl Error for ParseGidRangeError {} impl From for ParseGidRangeError { fn from(_value: ParseIdRangeError) -> Self { Self } } #[derive(Debug)] pub struct GidRange(IdRange); impl FromStr for GidRange { type Err = ParseGidRangeError; fn from_str(s: &str) -> Result { Ok(IdRange::from_str(s).map(Self)?) } } impl Display for GidRange { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Debug)] pub struct Options(Vec); impl FromStr for Options { type Err = T::Err; fn from_str(s: &str) -> Result { s.split(',') .map(T::from_str) .process_results(|iter| Options::from(iter.collect::>())) } } impl Display for Options { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut iter = self.0.iter(); if let Some(head) = iter.next() { write!(f, "{head}")?; for item in iter { write!(f, ",{item}")?; } } Ok(()) } } impl From> for Options { fn from(value: Vec) -> Self { Self(value) } } #[derive(Debug)] pub struct Sources(Options); impl FromStr for Sources { type Err = ParseOptionsSourceError; fn from_str(s: &str) -> Result { Ok(Self(Options::from_str(s)?)) } } #[derive(Debug)] pub struct ParseUncheckedOptionsError; impl Display for ParseUncheckedOptionsError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unreachable!() } } impl Error for ParseUncheckedOptionsError {} impl From for ParseUncheckedOptionsError { fn from(_value: Infallible) -> Self { Self } } #[derive(Debug)] pub struct UncheckedOptions(Options); impl FromStr for UncheckedOptions { type Err = ParseUncheckedOptionsError; fn from_str(s: &str) -> Result { Ok(Self(Options::::from_str(s)?)) } } impl Display for UncheckedOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl From for UncheckedOptions { fn from(value: String) -> Self { Self::from_str(&value).unwrap() } } impl From<&str> for UncheckedOptions { fn from(value: &str) -> Self { Self::from_str(value).unwrap() } } impl From> for UncheckedOptions { fn from(value: Vec) -> Self { Self(Options::from(value)) } } impl From> for UncheckedOptions { fn from(value: Vec<&str>) -> Self { value .into_iter() .map(String::from) .collect::>() .into() } } impl From for Options { fn from(value: UncheckedOptions) -> Self { value.0 } } impl From> for UncheckedOptions { fn from(value: Options) -> Self { Self(value) } } #[derive(Debug)] pub enum ParseMountOptionError { DeviceId(ParseDeviceIdTypeError), MapUsers(ParseUserMapError), MapGroups(ParseGidRangeError), OptionsMode(ParseOptionsModeError), OptionsSource(ParseOptionsSourceError), UnknownOption(String), } impl Display for ParseMountOptionError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::DeviceId(e) => write!(f, "{e}"), Self::MapUsers(e) => write!(f, "{e}"), Self::MapGroups(e) => write!(f, "{e}"), Self::OptionsMode(e) => write!(f, "{e}"), Self::OptionsSource(e) => write!(f, "{e}"), Self::UnknownOption(opt) => write!(f, "unknown option: {opt}"), } } } impl Error for ParseMountOptionError {} impl From for ParseMountOptionError { fn from(value: ParseDeviceIdTypeError) -> Self { Self::DeviceId(value) } } impl From for ParseMountOptionError { fn from(value: ParseUserMapError) -> Self { Self::MapUsers(value) } } impl From for ParseMountOptionError { fn from(value: ParseGidRangeError) -> Self { Self::MapGroups(value) } } impl From for ParseMountOptionError { fn from(value: ParseOptionsModeError) -> Self { Self::OptionsMode(value) } } impl From for ParseMountOptionError { fn from(_value: ParseUncheckedOptionsError) -> Self { unreachable!() } } impl From for ParseMountOptionError { fn from(value: ParseOptionsSourceError) -> Self { Self::OptionsSource(value) } } #[derive(Debug)] pub enum MountOption { Label(DeviceId), FsType(String), All, NoCanonicalize, Fake, Fork, Fstab(String), InternalOnly, ShowLabels, MapGroups(GidRange), MapUsers(MapUsers), MakeDir(Option), NoMtab, OptionsMode(OptionsMode), OptionsSource(Options), OptionsSourceForce, OnlyOnce, Options(UncheckedOptions), TestOptions(UncheckedOptions), Types(UncheckedOptions), Source(UncheckedOptions), Target(PathBuf), TargetPrefix(PathBuf), Verbose, Access(Access), Namespace(String), Operation(MountOperation), Subtree(Subtree), } impl FromStr for MountOption { type Err = ParseMountOptionError; fn from_str(s: &str) -> Result { let s = s.trim().trim_start_matches('-'); let option = match s.split_once(|delim| delim == ' ' || delim == '=') { Some((option, value)) => (option, Some(value)), None => (s, None), }; match option { ("L" | "label", Some(id)) => Ok(Self::Label(DeviceId::from_str(id)?)), ("t" | "types", Some(types)) => Ok(Self::FsType(types.to_string())), ("a" | "all", None) => Ok(Self::All), ("c" | "no-canonicalize", None) => Ok(Self::NoCanonicalize), ("f" | "fake", None) => Ok(Self::Fake), ("F" | "fork", None) => Ok(Self::Fork), ("T" | "fstab", Some(path)) => Ok(Self::Fstab(path.into())), ("i" | "internal-only", None) => Ok(Self::InternalOnly), ("l" | "show-labels", None) => Ok(Self::ShowLabels), ("map-groups", Some(map)) => Ok(Self::MapGroups(GidRange::from_str(map)?)), ("map-users", Some(map)) => Ok(Self::MapUsers(MapUsers::from_str(map)?)), ("m" | "mkdir", arg) => Ok(Self::MakeDir( arg.and_then(|arg| Permissions::from_str(arg).ok()), )), ("n" | "no-mtab", None) => Ok(Self::NoMtab), ("options-mode", Some(mode)) => Ok(Self::OptionsMode(OptionsMode::from_str(mode)?)), ("options-source", Some(source)) => Ok(Self::OptionsSource(Options::from_str(source)?)), ("options-source-force", None) => Ok(Self::OptionsSourceForce), ("onlyonce", None) => Ok(Self::OnlyOnce), ("o" | "options", Some(options)) => { Ok(Self::Options(UncheckedOptions::from_str(options)?)) } ("O" | "test-opts", Some(options)) => { Ok(Self::TestOptions(UncheckedOptions::from_str(options)?)) } ("r" | "read-only", None) => Ok(Self::Access(Access::ReadOnly)), ("source", Some(src)) => Ok(Self::Source(UncheckedOptions::from_str(src)?)), ("target", Some(target)) => Ok(Self::Target(target.into())), ("target-prefix", Some(path)) => Ok(Self::TargetPrefix(path.into())), ("w" | "rw" | "read-write", None) => Ok(Self::Access(Access::ReadWrite)), ("N" | "namespace", Some(ns)) => Ok(Self::Namespace(ns.into())), ("U" | "uuid", Some(uuid)) => Ok(Self::Label(DeviceId::Uuid(uuid.into()))), ("B" | "bind", None) => Ok(Self::Operation(MountOperation::Bind)), ("M" | "move", None) => Ok(Self::Operation(MountOperation::Move)), ("R" | "rbind", None) => Ok(Self::Operation(MountOperation::RecursiveBind)), ("make-shared", None) => Ok(Self::Subtree(Subtree::Shared)), ("make-slave", None) => Ok(Self::Subtree(Subtree::Replica)), ("make-private", None) => Ok(Self::Subtree(Subtree::Private)), ("make-unbindable", None) => Ok(Self::Subtree(Subtree::Unbindable)), ("make-rshared", None) => Ok(Self::Subtree(Subtree::Shared.recursive())), ("make-rslave", None) => Ok(Self::Subtree(Subtree::Replica.recursive())), ("make-rprivate", None) => Ok(Self::Subtree(Subtree::Private.recursive())), ("make-runbindable", None) => Ok(Self::Subtree(Subtree::Unbindable.recursive())), (opt, _) => Err(ParseMountOptionError::UnknownOption(opt.to_string())), } } } impl Display for MountOption { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Self::Label(id) => format!("--label {id}"), Self::FsType(t) => format!("--types {t}"), Self::All => "--all".to_string(), Self::NoCanonicalize => "--no-canonicalize".to_string(), Self::Fake => "--fake".to_string(), Self::Fork => "--fork".to_string(), Self::Fstab(path) => format!("--fstab {path}"), Self::InternalOnly => "--internal-only".to_string(), Self::ShowLabels => "--show-labels".to_string(), Self::MapGroups(range) => format!("--map-groups {range}"), Self::MapUsers(map) => format!("--map-users {map}"), Self::MakeDir(mode) => format!( "--mkdir {}", mode.clone() .map(|m| m.to_string()) .unwrap_or_else(String::new) ), Self::NoMtab => "--no-mtab".to_string(), Self::OptionsMode(mode) => format!("--options-mode {mode}"), Self::OptionsSource(source) => format!("--options-source {source}"), Self::OptionsSourceForce => "--options-source-force".to_string(), Self::OnlyOnce => "--onlyonce".to_string(), Self::Options(opts) => format!("--options {opts}"), Self::TestOptions(opts) => format!("--test-opts {opts}"), Self::Types(types) => format!("--types {types}"), Self::Source(src) => format!("--source {src}"), Self::Target(path) => format!("--target {}", path.to_string_lossy()), Self::TargetPrefix(path) => format!("--target-prefix {}", path.to_string_lossy()), Self::Verbose => "--verbose".to_string(), Self::Access(access) => format!("--{access}"), Self::Namespace(ns) => format!("--namespace {ns}"), Self::Operation(op) => format!("--{op}"), Self::Subtree(op) => format!("--make-{op}"), } ) } } #[derive(Debug)] pub enum Subtree { Shared, Replica, Private, Unbindable, Recursive(Box), } impl Subtree { pub fn recursive(self) -> Self { Self::Recursive(Box::new(self)) } } impl Display for Subtree { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Subtree::Shared => "shared".to_string(), Subtree::Replica => "slave".to_string(), Subtree::Private => "private".to_string(), Subtree::Unbindable => "unbindable".to_string(), Subtree::Recursive(op) => format!("r{op}"), }, ) } } #[derive(Debug)] pub struct ParseMountOperationError(String); impl Display for ParseMountOperationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "could not parse operation: {}", self.0) } } impl Error for ParseMountOperationError {} #[derive(Debug)] pub enum MountOperation { Bind, RecursiveBind, Move, } impl FromStr for MountOperation { type Err = ParseMountOperationError; fn from_str(s: &str) -> Result { match s { "bind" => Ok(Self::Bind), "rbind" => Ok(Self::RecursiveBind), "move" => Ok(Self::Move), v => Err(ParseMountOperationError(v.to_string())), } } } impl Display for MountOperation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { MountOperation::Bind => "bind", MountOperation::RecursiveBind => "rbind", MountOperation::Move => "move", } ) } } #[derive(Debug)] pub struct Mount { source: DeviceId, target: PathBuf, options: MountOptions, } impl Mount { pub fn new( src: impl Into, target: impl Into, options: impl Into, ) -> Self { Self { source: src.into(), target: target.into(), options: options.into(), } } pub fn exec(&self) -> std::io::Result { Command::new("mount") .arg(self.options.to_string()) .arg(self.source.to_string()) .arg(&self.target) .output() } pub fn test(&self) -> Command { let mut cmd = Command::new("mount"); cmd.arg(self.options.to_string()) .arg(self.source.to_string()) .arg(&self.target); cmd } } #[cfg(test)] mod tests { use super::{Mount, MountOption, MountOptions, Options}; #[test] fn it_works() { let mut options = MountOptions::default(); options.push(MountOption::Types(vec!["overlay"].into())); let mount = Mount::new("/test", "/target", options); println!("{:?}", mount.test()); } }