818 lines
22 KiB
Rust
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());
|
|
}
|
|
}
|