424 lines
11 KiB
Rust
424 lines
11 KiB
Rust
use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
|
|
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
|
|
|
use itertools::{peek_nth, Itertools};
|
|
|
|
#[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, PartialEq)]
|
|
pub enum ParseTypedIdRangeError {
|
|
IdRange(ParseUntypedIdRangeError),
|
|
MissingType(FromStrError),
|
|
}
|
|
|
|
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::MissingType(value)
|
|
}
|
|
}
|
|
|
|
impl From<ParseUntypedIdRangeError> for ParseTypedIdRangeError {
|
|
fn from(value: ParseUntypedIdRangeError) -> Self {
|
|
Self::IdRange(value)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct TypedIdRange(pub IdKind, pub UntypedIdRange);
|
|
|
|
impl FromStr for TypedIdRange {
|
|
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)?,
|
|
UntypedIdRange::from_str(rest)?,
|
|
)),
|
|
None => Ok(Self(IdKind::default(), UntypedIdRange::from_str(s)?)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for TypedIdRange {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}:{}", self.0, self.1)
|
|
}
|
|
}
|
|
|
|
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 ParseUntypedIdRangeError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
ParseUntypedIdRangeError::InvalidArguments(args) => write!(
|
|
f,
|
|
"invalid number of arguments given (got {args}, expected 3)"
|
|
),
|
|
ParseUntypedIdRangeError::NotANumber(err) => {
|
|
write!(f, "could not parse argument: {err}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for ParseUntypedIdRangeError {}
|
|
|
|
impl From<ParseIntError> for ParseUntypedIdRangeError {
|
|
fn from(value: ParseIntError) -> Self {
|
|
Self::NotANumber(value)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct UntypedIdRange(pub usize, pub usize, pub usize);
|
|
|
|
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(ParseUntypedIdRangeError::InvalidArguments(len));
|
|
}
|
|
|
|
let range: Vec<usize> = parts
|
|
.iter()
|
|
.map(|n| usize::from_str_radix(n, 10))
|
|
.process_results(|iter| iter.collect())?;
|
|
|
|
Ok(Self(range[0], range[1], range[2]))
|
|
}
|
|
}
|
|
|
|
impl Display for UntypedIdRange {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}:{}:{}", self.0, self.1, self.2)
|
|
}
|
|
}
|
|
|
|
impl From<TypedIdRange> for UntypedIdRange {
|
|
fn from(value: TypedIdRange) -> Self {
|
|
value.1
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
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> {
|
|
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.ranges.iter();
|
|
let delim = self.delimiter;
|
|
|
|
if let Some(head) = iter.next() {
|
|
write!(f, "{head}")?;
|
|
|
|
for item in iter {
|
|
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"
|
|
);
|
|
}
|
|
}
|