feat: unit testing,,,,
This commit is contained in:
parent
2f1fc9ba90
commit
082d666fe0
5 changed files with 862 additions and 105 deletions
|
@ -3,7 +3,7 @@ pub use char_enum_derive;
|
|||
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FromCharError {
|
||||
message: String,
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ impl Display for FromCharError {
|
|||
|
||||
impl Error for FromCharError {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FromStrError {
|
||||
message: String,
|
||||
}
|
||||
|
|
|
@ -1,43 +1,61 @@
|
|||
use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
|
||||
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
||||
use std::{error::Error, fmt::Display, num::ParseIntError, ops::ControlFlow, str::FromStr};
|
||||
|
||||
use itertools::Itertools;
|
||||
use itertools::{peek_nth, Itertools};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, FromChar)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, FromChar)]
|
||||
pub enum IdKind {
|
||||
#[value = "u"]
|
||||
Uid,
|
||||
#[value = "g"]
|
||||
Gid,
|
||||
#[default]
|
||||
#[value = "b"]
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedIdRangeError(String);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ParseTypedIdRangeError {
|
||||
IdRange(ParseUntypedIdRangeError),
|
||||
MissingType(FromStrError),
|
||||
}
|
||||
|
||||
impl From<FromStrError> for TypedIdRangeError {
|
||||
impl Display for ParseTypedIdRangeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ParseTypedIdRangeError::IdRange(err) => write!(f, "{err}"),
|
||||
ParseTypedIdRangeError::MissingType(err) => write!(f, "{err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseTypedIdRangeError {}
|
||||
|
||||
impl From<FromStrError> for ParseTypedIdRangeError {
|
||||
fn from(value: FromStrError) -> Self {
|
||||
Self(value.to_string())
|
||||
Self::MissingType(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIdRangeError> for TypedIdRangeError {
|
||||
fn from(value: ParseIdRangeError) -> Self {
|
||||
Self(value.to_string())
|
||||
impl From<ParseUntypedIdRangeError> for ParseTypedIdRangeError {
|
||||
fn from(value: ParseUntypedIdRangeError) -> Self {
|
||||
Self::IdRange(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TypedIdRange(IdKind, IdRange);
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct TypedIdRange(pub IdKind, pub UntypedIdRange);
|
||||
|
||||
impl FromStr for TypedIdRange {
|
||||
type Err = TypedIdRangeError;
|
||||
type Err = ParseTypedIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once(":") {
|
||||
Some((kind, rest)) => Ok(Self(IdKind::from_str(kind)?, IdRange::from_str(rest)?)),
|
||||
None => Err(FromStrError::new(s.to_string()).into()),
|
||||
Some((kind, rest)) => Ok(Self(
|
||||
IdKind::from_str(kind)?,
|
||||
UntypedIdRange::from_str(rest)?,
|
||||
)),
|
||||
None => Ok(Self(IdKind::default(), UntypedIdRange::from_str(s)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,46 +66,52 @@ impl Display for TypedIdRange {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParseIdRangeError {
|
||||
impl From<UntypedIdRange> for TypedIdRange {
|
||||
fn from(value: UntypedIdRange) -> Self {
|
||||
Self(IdKind::Both, value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ParseUntypedIdRangeError {
|
||||
InvalidArguments(usize),
|
||||
NotANumber(ParseIntError),
|
||||
}
|
||||
|
||||
impl Display for ParseIdRangeError {
|
||||
impl Display for ParseUntypedIdRangeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ParseIdRangeError::InvalidArguments(args) => write!(
|
||||
ParseUntypedIdRangeError::InvalidArguments(args) => write!(
|
||||
f,
|
||||
"invalid number of arguments given (got {args}, expected 3)"
|
||||
),
|
||||
ParseIdRangeError::NotANumber(err) => {
|
||||
ParseUntypedIdRangeError::NotANumber(err) => {
|
||||
write!(f, "could not parse argument: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseIdRangeError {}
|
||||
impl Error for ParseUntypedIdRangeError {}
|
||||
|
||||
impl From<ParseIntError> for ParseIdRangeError {
|
||||
impl From<ParseIntError> for ParseUntypedIdRangeError {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::NotANumber(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct IdRange(usize, usize, usize);
|
||||
pub struct UntypedIdRange(pub usize, pub usize, pub usize);
|
||||
|
||||
impl FromStr for IdRange {
|
||||
type Err = ParseIdRangeError;
|
||||
impl FromStr for UntypedIdRange {
|
||||
type Err = ParseUntypedIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split(':').collect();
|
||||
|
||||
let len = parts.len();
|
||||
if len > 3 {
|
||||
return Err(ParseIdRangeError::InvalidArguments(len));
|
||||
if len != 3 {
|
||||
return Err(ParseUntypedIdRangeError::InvalidArguments(len));
|
||||
}
|
||||
|
||||
let range: Vec<usize> = parts
|
||||
|
@ -99,37 +123,302 @@ impl FromStr for IdRange {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for IdRange {
|
||||
impl Display for UntypedIdRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{}:{}", self.0, self.1, self.2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct IdMapping(Vec<IdRange>);
|
||||
impl From<TypedIdRange> for UntypedIdRange {
|
||||
fn from(value: TypedIdRange) -> Self {
|
||||
value.1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ParseIdRangeError(String);
|
||||
|
||||
impl Display for ParseIdRangeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseIdRangeError {}
|
||||
|
||||
impl From<ParseUntypedIdRangeError> for ParseIdRangeError {
|
||||
fn from(value: ParseUntypedIdRangeError) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseTypedIdRangeError> for ParseIdRangeError {
|
||||
fn from(value: ParseTypedIdRangeError) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum IdRange {
|
||||
Typed(TypedIdRange),
|
||||
Untyped(UntypedIdRange),
|
||||
}
|
||||
|
||||
impl FromStr for IdRange {
|
||||
type Err = ParseIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
TypedIdRange::from_str(s)
|
||||
.map(IdRange::Typed)
|
||||
.or_else(|_| Ok(UntypedIdRange::from_str(s)?.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IdRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
IdRange::Typed(range) => write!(f, "{range}"),
|
||||
IdRange::Untyped(range) => write!(f, "{range}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TypedIdRange> for IdRange {
|
||||
fn from(value: TypedIdRange) -> Self {
|
||||
Self::Typed(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UntypedIdRange> for IdRange {
|
||||
fn from(value: UntypedIdRange) -> Self {
|
||||
Self::Untyped(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Delimiter(char);
|
||||
|
||||
impl Default for Delimiter {
|
||||
fn default() -> Self {
|
||||
Self(',')
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Delimiter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Delimiter {
|
||||
fn from(value: char) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_split(s: &str, delim: char) -> Option<impl Iterator<Item = &str>> {
|
||||
let mut iter = peek_nth(s.split(delim));
|
||||
match iter.peek_nth(1) {
|
||||
Some(_) => Some(iter),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn split<'a, 'b>(s: &'a str, delim: &'b [char]) -> Option<(char, impl Iterator<Item = &'a str>)> {
|
||||
let mut found = None;
|
||||
|
||||
for d in delim {
|
||||
if let Some(iter) = try_split(s, *d) {
|
||||
found = Some((*d, iter));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct IdMapping {
|
||||
ranges: Vec<IdRange>,
|
||||
delimiter: Delimiter,
|
||||
}
|
||||
|
||||
impl IdMapping {
|
||||
pub fn new(
|
||||
ranges: impl Iterator<Item = impl Into<IdRange>>,
|
||||
delimiter: impl Into<Delimiter>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ranges: ranges.map_into().collect(),
|
||||
delimiter: delimiter.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_iter<'a>(s: impl IntoIterator<Item = &'a str>) -> Result<Self, ParseIdRangeError> {
|
||||
Ok(IdMapping {
|
||||
ranges: s
|
||||
.into_iter()
|
||||
.map_while(|v| IdRange::from_str(v).ok())
|
||||
.collect(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_delimiter(self, delimiter: impl Into<Delimiter>) -> Self {
|
||||
Self {
|
||||
delimiter: delimiter.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IdMapping {
|
||||
type Err = ParseIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split(',')
|
||||
.map(|s| IdRange::from_str(s))
|
||||
.process_results(|iter| Self(iter.collect()))
|
||||
match split(s, &[',', ' ']) {
|
||||
Some((d, iter)) => Ok(Self::from_iter(iter)?.with_delimiter(d)),
|
||||
None => Err(ParseIdRangeError(
|
||||
"none of the provided delimiters matched string".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IdMapping {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut iter = self.0.iter();
|
||||
let mut iter = self.ranges.iter();
|
||||
let delim = self.delimiter;
|
||||
|
||||
if let Some(head) = iter.next() {
|
||||
write!(f, "{head}")?;
|
||||
|
||||
for item in iter {
|
||||
write!(f, ",{item}")?;
|
||||
write!(f, "{delim}{item}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::fs::id_mapping::{
|
||||
IdKind, IdRange, ParseTypedIdRangeError, ParseUntypedIdRangeError,
|
||||
};
|
||||
|
||||
use super::{IdMapping, TypedIdRange, UntypedIdRange};
|
||||
|
||||
#[test]
|
||||
fn untyped_id_range_from_str() {
|
||||
let three_args = UntypedIdRange::from_str("1:100:3");
|
||||
assert_eq!(three_args, Ok(UntypedIdRange(1, 100, 3)));
|
||||
|
||||
let too_many = UntypedIdRange::from_str("1:100:3:4");
|
||||
assert_eq!(too_many, Err(ParseUntypedIdRangeError::InvalidArguments(4)));
|
||||
|
||||
let too_few = UntypedIdRange::from_str("1:100");
|
||||
assert_eq!(too_few, Err(ParseUntypedIdRangeError::InvalidArguments(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_id_range_from_str() {
|
||||
let uid_range = TypedIdRange::from_str("u:1:100:3");
|
||||
assert_eq!(
|
||||
uid_range,
|
||||
Ok(TypedIdRange(IdKind::Uid, UntypedIdRange(1, 100, 3)))
|
||||
);
|
||||
|
||||
let gid_range = TypedIdRange::from_str("g:1:100:3");
|
||||
assert_eq!(
|
||||
gid_range,
|
||||
Ok(TypedIdRange(IdKind::Gid, UntypedIdRange(1, 100, 3)))
|
||||
);
|
||||
|
||||
let both_range = TypedIdRange::from_str("b:1:100:3");
|
||||
assert_eq!(
|
||||
both_range,
|
||||
Ok(TypedIdRange(IdKind::Both, UntypedIdRange(1, 100, 3)))
|
||||
);
|
||||
|
||||
let too_many = TypedIdRange::from_str("u:1:100:3:4");
|
||||
assert_eq!(
|
||||
too_many,
|
||||
Err(ParseTypedIdRangeError::IdRange(
|
||||
ParseUntypedIdRangeError::InvalidArguments(4)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_mapping_from_str() {
|
||||
let map = IdMapping::from_str("1:100:3,2:101:5");
|
||||
|
||||
assert_eq!(
|
||||
map,
|
||||
Ok(IdMapping::new(
|
||||
vec![UntypedIdRange(1, 100, 3), UntypedIdRange(2, 101, 5)].into_iter(),
|
||||
','
|
||||
))
|
||||
);
|
||||
|
||||
let map = IdMapping::from_str("u:1000:0:1 g:1001:1:2 5000:1000:2");
|
||||
|
||||
assert_eq!(
|
||||
map,
|
||||
Ok(IdMapping::new(
|
||||
vec![
|
||||
IdRange::Typed(TypedIdRange(IdKind::Uid, UntypedIdRange(1000, 0, 1))),
|
||||
IdRange::Typed(TypedIdRange(IdKind::Gid, UntypedIdRange(1001, 1, 2))),
|
||||
IdRange::Untyped(UntypedIdRange(5000, 1000, 2)),
|
||||
]
|
||||
.into_iter(),
|
||||
' '
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_and_display_are_the_same() {
|
||||
assert_eq!(IdKind::from_str("u").unwrap().to_string(), "u");
|
||||
assert_eq!(IdKind::from_str("g").unwrap().to_string(), "g");
|
||||
assert_eq!(IdKind::from_str("b").unwrap().to_string(), "b");
|
||||
|
||||
assert_eq!(
|
||||
UntypedIdRange::from_str("1000:0:1").unwrap().to_string(),
|
||||
"1000:0:1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TypedIdRange::from_str("u:1000:0:1").unwrap().to_string(),
|
||||
"u:1000:0:1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
IdRange::from_str("u:1000:0:1").unwrap().to_string(),
|
||||
"u:1000:0:1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
IdRange::from_str("1000:0:1").unwrap().to_string(),
|
||||
"1000:0:1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
IdMapping::from_str("1000:0:1,2:1001:5")
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"1000:0:1,2:1001:5"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
IdMapping::from_str("b:1000:0:1 g:2:1001:5")
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"b:1000:0:1 g:2:1001:5"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
544
src/fs/mount.rs
544
src/fs/mount.rs
|
@ -1,3 +1,4 @@
|
|||
use char_enum::FromStrError;
|
||||
use itertools::Itertools;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
|
@ -10,7 +11,7 @@ use std::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
id_mapping::{IdRange, ParseIdRangeError},
|
||||
id_mapping::{ParseUntypedIdRangeError, UntypedIdRange},
|
||||
permission::Permissions,
|
||||
};
|
||||
|
||||
|
@ -25,7 +26,7 @@ impl Display for ParseKeyValuePairError {
|
|||
|
||||
impl Error for ParseKeyValuePairError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct KeyValuePair {
|
||||
name: String,
|
||||
value: Option<String>,
|
||||
|
@ -104,13 +105,36 @@ impl Display for MountOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ParseAccessError;
|
||||
|
||||
impl Display for ParseAccessError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "received invalid access type")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseAccessError {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Access {
|
||||
ReadOnly,
|
||||
#[default]
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
impl FromStr for Access {
|
||||
type Err = ParseAccessError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"read-only" => Ok(Self::ReadOnly),
|
||||
"read-write" => Ok(Self::ReadWrite),
|
||||
_ => Err(ParseAccessError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Access {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
|
@ -135,7 +159,7 @@ impl Display for ParseDeviceIdTypeError {
|
|||
|
||||
impl Error for ParseDeviceIdTypeError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DeviceId {
|
||||
Label(String),
|
||||
Uuid(String),
|
||||
|
@ -192,7 +216,7 @@ impl From<&str> for DeviceId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseOptionsModeError;
|
||||
|
||||
impl Display for ParseOptionsModeError {
|
||||
|
@ -203,7 +227,7 @@ impl Display for ParseOptionsModeError {
|
|||
|
||||
impl Error for ParseOptionsModeError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OptionsMode {
|
||||
Ignore,
|
||||
Append,
|
||||
|
@ -240,7 +264,7 @@ impl Display for OptionsMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseOptionsSourceError(String);
|
||||
|
||||
impl Display for ParseOptionsSourceError {
|
||||
|
@ -251,7 +275,7 @@ impl Display for ParseOptionsSourceError {
|
|||
|
||||
impl Error for ParseOptionsSourceError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OptionsSource {
|
||||
Mtab,
|
||||
Fstab,
|
||||
|
@ -285,7 +309,7 @@ impl Display for OptionsSource {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseUserMapError;
|
||||
|
||||
impl Display for ParseUserMapError {
|
||||
|
@ -296,14 +320,14 @@ impl Display for ParseUserMapError {
|
|||
|
||||
impl Error for ParseUserMapError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MapUsers {
|
||||
Uid(IdRange),
|
||||
Uid(UntypedIdRange),
|
||||
Namespace(PathBuf),
|
||||
}
|
||||
|
||||
impl From<IdRange> for MapUsers {
|
||||
fn from(value: IdRange) -> Self {
|
||||
impl From<UntypedIdRange> for MapUsers {
|
||||
fn from(value: UntypedIdRange) -> Self {
|
||||
Self::Uid(value)
|
||||
}
|
||||
}
|
||||
|
@ -318,7 +342,7 @@ impl FromStr for MapUsers {
|
|||
type Err = ParseUserMapError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match IdRange::from_str(s) {
|
||||
match UntypedIdRange::from_str(s) {
|
||||
Ok(range) => Ok(Self::from(range)),
|
||||
Err(_) => match PathBuf::from_str(s) {
|
||||
Ok(path) => Ok(Self::from(path)),
|
||||
|
@ -341,7 +365,7 @@ impl Display for MapUsers {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseGidRangeError;
|
||||
|
||||
impl Display for ParseGidRangeError {
|
||||
|
@ -352,20 +376,20 @@ impl Display for ParseGidRangeError {
|
|||
|
||||
impl Error for ParseGidRangeError {}
|
||||
|
||||
impl From<ParseIdRangeError> for ParseGidRangeError {
|
||||
fn from(_value: ParseIdRangeError) -> Self {
|
||||
impl From<ParseUntypedIdRangeError> for ParseGidRangeError {
|
||||
fn from(_value: ParseUntypedIdRangeError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GidRange(IdRange);
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct GidRange(UntypedIdRange);
|
||||
|
||||
impl FromStr for GidRange {
|
||||
type Err = ParseGidRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(IdRange::from_str(s).map(Self)?)
|
||||
Ok(UntypedIdRange::from_str(s).map(Self)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,9 +399,29 @@ impl Display for GidRange {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<UntypedIdRange> for GidRange {
|
||||
fn from(value: UntypedIdRange) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options<T>(Vec<T>);
|
||||
|
||||
impl<T: Clone> Clone for Options<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Options<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq> Eq for Options<T> {}
|
||||
|
||||
impl<T: FromStr> FromStr for Options<T> {
|
||||
type Err = T::Err;
|
||||
|
||||
|
@ -409,7 +453,7 @@ impl<T> From<Vec<T>> for Options<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Sources(Options<OptionsSource>);
|
||||
|
||||
impl FromStr for Sources {
|
||||
|
@ -420,11 +464,11 @@ impl FromStr for Sources {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseUncheckedOptionsError;
|
||||
|
||||
impl Display for ParseUncheckedOptionsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
@ -437,7 +481,7 @@ impl From<Infallible> for ParseUncheckedOptionsError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct UncheckedOptions(Options<String>);
|
||||
|
||||
impl FromStr for UncheckedOptions {
|
||||
|
@ -494,13 +538,15 @@ impl From<Options<String>> for UncheckedOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ParseMountOptionError {
|
||||
DeviceId(ParseDeviceIdTypeError),
|
||||
MapUsers(ParseUserMapError),
|
||||
MapGroups(ParseGidRangeError),
|
||||
OptionsMode(ParseOptionsModeError),
|
||||
OptionsSource(ParseOptionsSourceError),
|
||||
Operation(ParseMountOperationError),
|
||||
Subtree(ParseSubtreeError),
|
||||
UnknownOption(String),
|
||||
}
|
||||
|
||||
|
@ -512,6 +558,8 @@ impl Display for ParseMountOptionError {
|
|||
Self::MapGroups(e) => write!(f, "{e}"),
|
||||
Self::OptionsMode(e) => write!(f, "{e}"),
|
||||
Self::OptionsSource(e) => write!(f, "{e}"),
|
||||
Self::Operation(e) => write!(f, "{e}"),
|
||||
Self::Subtree(e) => write!(f, "{e}"),
|
||||
Self::UnknownOption(opt) => write!(f, "unknown option: {opt}"),
|
||||
}
|
||||
}
|
||||
|
@ -555,10 +603,22 @@ impl From<ParseOptionsSourceError> for ParseMountOptionError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
impl From<ParseMountOperationError> for ParseMountOptionError {
|
||||
fn from(value: ParseMountOperationError) -> Self {
|
||||
Self::Operation(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseSubtreeError> for ParseMountOptionError {
|
||||
fn from(value: ParseSubtreeError) -> Self {
|
||||
Self::Subtree(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MountOption {
|
||||
Label(DeviceId),
|
||||
FsType(String),
|
||||
FsType(UncheckedOptions),
|
||||
All,
|
||||
NoCanonicalize,
|
||||
Fake,
|
||||
|
@ -576,8 +636,7 @@ pub enum MountOption {
|
|||
OnlyOnce,
|
||||
Options(UncheckedOptions),
|
||||
TestOptions(UncheckedOptions),
|
||||
Types(UncheckedOptions),
|
||||
Source(UncheckedOptions),
|
||||
Source(DeviceId),
|
||||
Target(PathBuf),
|
||||
TargetPrefix(PathBuf),
|
||||
Verbose,
|
||||
|
@ -600,7 +659,7 @@ impl FromStr for MountOption {
|
|||
|
||||
match option {
|
||||
("L" | "label", Some(id)) => Ok(Self::Label(DeviceId::from_str(id)?)),
|
||||
("t" | "types", Some(types)) => Ok(Self::FsType(types.to_string())),
|
||||
("t" | "types", Some(types)) => Ok(Self::FsType(UncheckedOptions::from_str(types)?)),
|
||||
("a" | "all", None) => Ok(Self::All),
|
||||
("c" | "no-canonicalize", None) => Ok(Self::NoCanonicalize),
|
||||
("f" | "fake", None) => Ok(Self::Fake),
|
||||
|
@ -625,7 +684,7 @@ impl FromStr for MountOption {
|
|||
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)?)),
|
||||
("source", Some(src)) => Ok(Self::Source(DeviceId::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)),
|
||||
|
@ -634,14 +693,9 @@ impl FromStr for MountOption {
|
|||
("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())),
|
||||
(op, None) if op.starts_with("make-") => Ok(Self::Subtree(Subtree::from_str(
|
||||
op.strip_prefix("make-").unwrap(),
|
||||
)?)),
|
||||
(opt, _) => Err(ParseMountOptionError::UnknownOption(opt.to_string())),
|
||||
}
|
||||
}
|
||||
|
@ -677,7 +731,6 @@ impl Display for MountOption {
|
|||
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()),
|
||||
|
@ -691,7 +744,18 @@ impl Display for MountOption {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseSubtreeError(String);
|
||||
|
||||
impl Display for ParseSubtreeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "received invalid subtree: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseSubtreeError {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Subtree {
|
||||
Shared,
|
||||
Replica,
|
||||
|
@ -706,6 +770,26 @@ impl Subtree {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for Subtree {
|
||||
type Err = ParseSubtreeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"shared" => Ok(Self::Shared),
|
||||
"slave" | "replica" => Ok(Self::Replica),
|
||||
"private" => Ok(Self::Private),
|
||||
"unbindable" => Ok(Self::Unbindable),
|
||||
|
||||
"rshared" => Ok(Self::recursive(Self::Shared)),
|
||||
"rslave" | "rreplica" => Ok(Self::recursive(Self::Replica)),
|
||||
"rprivate" => Ok(Self::recursive(Self::Private)),
|
||||
"runbindable" => Ok(Self::recursive(Self::Unbindable)),
|
||||
|
||||
_ => Err(ParseSubtreeError(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Subtree {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
|
@ -722,7 +806,7 @@ impl Display for Subtree {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseMountOperationError(String);
|
||||
|
||||
impl Display for ParseMountOperationError {
|
||||
|
@ -733,7 +817,7 @@ impl Display for ParseMountOperationError {
|
|||
|
||||
impl Error for ParseMountOperationError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum MountOperation {
|
||||
Bind,
|
||||
RecursiveBind,
|
||||
|
@ -794,25 +878,381 @@ impl Mount {
|
|||
.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
|
||||
impl Display for Mount {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"mount {} {} {}",
|
||||
self.options,
|
||||
self.source,
|
||||
self.target.to_string_lossy()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Mount, MountOption, MountOptions, Options};
|
||||
use std::{borrow::Cow, path::PathBuf, str::FromStr};
|
||||
|
||||
use enumflags2::BitFlags;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::fs::{
|
||||
id_mapping::UntypedIdRange,
|
||||
mount::{ParseDeviceIdTypeError, ParseOptionsSourceError},
|
||||
permission::Permissions,
|
||||
};
|
||||
|
||||
use super::{
|
||||
Access, DeviceId, KeyValuePair, MapUsers, Mount, MountOperation, MountOption, MountOptions,
|
||||
Options, OptionsMode, OptionsSource, ParseMountOptionError, Subtree, UncheckedOptions,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn key_value() {
|
||||
let kv = KeyValuePair::from_str("test=value").unwrap();
|
||||
let k = KeyValuePair::from_str("keyword").unwrap();
|
||||
|
||||
assert_eq!(kv, KeyValuePair::new("test", Some("value")));
|
||||
assert_eq!(k, KeyValuePair::new("keyword", None::<String>));
|
||||
|
||||
assert_eq!(kv.to_string(), "test=value");
|
||||
assert_eq!(k.to_string(), "keyword");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access() {
|
||||
let read = Access::from_str("read-only").unwrap();
|
||||
|
||||
let read_write = Access::from_str("read-write").unwrap();
|
||||
|
||||
assert_eq!(read, Access::ReadOnly);
|
||||
assert_eq!(read_write, Access::ReadWrite);
|
||||
|
||||
assert_eq!(read.to_string(), "read-only");
|
||||
assert_eq!(read_write.to_string(), "read-write");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_id() {
|
||||
let label = DeviceId::from_str("LABEL=test").unwrap();
|
||||
let uuid = DeviceId::from_str("UUID=1234").unwrap();
|
||||
let pl = DeviceId::from_str("PARTLABEL=test").unwrap();
|
||||
let pu = DeviceId::from_str("PARTUUID=98776543").unwrap();
|
||||
let id = DeviceId::from_str("ID=awawa").unwrap();
|
||||
let other = DeviceId::from_str("awawa").unwrap();
|
||||
let should_err = DeviceId::from_str("GARBAGE=trash");
|
||||
|
||||
assert_eq!(label, DeviceId::Label("test".to_string()));
|
||||
assert_eq!(uuid, DeviceId::Uuid("1234".to_string()));
|
||||
assert_eq!(pl, DeviceId::PartLabel("test".to_string()));
|
||||
assert_eq!(pu, DeviceId::PartUuid("98776543".to_string()));
|
||||
assert_eq!(id, DeviceId::Id("awawa".to_string()));
|
||||
assert_eq!(other, DeviceId::Other("awawa".to_string()));
|
||||
assert_eq!(
|
||||
should_err,
|
||||
Err(ParseDeviceIdTypeError("GARBAGE".to_string()))
|
||||
);
|
||||
|
||||
assert_eq!(label.to_string(), "LABEL=test");
|
||||
assert_eq!(uuid.to_string(), "UUID=1234");
|
||||
assert_eq!(pl.to_string(), "PARTLABEL=test");
|
||||
assert_eq!(pu.to_string(), "PARTUUID=98776543");
|
||||
assert_eq!(id.to_string(), "ID=awawa");
|
||||
assert_eq!(other.to_string(), "awawa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn options_mode() {
|
||||
let ignore = OptionsMode::from_str("ignore").unwrap();
|
||||
let append = OptionsMode::from_str("append").unwrap();
|
||||
let prepend = OptionsMode::from_str("prepend").unwrap();
|
||||
let replace = OptionsMode::from_str("replace").unwrap();
|
||||
|
||||
assert_eq!(ignore, OptionsMode::Ignore);
|
||||
assert_eq!(append, OptionsMode::Append);
|
||||
assert_eq!(prepend, OptionsMode::Prepend);
|
||||
assert_eq!(replace, OptionsMode::Replace);
|
||||
|
||||
assert_eq!(ignore.to_string(), "ignore");
|
||||
assert_eq!(append.to_string(), "append");
|
||||
assert_eq!(prepend.to_string(), "prepend");
|
||||
assert_eq!(replace.to_string(), "replace");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn options_source() {
|
||||
let mtab = OptionsSource::from_str("mtab").unwrap();
|
||||
let fstab = OptionsSource::from_str("fstab").unwrap();
|
||||
let disable = OptionsSource::from_str("disable").unwrap();
|
||||
let should_err = OptionsSource::from_str("awawa");
|
||||
|
||||
assert_eq!(mtab, OptionsSource::Mtab);
|
||||
assert_eq!(fstab, OptionsSource::Fstab);
|
||||
assert_eq!(disable, OptionsSource::Disable);
|
||||
assert_eq!(
|
||||
should_err,
|
||||
Err(ParseOptionsSourceError("awawa".to_string()))
|
||||
);
|
||||
|
||||
assert_eq!(mtab.to_string(), "mtab");
|
||||
assert_eq!(fstab.to_string(), "fstab");
|
||||
assert_eq!(disable.to_string(), "disable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_users() {
|
||||
let uid_map = MapUsers::from_str("1000:1001:1").unwrap();
|
||||
let ns_map = MapUsers::from_str("/proc/1000/ns/user").unwrap();
|
||||
|
||||
assert_eq!(uid_map, MapUsers::Uid(UntypedIdRange(1000, 1001, 1)));
|
||||
assert_eq!(
|
||||
ns_map,
|
||||
MapUsers::Namespace(PathBuf::from_str("/proc/1000/ns/user").unwrap())
|
||||
);
|
||||
|
||||
assert_eq!(uid_map.to_string(), "1000:1001:1");
|
||||
assert_eq!(ns_map.to_string(), "/proc/1000/ns/user");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn options() {
|
||||
let opts = Options::<KeyValuePair>::from_str("key=value,keyword").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opts,
|
||||
Options(vec![
|
||||
KeyValuePair::new("key", Some("value")),
|
||||
KeyValuePair::new("keyword", None::<&str>)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(opts.to_string(), "key=value,keyword");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operations() {
|
||||
let bind = MountOperation::from_str("bind").unwrap();
|
||||
let mov = MountOperation::from_str("move").unwrap();
|
||||
let rbind = MountOperation::from_str("rbind").unwrap();
|
||||
|
||||
assert_eq!(bind, MountOperation::Bind);
|
||||
assert_eq!(mov, MountOperation::Move);
|
||||
assert_eq!(rbind, MountOperation::RecursiveBind);
|
||||
|
||||
assert_eq!(bind.to_string(), "bind");
|
||||
assert_eq!(mov.to_string(), "move");
|
||||
assert_eq!(rbind.to_string(), "rbind");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtree() {
|
||||
let shared = Subtree::from_str("shared").unwrap();
|
||||
let rshared = Subtree::from_str("rshared").unwrap();
|
||||
let private = Subtree::from_str("private").unwrap();
|
||||
let rprivate = Subtree::from_str("rprivate").unwrap();
|
||||
let replica = Subtree::from_str("replica").unwrap();
|
||||
let rreplica = Subtree::from_str("rreplica").unwrap();
|
||||
let unbind = Subtree::from_str("unbindable").unwrap();
|
||||
let runbind = Subtree::from_str("runbindable").unwrap();
|
||||
|
||||
assert_eq!(shared, Subtree::Shared);
|
||||
assert_eq!(rshared, Subtree::recursive(Subtree::Shared));
|
||||
assert_eq!(private, Subtree::Private);
|
||||
assert_eq!(rprivate, Subtree::recursive(Subtree::Private));
|
||||
assert_eq!(replica, Subtree::Replica);
|
||||
assert_eq!(rreplica, Subtree::recursive(Subtree::Replica));
|
||||
assert_eq!(unbind, Subtree::Unbindable);
|
||||
assert_eq!(runbind, Subtree::recursive(Subtree::Unbindable));
|
||||
|
||||
assert_eq!(shared.to_string(), "shared");
|
||||
assert_eq!(rshared.to_string(), "rshared");
|
||||
assert_eq!(private.to_string(), "private");
|
||||
assert_eq!(rprivate.to_string(), "rprivate");
|
||||
assert_eq!(replica.to_string(), "slave");
|
||||
assert_eq!(rreplica.to_string(), "rslave");
|
||||
assert_eq!(unbind.to_string(), "unbindable");
|
||||
assert_eq!(runbind.to_string(), "runbindable");
|
||||
}
|
||||
|
||||
fn try_opts<'a, 'b>(
|
||||
cmds: &[&'a str],
|
||||
args: impl Into<Option<&'b str>>,
|
||||
) -> Result<MountOption, ParseMountOptionError>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
let args = args.into();
|
||||
let to_args = |c: &'a str| -> Cow<'b, str> {
|
||||
match args {
|
||||
Some(x) => format!("{c} {x}").into(),
|
||||
None => c.into(),
|
||||
}
|
||||
};
|
||||
|
||||
cmds.iter().map(|s| to_args(s)).fold(
|
||||
Err(ParseMountOptionError::UnknownOption("".to_string())),
|
||||
|_, c| MountOption::from_str(&c),
|
||||
)
|
||||
}
|
||||
|
||||
struct Opts<'a, 'b> {
|
||||
options: Cow<'a, [&'a str]>,
|
||||
args: Option<&'b str>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Opts<'a, 'b> {
|
||||
fn assert(&self, expected: MountOption) {
|
||||
let opt = try_opts(&self.options, self.args).unwrap();
|
||||
assert_eq!(opt, expected);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<&'a str> for Opts<'a, 'b> {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Self {
|
||||
options: vec![value].into(),
|
||||
args: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<(&'a str, &'b str)> for Opts<'a, 'b> {
|
||||
fn from(value: (&'a str, &'b str)) -> Self {
|
||||
Self {
|
||||
options: vec![value.0].into(),
|
||||
args: Some(value.1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<&'a [&'a str]> for Opts<'a, 'b> {
|
||||
fn from(value: &'a [&'a str]) -> Self {
|
||||
Self {
|
||||
options: value.into(),
|
||||
args: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<(&'a [&str], &'b str)> for Opts<'a, 'b> {
|
||||
fn from(value: (&'a [&str], &'b str)) -> Self {
|
||||
Self {
|
||||
options: value.0.into(),
|
||||
args: Some(value.1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mount_options() {
|
||||
Opts::from((["-L", "--label"].as_slice(), "LABEL=label"))
|
||||
.assert(MountOption::Label(DeviceId::Label("label".to_string())));
|
||||
|
||||
Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs"))
|
||||
.assert(MountOption::FsType(vec!["nomsdos", "smbs"].into()));
|
||||
|
||||
Opts::from("-a").assert(MountOption::All);
|
||||
Opts::from(["-c", "--no-canonicalize"].as_slice()).assert(MountOption::NoCanonicalize);
|
||||
Opts::from(["-f", "--fake"].as_slice()).assert(MountOption::Fake);
|
||||
Opts::from(["-F", "--fork"].as_slice()).assert(MountOption::Fork);
|
||||
|
||||
Opts::from((["-T", "--fstab"].as_slice(), "/etc/fstab"))
|
||||
.assert(MountOption::Fstab("/etc/fstab".into()));
|
||||
|
||||
Opts::from(["-i", "--internal-only"].as_slice()).assert(MountOption::InternalOnly);
|
||||
Opts::from(["-l", "--show-labels"].as_slice()).assert(MountOption::ShowLabels);
|
||||
|
||||
Opts::from(("--map-groups", "1000:1001:1"))
|
||||
.assert(MountOption::MapGroups(UntypedIdRange(1000, 1001, 1).into()));
|
||||
|
||||
Opts::from(("--map-users", "1000:1001:1")).assert(MountOption::MapUsers(MapUsers::Uid(
|
||||
UntypedIdRange(1000, 1001, 1),
|
||||
)));
|
||||
|
||||
Opts::from(("--map-users", "/proc/1000/ns/user")).assert(MountOption::MapUsers(
|
||||
MapUsers::Namespace("/proc/1000/ns/user".into()),
|
||||
));
|
||||
|
||||
Opts::from(["-m", "--mkdir"].as_slice()).assert(MountOption::MakeDir(None));
|
||||
|
||||
Opts::from((["-m", "--mkdir"].as_slice(), "7777")).assert(MountOption::MakeDir(Some(
|
||||
Permissions::Octal(BitFlags::all().into()),
|
||||
)));
|
||||
|
||||
Opts::from(["-n", "--no-mtab"].as_slice()).assert(MountOption::NoMtab);
|
||||
|
||||
Opts::from(("--options-mode", "ignore"))
|
||||
.assert(MountOption::OptionsMode(OptionsMode::Ignore));
|
||||
|
||||
Opts::from(("--options-source", "fstab")).assert(MountOption::OptionsSource(
|
||||
vec![OptionsSource::Fstab].into(),
|
||||
));
|
||||
|
||||
Opts::from("--options-source-force").assert(MountOption::OptionsSourceForce);
|
||||
Opts::from("--onlyonce").assert(MountOption::OnlyOnce);
|
||||
|
||||
let opts: UncheckedOptions = vec!["kbity", "dogy", "aaaaaa=awawa"]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect_vec()
|
||||
.into();
|
||||
|
||||
Opts::from((["-o", "--options"].as_slice(), "kbity,dogy,aaaaaa=awawa"))
|
||||
.assert(MountOption::Options(opts.clone()));
|
||||
|
||||
Opts::from((["-O", "--test-opts"].as_slice(), "kbity,dogy,aaaaaa=awawa"))
|
||||
.assert(MountOption::TestOptions(opts));
|
||||
|
||||
Opts::from(["-r", "--read-only"].as_slice()).assert(MountOption::Access(Access::ReadOnly));
|
||||
|
||||
Opts::from(("--source", "LABEL=label"))
|
||||
.assert(MountOption::Source(DeviceId::Label("label".into())));
|
||||
|
||||
Opts::from(("--target", "/mnt")).assert(MountOption::Target("/mnt".into()));
|
||||
Opts::from(("--target-prefix", "/pfx")).assert(MountOption::TargetPrefix("/pfx".into()));
|
||||
|
||||
Opts::from(["-w", "--rw", "--read-write"].as_slice())
|
||||
.assert(MountOption::Access(Access::ReadWrite));
|
||||
|
||||
Opts::from((["-N", "--namespace"].as_slice(), "1000"))
|
||||
.assert(MountOption::Namespace("1000".into()));
|
||||
|
||||
Opts::from((["-U", "--uuid"].as_slice(), "1234"))
|
||||
.assert(MountOption::Label(DeviceId::Uuid("1234".into())));
|
||||
|
||||
Opts::from(["-B", "--bind"].as_slice())
|
||||
.assert(MountOption::Operation(MountOperation::Bind));
|
||||
Opts::from(["-M", "--move"].as_slice())
|
||||
.assert(MountOption::Operation(MountOperation::Move));
|
||||
Opts::from(["-R", "--rbind"].as_slice())
|
||||
.assert(MountOption::Operation(MountOperation::RecursiveBind));
|
||||
|
||||
Opts::from("--make-shared").assert(MountOption::Subtree(Subtree::Shared));
|
||||
Opts::from("--make-rshared")
|
||||
.assert(MountOption::Subtree(Subtree::recursive(Subtree::Shared)));
|
||||
|
||||
Opts::from("--make-private").assert(MountOption::Subtree(Subtree::Private));
|
||||
Opts::from("--make-rprivate")
|
||||
.assert(MountOption::Subtree(Subtree::recursive(Subtree::Private)));
|
||||
|
||||
Opts::from("--make-slave").assert(MountOption::Subtree(Subtree::Replica));
|
||||
Opts::from("--make-rslave")
|
||||
.assert(MountOption::Subtree(Subtree::recursive(Subtree::Replica)));
|
||||
|
||||
Opts::from("--make-unbindable").assert(MountOption::Subtree(Subtree::Unbindable));
|
||||
Opts::from("--make-runbindable").assert(MountOption::Subtree(Subtree::recursive(
|
||||
Subtree::Unbindable,
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut options = MountOptions::default();
|
||||
options.push(MountOption::Types(vec!["overlay"].into()));
|
||||
options.push(MountOption::FsType(vec!["overlay"].into()));
|
||||
let mount = Mount::new("/test", "/target", options);
|
||||
println!("{:?}", mount.test());
|
||||
println!("{mount}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ impl From<FromBitsError<Mode>> for ParseModeError {
|
|||
|
||||
#[bitflags]
|
||||
#[repr(u16)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
IXOTH = 0x0001,
|
||||
IWOTH = 0x0002,
|
||||
|
@ -59,14 +59,14 @@ impl Display for Mode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ModeFlags(BitFlags<Mode>);
|
||||
|
||||
impl FromStr for ModeFlags {
|
||||
type Err = ParseModeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let bits = u16::from_str(s)?;
|
||||
let bits = u16::from_str_radix(s, 16)?;
|
||||
|
||||
Ok(Self(BitFlags::from_bits(bits)?))
|
||||
}
|
||||
|
@ -78,7 +78,21 @@ impl Display for ModeFlags {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromChar)]
|
||||
impl TryFrom<u16> for ModeFlags {
|
||||
type Error = FromBitsError<Mode>;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
Ok(Self(BitFlags::from_bits(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitFlags<Mode>> for ModeFlags {
|
||||
fn from(value: BitFlags<Mode>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromChar, PartialEq, Eq)]
|
||||
pub enum Operator {
|
||||
#[value = "-"]
|
||||
Sub,
|
||||
|
@ -105,7 +119,7 @@ impl Operator {
|
|||
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, FromChar)]
|
||||
#[derive(Clone, Copy, Debug, FromChar, PartialEq, Eq)]
|
||||
pub enum SymbolicMode {
|
||||
#[value = 'r']
|
||||
Read,
|
||||
|
@ -123,7 +137,7 @@ pub enum SymbolicMode {
|
|||
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, FromChar)]
|
||||
#[derive(Clone, Copy, Debug, FromChar, PartialEq, Eq)]
|
||||
pub enum GroupId {
|
||||
#[value = 'u']
|
||||
User,
|
||||
|
@ -133,7 +147,7 @@ pub enum GroupId {
|
|||
Other,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Modes<T: BitFlag>(BitFlags<T>);
|
||||
|
||||
impl<T: BitFlag> Default for Modes<T> {
|
||||
|
@ -170,7 +184,7 @@ impl<T: BitFlag + Display> Display for Modes<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ParseSymbolicArgsError(FromStrError);
|
||||
|
||||
impl Display for ParseSymbolicArgsError {
|
||||
|
@ -193,7 +207,7 @@ impl From<FromStrError> for ParseSymbolicArgsError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum SymbolicArgs {
|
||||
Group(Modes<GroupId>),
|
||||
Mode(Modes<SymbolicMode>),
|
||||
|
@ -222,7 +236,7 @@ impl Display for SymbolicArgs {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ParseSymbolicError {
|
||||
Group(String),
|
||||
Operator(String),
|
||||
|
@ -251,7 +265,7 @@ impl From<ParseSymbolicArgsError> for ParseSymbolicError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SymbolicPermissions {
|
||||
groups: Modes<GroupId>,
|
||||
operator: Operator,
|
||||
|
@ -297,7 +311,7 @@ impl Display for SymbolicPermissions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseOctalError(String);
|
||||
|
||||
impl Display for ParseOctalError {
|
||||
|
@ -314,7 +328,7 @@ impl From<ParseModeError> for ParseOctalError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OctalPermissions(ModeFlags);
|
||||
|
||||
impl Display for OctalPermissions {
|
||||
|
@ -331,7 +345,21 @@ impl FromStr for OctalPermissions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
impl TryFrom<u16> for OctalPermissions {
|
||||
type Error = FromBitsError<Mode>;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
Ok(Self(ModeFlags::try_from(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitFlags<Mode>> for OctalPermissions {
|
||||
fn from(value: BitFlags<Mode>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ParsePermissionsError {
|
||||
Symbolic(ParseSymbolicError),
|
||||
Octal(ParseOctalError),
|
||||
|
@ -360,7 +388,7 @@ impl From<ParseOctalError> for ParsePermissionsError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Permissions {
|
||||
Symbolic(SymbolicPermissions),
|
||||
Octal(OctalPermissions),
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::fs::{id_mapping::IdMapping, AsIter, FileSystem, Mountpoint};
|
|||
|
||||
use super::{Stack, Stackable};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FuseOverlayOption {
|
||||
CloneFd,
|
||||
MaxIdleThreads(isize),
|
||||
|
|
Loading…
Add table
Reference in a new issue