vahanafs/src/fs/mount.rs
2025-02-28 02:50:43 -06:00

818 lines
22 KiB
Rust

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<String>,
}
impl KeyValuePair {
pub fn new(name: impl Into<String>, value: Option<impl Into<String>>) -> Self {
Self {
name: name.into(),
value: value.map(Into::into),
}
}
pub fn into_tuple(self) -> (String, Option<String>) {
(self.name, self.value)
}
}
impl From<KeyValuePair> for (String, Option<String>) {
fn from(kvp: KeyValuePair) -> Self {
kvp.into_tuple()
}
}
impl FromStr for KeyValuePair {
type Err = ParseKeyValuePairError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<MountOption>);
impl Deref for MountOptions {
type Target = Vec<MountOption>;
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<Self, Self::Err> {
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<Self, Self::Err> {
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<String> 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<Self, Self::Err> {
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<Self, Self::Err> {
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<IdRange> for MapUsers {
fn from(value: IdRange) -> Self {
Self::Uid(value)
}
}
impl From<PathBuf> for MapUsers {
fn from(value: PathBuf) -> Self {
Self::Namespace(value)
}
}
impl FromStr for MapUsers {
type Err = ParseUserMapError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<ParseIdRangeError> 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<Self, Self::Err> {
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<T>(Vec<T>);
impl<T: FromStr> FromStr for Options<T> {
type Err = T::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(',')
.map(T::from_str)
.process_results(|iter| Options::from(iter.collect::<Vec<_>>()))
}
}
impl<T: Display> Display for Options<T> {
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<T> From<Vec<T>> for Options<T> {
fn from(value: Vec<T>) -> Self {
Self(value)
}
}
#[derive(Debug)]
pub struct Sources(Options<OptionsSource>);
impl FromStr for Sources {
type Err = ParseOptionsSourceError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<Infallible> for ParseUncheckedOptionsError {
fn from(_value: Infallible) -> Self {
Self
}
}
#[derive(Debug)]
pub struct UncheckedOptions(Options<String>);
impl FromStr for UncheckedOptions {
type Err = ParseUncheckedOptionsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(Options::<String>::from_str(s)?))
}
}
impl Display for UncheckedOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> 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<Vec<String>> for UncheckedOptions {
fn from(value: Vec<String>) -> Self {
Self(Options::from(value))
}
}
impl From<Vec<&str>> for UncheckedOptions {
fn from(value: Vec<&str>) -> Self {
value
.into_iter()
.map(String::from)
.collect::<Vec<String>>()
.into()
}
}
impl From<UncheckedOptions> for Options<String> {
fn from(value: UncheckedOptions) -> Self {
value.0
}
}
impl From<Options<String>> for UncheckedOptions {
fn from(value: Options<String>) -> 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<ParseDeviceIdTypeError> for ParseMountOptionError {
fn from(value: ParseDeviceIdTypeError) -> Self {
Self::DeviceId(value)
}
}
impl From<ParseUserMapError> for ParseMountOptionError {
fn from(value: ParseUserMapError) -> Self {
Self::MapUsers(value)
}
}
impl From<ParseGidRangeError> for ParseMountOptionError {
fn from(value: ParseGidRangeError) -> Self {
Self::MapGroups(value)
}
}
impl From<ParseOptionsModeError> for ParseMountOptionError {
fn from(value: ParseOptionsModeError) -> Self {
Self::OptionsMode(value)
}
}
impl From<ParseUncheckedOptionsError> for ParseMountOptionError {
fn from(_value: ParseUncheckedOptionsError) -> Self {
unreachable!()
}
}
impl From<ParseOptionsSourceError> 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<Permissions>),
NoMtab,
OptionsMode(OptionsMode),
OptionsSource(Options<OptionsSource>),
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<Self, Self::Err> {
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<Subtree>),
}
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<Self, Self::Err> {
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<DeviceId>,
target: impl Into<PathBuf>,
options: impl Into<MountOptions>,
) -> Self {
Self {
source: src.into(),
target: target.into(),
options: options.into(),
}
}
pub fn exec(&self) -> std::io::Result<std::process::Output> {
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());
}
}