more unit testing,, aaa
This commit is contained in:
parent
cd0a1527f2
commit
af4af462bd
5 changed files with 337 additions and 185 deletions
|
@ -58,7 +58,7 @@ pub fn as_char_derive(input: TokenStream) -> TokenStream {
|
||||||
fn from_char(c: char) -> Result<Self, Self::Err> {
|
fn from_char(c: char) -> Result<Self, Self::Err> {
|
||||||
match c {
|
match c {
|
||||||
#(#from_char_variants)*
|
#(#from_char_variants)*
|
||||||
ch => Err(char_enum::FromCharError::new(format!("{ch} is not a valid variant. expected one of: #(#chars)*")))
|
ch => Err(char_enum::FromCharError(ch))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ pub fn as_char_derive(input: TokenStream) -> TokenStream {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.chars().next() {
|
match s.chars().next() {
|
||||||
Some(c) => Ok(Self::from_char(c)?),
|
Some(c) => Ok(Self::from_char(c)?),
|
||||||
None => Err(char_enum::FromStrError::new(format!("{s} does not correspond to a valid #name variant"))),
|
None => Err(char_enum::FromStrError(s.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
|
use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
|
||||||
|
use std::ops::Deref;
|
||||||
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
||||||
|
|
||||||
use itertools::{peek_nth, Itertools};
|
use itertools::{peek_nth, Itertools};
|
||||||
|
@ -135,6 +136,12 @@ impl From<TypedIdRange> for UntypedIdRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(usize, usize, usize)> for UntypedIdRange {
|
||||||
|
fn from(value: (usize, usize, usize)) -> Self {
|
||||||
|
Self(value.0, value.1, value.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ParseIdRangeError(String);
|
pub struct ParseIdRangeError(String);
|
||||||
|
|
||||||
|
@ -278,9 +285,10 @@ impl FromStr for IdMapping {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match split(s, &[',', ' ']) {
|
match split(s, &[',', ' ']) {
|
||||||
Some((d, iter)) => Ok(Self::from_iter(iter)?.with_delimiter(d)),
|
Some((d, iter)) => Ok(Self::from_iter(iter)?.with_delimiter(d)),
|
||||||
None => Err(ParseIdRangeError(
|
None => {
|
||||||
"none of the provided delimiters matched string".to_string(),
|
let s = std::slice::from_ref(&s).iter().map(Deref::deref);
|
||||||
)),
|
Ok(Self::from_iter(s)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,9 +219,7 @@ impl FromStr for SymbolicArgs {
|
||||||
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Mode(perms)),
|
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Mode(perms)),
|
||||||
Ok(_) | Err(_) => match Modes::<GroupId>::from_str(s) {
|
Ok(_) | Err(_) => match Modes::<GroupId>::from_str(s) {
|
||||||
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Group(perms)),
|
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Group(perms)),
|
||||||
Ok(_) | Err(_) => {
|
Ok(_) | Err(_) => Err(FromStrError(s.to_string()).into()),
|
||||||
Err(FromStrError::new(format!("{} is not a valid argument", s)).into())
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,12 @@ use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
|
slice::Iter,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::fs::{
|
use crate::fs::{
|
||||||
id_mapping::{IdMapping, ParseIdRangeError},
|
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
|
||||||
mount::MountOptions,
|
mount::MountOptions,
|
||||||
AsIter, FileSystem, Mountpoint,
|
AsIter, FileSystem, Mountpoint,
|
||||||
};
|
};
|
||||||
|
@ -18,8 +19,8 @@ use crate::macros::*;
|
||||||
|
|
||||||
use super::Stack;
|
use super::Stack;
|
||||||
|
|
||||||
macro_rules! impl_wrapper {
|
macro_rules! impl_deref {
|
||||||
($name:ident($inner:ty), $err:ty, $from_str:block) => {
|
($name:ident($inner:ty)) => {
|
||||||
impl Deref for $name {
|
impl Deref for $name {
|
||||||
type Target = $inner;
|
type Target = $inner;
|
||||||
|
|
||||||
|
@ -27,16 +28,23 @@ macro_rules! impl_wrapper {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_fromstr {
|
||||||
|
($name:ident($inner:ty), $err:ty) => {
|
||||||
impl FromStr for $name {
|
impl FromStr for $name {
|
||||||
type Err = $err;
|
type Err = $err;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
$from_str
|
Ok(Self(<$inner>::from_str(s)?))
|
||||||
//Ok(Self($target::from_str(s)?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_display {
|
||||||
|
($name:ident) => {
|
||||||
impl Display for $name {
|
impl Display for $name {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
|
@ -45,12 +53,13 @@ macro_rules! impl_wrapper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
macro_rules! impl_wrapper_struct {
|
||||||
pub struct Test(usize);
|
($name:ident($inner:ty), $err:ty) => {
|
||||||
|
impl_deref!($name($inner));
|
||||||
impl_wrapper!(Test(usize), ParseIntError, {
|
impl_fromstr!($name($inner), $err);
|
||||||
Ok(Test(usize::from_str(s)?))
|
impl_display!($name);
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ParseMaxIdleThreadsError(ParseIntError);
|
pub struct ParseMaxIdleThreadsError(ParseIntError);
|
||||||
|
@ -85,9 +94,39 @@ impl_wrapper_err_for!(ParseSquashToGidError(ParseSquashToIdError), "invalid gid"
|
||||||
impl_wrapper_err_for!(ParseUidMappingError(ParseIdRangeError), "invalid uid");
|
impl_wrapper_err_for!(ParseUidMappingError(ParseIdRangeError), "invalid uid");
|
||||||
impl_wrapper_err_for!(ParseGidMappingError(ParseIdRangeError), "invalid gid");
|
impl_wrapper_err_for!(ParseGidMappingError(ParseIdRangeError), "invalid gid");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct DirIter<'a> {
|
||||||
|
inner: Iter<'a, PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DirIter<'a> {
|
||||||
|
type Item = &'a Path;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.inner.next().map(PathBuf::as_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct LowerDirs(Vec<PathBuf>);
|
pub struct LowerDirs(Vec<PathBuf>);
|
||||||
|
|
||||||
|
impl<A: Into<PathBuf>> FromIterator<A> for LowerDirs {
|
||||||
|
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||||
|
Self(iter.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a LowerDirs {
|
||||||
|
type Item = &'a Path;
|
||||||
|
type IntoIter = DirIter<'a>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
DirIter {
|
||||||
|
inner: self.as_slice().iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for LowerDirs {
|
impl Deref for LowerDirs {
|
||||||
type Target = Vec<PathBuf>;
|
type Target = Vec<PathBuf>;
|
||||||
|
|
||||||
|
@ -98,14 +137,16 @@ impl Deref for LowerDirs {
|
||||||
|
|
||||||
impl Display for LowerDirs {
|
impl Display for LowerDirs {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
let mut iter = self.0.iter();
|
||||||
f,
|
if let Some(head) = iter.next() {
|
||||||
"{}",
|
write!(f, "{}", head.to_string_lossy())?;
|
||||||
self.0
|
|
||||||
.iter()
|
for item in iter {
|
||||||
.map(|v| v.to_string_lossy())
|
write!(f, ",{}", item.to_string_lossy())?;
|
||||||
.fold(String::new(), |a, b| a + &b)
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +154,12 @@ impl FromStr for LowerDirs {
|
||||||
type Err = Infallible;
|
type Err = Infallible;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(Self(s.split(',').map(Into::into).collect()))
|
Ok(Self(
|
||||||
|
s.split(',')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,22 +187,16 @@ impl From<Vec<PathBuf>> for LowerDirs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
pub struct MaxIdleThreads(Option<usize>);
|
pub struct MaxIdleThreads(Option<usize>);
|
||||||
|
|
||||||
impl Default for MaxIdleThreads {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for MaxIdleThreads {
|
impl FromStr for MaxIdleThreads {
|
||||||
type Err = ParseMaxIdleThreadsError;
|
type Err = ParseMaxIdleThreadsError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"-1" => Ok(Self(None)),
|
"-1" => Ok(Self(None)),
|
||||||
s => Ok(usize::from_str_radix(s, 10).map(Some).map(Self)?),
|
s => Ok(s.parse::<usize>().map(Some).map(Self)?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,6 +210,8 @@ impl Display for MaxIdleThreads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_deref!(MaxIdleThreads(Option<usize>));
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct MaxThreads(usize);
|
pub struct MaxThreads(usize);
|
||||||
|
|
||||||
|
@ -187,127 +229,59 @@ impl FromStr for MaxThreads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for MaxThreads {
|
impl_deref!(MaxThreads(usize));
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
impl_display!(MaxThreads);
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct SquashToId(usize);
|
pub struct SquashToId(usize);
|
||||||
|
|
||||||
|
impl_deref!(SquashToId(usize));
|
||||||
|
impl_display!(SquashToId);
|
||||||
|
|
||||||
impl FromStr for SquashToId {
|
impl FromStr for SquashToId {
|
||||||
type Err = ParseSquashToIdError;
|
type Err = ParseSquashToIdError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(usize::from_str_radix(s, 10).map(Self)?)
|
Ok(Self(s.parse::<usize>()?))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SquashToId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct SquashToUid(SquashToId);
|
pub struct SquashToUid(SquashToId);
|
||||||
|
|
||||||
impl Deref for SquashToUid {
|
impl_wrapper_struct!(SquashToUid(SquashToId), ParseSquashToUidError);
|
||||||
type Target = SquashToId;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for SquashToUid {
|
|
||||||
type Err = ParseSquashToUidError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(SquashToId::from_str(s)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SquashToUid {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct SquashToGid(SquashToId);
|
pub struct SquashToGid(SquashToId);
|
||||||
|
|
||||||
impl Deref for SquashToGid {
|
impl_wrapper_struct!(SquashToGid(SquashToId), ParseSquashToGidError);
|
||||||
type Target = SquashToId;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for SquashToGid {
|
|
||||||
type Err = ParseSquashToGidError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(SquashToId::from_str(s)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SquashToGid {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct GidMapping(IdMapping);
|
pub struct IdMapping(OriginalIdMapping);
|
||||||
|
|
||||||
impl Deref for GidMapping {
|
impl IdMapping {
|
||||||
type Target = IdMapping;
|
pub fn new(ranges: impl IntoIterator<Item = impl Into<IdRange>>) -> Self {
|
||||||
|
Self(OriginalIdMapping::new(ranges.into_iter(), ','))
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for GidMapping {
|
impl From<OriginalIdMapping> for IdMapping {
|
||||||
type Err = ParseGidMappingError;
|
fn from(value: OriginalIdMapping) -> Self {
|
||||||
|
Self(value)
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(IdMapping::from_str(s)?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for GidMapping {
|
impl_wrapper_struct!(IdMapping(OriginalIdMapping), ParseIdRangeError);
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
}
|
pub struct UidMapping(IdMapping);
|
||||||
}
|
|
||||||
|
impl_wrapper_struct!(UidMapping(IdMapping), ParseUidMappingError);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct GidMapping(IdMapping);
|
pub struct GidMapping(IdMapping);
|
||||||
|
|
||||||
impl Deref for GidMapping {
|
impl_wrapper_struct!(GidMapping(IdMapping), ParseGidMappingError);
|
||||||
type Target = IdMapping;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for GidMapping {
|
|
||||||
type Err = ParseGidMappingError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(IdMapping::from_str(s)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for GidMapping {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ParseFuseOverlayOptionError {
|
pub enum ParseFuseOverlayOptionError {
|
||||||
|
@ -317,6 +291,7 @@ pub enum ParseFuseOverlayOptionError {
|
||||||
SquashToGid(ParseSquashToGidError),
|
SquashToGid(ParseSquashToGidError),
|
||||||
UidMapping(ParseUidMappingError),
|
UidMapping(ParseUidMappingError),
|
||||||
GidMapping(ParseGidMappingError),
|
GidMapping(ParseGidMappingError),
|
||||||
|
UnknownOption(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ParseFuseOverlayOptionError {
|
impl Display for ParseFuseOverlayOptionError {
|
||||||
|
@ -328,6 +303,7 @@ impl Display for ParseFuseOverlayOptionError {
|
||||||
Self::SquashToGid(e) => write!(f, "{e}"),
|
Self::SquashToGid(e) => write!(f, "{e}"),
|
||||||
Self::UidMapping(e) => write!(f, "{e}"),
|
Self::UidMapping(e) => write!(f, "{e}"),
|
||||||
Self::GidMapping(e) => write!(f, "{e}"),
|
Self::GidMapping(e) => write!(f, "{e}"),
|
||||||
|
Self::UnknownOption(opt) => write!(f, "unknown option: {opt}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,7 +313,11 @@ impl Error for ParseFuseOverlayOptionError {}
|
||||||
impl_from_variants!(
|
impl_from_variants!(
|
||||||
ParseFuseOverlayOptionError,
|
ParseFuseOverlayOptionError,
|
||||||
MaxIdleThreads(ParseMaxIdleThreadsError),
|
MaxIdleThreads(ParseMaxIdleThreadsError),
|
||||||
MaxThreads(ParseMaxThreadsError)
|
MaxThreads(ParseMaxThreadsError),
|
||||||
|
SquashToUid(ParseSquashToUidError),
|
||||||
|
SquashToGid(ParseSquashToGidError),
|
||||||
|
UidMapping(ParseUidMappingError),
|
||||||
|
GidMapping(ParseGidMappingError)
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -355,8 +335,8 @@ pub enum FuseOverlayOption {
|
||||||
SquashToGid(SquashToGid),
|
SquashToGid(SquashToGid),
|
||||||
StaticNLink,
|
StaticNLink,
|
||||||
NoAcl,
|
NoAcl,
|
||||||
UidMapping(IdMapping),
|
UidMapping(UidMapping),
|
||||||
GidMapping(IdMapping),
|
GidMapping(GidMapping),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from_variants!(
|
impl_from_variants!(
|
||||||
|
@ -365,7 +345,9 @@ impl_from_variants!(
|
||||||
MaxIdleThreads(MaxIdleThreads),
|
MaxIdleThreads(MaxIdleThreads),
|
||||||
MaxThreads(MaxThreads),
|
MaxThreads(MaxThreads),
|
||||||
SquashToUid(SquashToUid),
|
SquashToUid(SquashToUid),
|
||||||
SquashToGid(SquashToGid)
|
SquashToGid(SquashToGid),
|
||||||
|
UidMapping(UidMapping),
|
||||||
|
GidMapping(GidMapping)
|
||||||
);
|
);
|
||||||
|
|
||||||
impl FromStr for FuseOverlayOption {
|
impl FromStr for FuseOverlayOption {
|
||||||
|
@ -374,7 +356,7 @@ impl FromStr for FuseOverlayOption {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let s = s.trim().trim_start_matches('-');
|
let s = s.trim().trim_start_matches('-');
|
||||||
|
|
||||||
let option = match s.split_once(|delim| delim == ' ' || delim == '=') {
|
let option = match s.split_once([' ', '=']) {
|
||||||
Some((option, value)) => (option, Some(value)),
|
Some((option, value)) => (option, Some(value)),
|
||||||
None => (s, None),
|
None => (s, None),
|
||||||
};
|
};
|
||||||
|
@ -384,17 +366,18 @@ impl FromStr for FuseOverlayOption {
|
||||||
("upperdir", Some(args)) => Ok(Self::UpperDir(PathBuf::from(args))),
|
("upperdir", Some(args)) => Ok(Self::UpperDir(PathBuf::from(args))),
|
||||||
("workdir", Some(args)) => Ok(Self::WorkDir(PathBuf::from(args))),
|
("workdir", Some(args)) => Ok(Self::WorkDir(PathBuf::from(args))),
|
||||||
("clonefd", None) => Ok(Self::CloneFd),
|
("clonefd", None) => Ok(Self::CloneFd),
|
||||||
("max_idle_threads", Some(args)) => Ok(MaxIdleThreads::from_str(args).into()?),
|
("max_idle_threads", Some(args)) => Ok(MaxIdleThreads::from_str(args)?.into()),
|
||||||
("max_threads", Some(args)) => Ok(MaxThreads::from_str(args).into()?),
|
("max_threads", Some(args)) => Ok(MaxThreads::from_str(args)?.into()),
|
||||||
("allow_other", None) => Ok(Self::AllowOther),
|
("allow_other", None) => Ok(Self::AllowOther),
|
||||||
("allow_root", None) => Ok(Self::AllowRoot),
|
("allow_root", None) => Ok(Self::AllowRoot),
|
||||||
("squash_to_root", None) => Ok(Self::SquashToRoot),
|
("squash_to_root", None) => Ok(Self::SquashToRoot),
|
||||||
("squash_to_uid", Some(uid)) => Ok(SquashToUid::from_str(uid).into()?),
|
("squash_to_uid", Some(uid)) => Ok(SquashToUid::from_str(uid)?.into()),
|
||||||
("squash_to_gid", Some(gid)) => Ok(SquashToGid::from_str(gid).into()?),
|
("squash_to_gid", Some(gid)) => Ok(SquashToGid::from_str(gid)?.into()),
|
||||||
("static_nlink", None) => Ok(Self::StaticNLink),
|
("static_nlink", None) => Ok(Self::StaticNLink),
|
||||||
("noacl", None) => Ok(Self::NoAcl),
|
("noacl", None) => Ok(Self::NoAcl),
|
||||||
("uidmapping", Some(ids)) => Ok(Self::UidMapping(IdMapping::from_str(ids)?)),
|
("uidmapping", Some(ids)) => Ok(UidMapping::from_str(ids)?.into()),
|
||||||
("gidmapping", Some(ids)) => Ok(Self::GidMapping(IdMapping::from_str(ids)?)),
|
("gidmapping", Some(ids)) => Ok(GidMapping::from_str(ids)?.into()),
|
||||||
|
(opt, _) => Err(ParseFuseOverlayOptionError::UnknownOption(opt.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,9 +387,9 @@ impl Display for FuseOverlayOption {
|
||||||
write!(f, "-o ")?;
|
write!(f, "-o ")?;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
FuseOverlayOption::LowerDir(lower) => write!(f, "{lower}"),
|
FuseOverlayOption::LowerDir(lower) => write!(f, "lowerdir={lower}"),
|
||||||
FuseOverlayOption::UpperDir(path) => write!(f, "{}", path.to_string_lossy()),
|
FuseOverlayOption::UpperDir(path) => write!(f, "upperdir={}", path.to_string_lossy()),
|
||||||
FuseOverlayOption::WorkDir(path) => write!(f, "{}", path.to_string_lossy()),
|
FuseOverlayOption::WorkDir(path) => write!(f, "workdir={}", path.to_string_lossy()),
|
||||||
FuseOverlayOption::CloneFd => write!(f, "clone_fd"),
|
FuseOverlayOption::CloneFd => write!(f, "clone_fd"),
|
||||||
FuseOverlayOption::MaxIdleThreads(n) => write!(f, "max_idle_threads={n}"),
|
FuseOverlayOption::MaxIdleThreads(n) => write!(f, "max_idle_threads={n}"),
|
||||||
FuseOverlayOption::MaxThreads(n) => write!(f, "max_threads={n}"),
|
FuseOverlayOption::MaxThreads(n) => write!(f, "max_threads={n}"),
|
||||||
|
@ -425,47 +408,59 @@ impl Display for FuseOverlayOption {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FuseOverlay {
|
pub struct FuseOverlay {
|
||||||
lower_dir: usize,
|
mount_target: PathBuf,
|
||||||
upper_dir: Option<usize>,
|
|
||||||
work_dir: Option<usize>,
|
|
||||||
options: MountOptions<FuseOverlayOption>,
|
options: MountOptions<FuseOverlayOption>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuseOverlay {
|
impl FuseOverlay {
|
||||||
pub fn new(lowerdir: impl Into<LowerDirs>) -> Self {
|
pub fn new(target: impl Into<PathBuf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
lower_dir: 0,
|
mount_target: target.into(),
|
||||||
upper_dir: None,
|
options: Default::default(),
|
||||||
work_dir: None,
|
|
||||||
options: MountOptions(vec![FuseOverlayOption::LowerDir(lowerdir.into())]),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push_option(&mut self, option: FuseOverlayOption) {
|
||||||
|
self.options.push(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_options(&mut self, options: impl IntoIterator<Item = FuseOverlayOption>) {
|
||||||
|
self.options.extend(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_option<'a, T, F>(&'a self, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: Fn(&'a FuseOverlayOption) -> Option<T>,
|
||||||
|
{
|
||||||
|
self.options.iter().filter_map(f).take(1).next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stack for FuseOverlay {
|
impl Stack for FuseOverlay {
|
||||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||||
match self.options.get(self.lower_dir) {
|
let result = self.find_option(|opt| match opt {
|
||||||
Some(FuseOverlayOption::LowerDir(lower)) => lower.as_iter().map(AsRef::as_ref),
|
FuseOverlayOption::LowerDir(dirs) => Some(dirs),
|
||||||
_ => panic!("invalid lowerdir option"),
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(dirs) => dirs.into_iter(),
|
||||||
|
None => DirIter::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
fn upper_dir(&self) -> Option<&std::path::Path> {
|
||||||
let upper = self.upper_dir.and_then(|i| self.options.get(i));
|
self.find_option(|opt| match opt {
|
||||||
|
FuseOverlayOption::UpperDir(dir) => Some(dir.as_path()),
|
||||||
match upper {
|
|
||||||
Some(FuseOverlayOption::UpperDir(upper)) => Some(upper),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||||
let work = self.work_dir.and_then(|i| self.options.get(i));
|
self.find_option(|opt| match opt {
|
||||||
|
FuseOverlayOption::WorkDir(dir) => Some(dir.as_path()),
|
||||||
match work {
|
|
||||||
Some(FuseOverlayOption::WorkDir(work)) => Some(work),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,29 +472,18 @@ impl Mountpoint for FuseOverlay {
|
||||||
|
|
||||||
impl FileSystem for FuseOverlay {
|
impl FileSystem for FuseOverlay {
|
||||||
fn mount(&mut self) -> std::io::Result<()> {
|
fn mount(&mut self) -> std::io::Result<()> {
|
||||||
let mut cmd = Command::new("fuse-overlayfs").arg(self.options.to_string());
|
Command::new("fuse-overlayfs")
|
||||||
|
.arg(self.options.to_string())
|
||||||
if self.fs.lower.len() > 0 {
|
.output()?;
|
||||||
cmd.arg(format!("-o lowerdir={}", self.fs.lower));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(upper) = &self.fs.upper {
|
|
||||||
cmd.arg(format!("-o upperdir={}", upper.to_string_lossy()));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(work) = &self.fs.work {
|
|
||||||
cmd.arg(format!("-o workdir={}", work.to_string_lossy()));
|
|
||||||
};
|
|
||||||
|
|
||||||
cmd.output()?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmount(&mut self) -> std::io::Result<()> {
|
fn unmount(&mut self) -> std::io::Result<()> {
|
||||||
if let Some(upper) = &self.fs.upper {
|
Command::new("fusermount")
|
||||||
Command::new("fusermount").arg("-u").arg(upper).output()?;
|
.arg("-u")
|
||||||
}
|
.arg(&self.mount_target)
|
||||||
|
.output()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -507,6 +491,165 @@ impl FileSystem for FuseOverlay {
|
||||||
|
|
||||||
impl Display for FuseOverlay {
|
impl Display for FuseOverlay {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "fuse-overlayfs")
|
write!(f, "fuse-overlayfs")?;
|
||||||
|
|
||||||
|
// INFO: this is to get rid of the extraneous space if
|
||||||
|
// self.options is empty
|
||||||
|
if self.options.len() > 0 {
|
||||||
|
write!(f, " {}", self.options)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, " {}", self.mount_target.to_string_lossy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::fs::id_mapping::UntypedIdRange;
|
||||||
|
use crate::fs::stackable::Stack;
|
||||||
|
use crate::fs::Mountpoint;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lower_dirs() {
|
||||||
|
let empty = LowerDirs::default();
|
||||||
|
assert_eq!(empty.to_string(), "");
|
||||||
|
|
||||||
|
let one = LowerDirs::from_iter(["/lower"]);
|
||||||
|
assert_eq!(one.to_string(), "/lower");
|
||||||
|
|
||||||
|
let two = LowerDirs::from_iter(["/first", "/second"]);
|
||||||
|
assert_eq!(two.to_string(), "/first,/second");
|
||||||
|
|
||||||
|
let from_str_none = LowerDirs::from_str("").unwrap();
|
||||||
|
assert_eq!(from_str_none, empty);
|
||||||
|
|
||||||
|
let from_str_one = LowerDirs::from_str("/lower").unwrap();
|
||||||
|
assert_eq!(from_str_one, one);
|
||||||
|
|
||||||
|
let from_str_two = LowerDirs::from_str("/first,/second").unwrap();
|
||||||
|
assert_eq!(from_str_two, two);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_threads() {
|
||||||
|
let default_threads = MaxThreads::default();
|
||||||
|
assert_eq!(*default_threads, 10);
|
||||||
|
assert_eq!(default_threads.to_string(), "10");
|
||||||
|
|
||||||
|
assert_eq!(MaxThreads::from_str("10").unwrap(), default_threads);
|
||||||
|
let err = MaxThreads::from_str("");
|
||||||
|
assert!(matches!(err, Err(ParseMaxThreadsError(_))));
|
||||||
|
assert_eq!(MaxThreads::from_str("69").unwrap(), MaxThreads(69));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_idle_threads() {
|
||||||
|
let default_threads = MaxIdleThreads::default();
|
||||||
|
assert_eq!(*default_threads, None);
|
||||||
|
assert_eq!(default_threads.to_string(), "-1");
|
||||||
|
assert_eq!(MaxIdleThreads::from_str("-1").unwrap(), default_threads);
|
||||||
|
assert_eq!(
|
||||||
|
MaxIdleThreads::from_str("69").unwrap(),
|
||||||
|
MaxIdleThreads(Some(69))
|
||||||
|
);
|
||||||
|
let err = MaxIdleThreads::from_str("");
|
||||||
|
assert!(matches!(err, Err(ParseMaxIdleThreadsError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn squash_to_id() {
|
||||||
|
let squash = SquashToId(1);
|
||||||
|
assert_eq!(squash.to_string(), "1");
|
||||||
|
assert_eq!(SquashToId::from_str("1").unwrap(), squash);
|
||||||
|
let err = SquashToId::from_str("");
|
||||||
|
assert!(matches!(err, Err(ParseSquashToIdError(_))));
|
||||||
|
|
||||||
|
let uid = SquashToUid(SquashToId(2));
|
||||||
|
let gid = SquashToGid(SquashToId(4));
|
||||||
|
|
||||||
|
assert_eq!(uid.to_string(), "2");
|
||||||
|
assert_eq!(gid.to_string(), "4");
|
||||||
|
|
||||||
|
assert_eq!(SquashToUid::from_str("2").unwrap(), uid);
|
||||||
|
assert_eq!(SquashToGid::from_str("4").unwrap(), gid);
|
||||||
|
|
||||||
|
let uid_err = SquashToUid::from_str("");
|
||||||
|
let gid_err = SquashToGid::from_str("");
|
||||||
|
assert!(matches!(uid_err, Err(ParseSquashToUidError(_))));
|
||||||
|
assert!(matches!(gid_err, Err(ParseSquashToGidError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_mapping() {
|
||||||
|
let id_range = IdRange::Untyped(UntypedIdRange(1, 1000, 1));
|
||||||
|
let uid = UidMapping(IdMapping::new([id_range.clone()]));
|
||||||
|
let gid = GidMapping(IdMapping::new([id_range.clone()]));
|
||||||
|
|
||||||
|
assert_eq!(uid.to_string(), "1:1000:1");
|
||||||
|
assert_eq!(gid.to_string(), "1:1000:1");
|
||||||
|
|
||||||
|
assert_eq!(UidMapping::from_str("1:1000:1").unwrap(), uid);
|
||||||
|
assert_eq!(GidMapping::from_str("1:1000:1").unwrap(), gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_output() {
|
||||||
|
let basic = FuseOverlay::new("/mnt");
|
||||||
|
assert_eq!(basic.to_string(), "fuse-overlayfs /mnt");
|
||||||
|
|
||||||
|
let mut with_options = FuseOverlay::new("~/.config");
|
||||||
|
with_options.push_options([
|
||||||
|
FuseOverlayOption::NoAcl,
|
||||||
|
LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
with_options.to_string(),
|
||||||
|
"fuse-overlayfs -o noacl -o lowerdir=~/.themes,~/.colors ~/.config"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mountpoint_trait_impl() {
|
||||||
|
let mut all = FuseOverlay::new("/mnt");
|
||||||
|
|
||||||
|
all.push_options([
|
||||||
|
LowerDirs::from_iter(["/doesnt", "/matter"]).into(),
|
||||||
|
FuseOverlayOption::UpperDir("/upper".into()),
|
||||||
|
FuseOverlayOption::WorkDir("/work".into()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let opts: Vec<String> = all.options().map(|x| x.to_string()).collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
opts,
|
||||||
|
[
|
||||||
|
"-o lowerdir=/doesnt,/matter",
|
||||||
|
"-o upperdir=/upper",
|
||||||
|
"-o workdir=/work"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stack_trait_impl() {
|
||||||
|
let mut all = FuseOverlay::new("/mnt");
|
||||||
|
|
||||||
|
assert_eq!(all.lower_dirs().next(), None);
|
||||||
|
assert_eq!(all.upper_dir(), None);
|
||||||
|
assert_eq!(all.work_dir(), None);
|
||||||
|
|
||||||
|
all.push_option(LowerDirs::from_iter(["/lower"]).into());
|
||||||
|
assert_eq!(all.lower_dirs().next(), Some(Path::new("/lower")));
|
||||||
|
|
||||||
|
all.push_option(FuseOverlayOption::UpperDir("/upper".into()));
|
||||||
|
assert_eq!(all.upper_dir(), Some(Path::new("/upper")));
|
||||||
|
|
||||||
|
all.push_option(FuseOverlayOption::WorkDir("/work".into()));
|
||||||
|
assert_eq!(all.work_dir(), Some(Path::new("/work")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::iter;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::fs::{AsIter, Mountpoint};
|
use crate::fs::{AsIter, Mountpoint};
|
||||||
|
|
||||||
use super::{Stack, Stackable};
|
use super::Stack;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum RedirectDir {
|
pub enum RedirectDir {
|
||||||
|
@ -96,21 +98,22 @@ impl Display for OverlayOptions {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Overlay {
|
pub struct Overlay {
|
||||||
fs: Stackable,
|
mount_target: PathBuf,
|
||||||
options: Vec<OverlayOptions>,
|
options: Vec<OverlayOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stack for Overlay {
|
impl Stack for Overlay {
|
||||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||||
self.fs.lower_dirs()
|
todo!();
|
||||||
|
iter::empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
fn upper_dir(&self) -> Option<&std::path::Path> {
|
||||||
self.fs.upper_dir()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||||
self.fs.work_dir()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue