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> {
|
||||
match c {
|
||||
#(#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> {
|
||||
match s.chars().next() {
|
||||
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 std::ops::Deref;
|
||||
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
||||
|
||||
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)]
|
||||
pub struct ParseIdRangeError(String);
|
||||
|
||||
|
@ -278,9 +285,10 @@ impl FromStr for IdMapping {
|
|||
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(),
|
||||
)),
|
||||
None => {
|
||||
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(_) | Err(_) => match Modes::<GroupId>::from_str(s) {
|
||||
Ok(perms) if perms.0.bits() > 0 => Ok(Self::Group(perms)),
|
||||
Ok(_) | Err(_) => {
|
||||
Err(FromStrError::new(format!("{} is not a valid argument", s)).into())
|
||||
}
|
||||
Ok(_) | Err(_) => Err(FromStrError(s.to_string()).into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,12 @@ use std::{
|
|||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
slice::Iter,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::fs::{
|
||||
id_mapping::{IdMapping, ParseIdRangeError},
|
||||
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
|
||||
mount::MountOptions,
|
||||
AsIter, FileSystem, Mountpoint,
|
||||
};
|
||||
|
@ -18,8 +19,8 @@ use crate::macros::*;
|
|||
|
||||
use super::Stack;
|
||||
|
||||
macro_rules! impl_wrapper {
|
||||
($name:ident($inner:ty), $err:ty, $from_str:block) => {
|
||||
macro_rules! impl_deref {
|
||||
($name:ident($inner:ty)) => {
|
||||
impl Deref for $name {
|
||||
type Target = $inner;
|
||||
|
||||
|
@ -27,16 +28,23 @@ macro_rules! impl_wrapper {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_fromstr {
|
||||
($name:ident($inner:ty), $err:ty) => {
|
||||
impl FromStr for $name {
|
||||
type Err = $err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
$from_str
|
||||
//Ok(Self($target::from_str(s)?))
|
||||
Ok(Self(<$inner>::from_str(s)?))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_display {
|
||||
($name:ident) => {
|
||||
impl Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
|
@ -45,12 +53,13 @@ macro_rules! impl_wrapper {
|
|||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Test(usize);
|
||||
|
||||
impl_wrapper!(Test(usize), ParseIntError, {
|
||||
Ok(Test(usize::from_str(s)?))
|
||||
});
|
||||
macro_rules! impl_wrapper_struct {
|
||||
($name:ident($inner:ty), $err:ty) => {
|
||||
impl_deref!($name($inner));
|
||||
impl_fromstr!($name($inner), $err);
|
||||
impl_display!($name);
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
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!(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)]
|
||||
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 {
|
||||
type Target = Vec<PathBuf>;
|
||||
|
||||
|
@ -98,14 +137,16 @@ impl Deref for LowerDirs {
|
|||
|
||||
impl Display for LowerDirs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
self.0
|
||||
.iter()
|
||||
.map(|v| v.to_string_lossy())
|
||||
.fold(String::new(), |a, b| a + &b)
|
||||
)
|
||||
let mut iter = self.0.iter();
|
||||
if let Some(head) = iter.next() {
|
||||
write!(f, "{}", head.to_string_lossy())?;
|
||||
|
||||
for item in iter {
|
||||
write!(f, ",{}", item.to_string_lossy())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +154,12 @@ impl FromStr for LowerDirs {
|
|||
type Err = Infallible;
|
||||
|
||||
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>);
|
||||
|
||||
impl Default for MaxIdleThreads {
|
||||
fn default() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MaxIdleThreads {
|
||||
type Err = ParseMaxIdleThreadsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"-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)]
|
||||
pub struct MaxThreads(usize);
|
||||
|
||||
|
@ -187,127 +229,59 @@ impl FromStr for MaxThreads {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for MaxThreads {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
impl_deref!(MaxThreads(usize));
|
||||
impl_display!(MaxThreads);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SquashToId(usize);
|
||||
|
||||
impl_deref!(SquashToId(usize));
|
||||
impl_display!(SquashToId);
|
||||
|
||||
impl FromStr for SquashToId {
|
||||
type Err = ParseSquashToIdError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(usize::from_str_radix(s, 10).map(Self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SquashToId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
Ok(Self(s.parse::<usize>()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SquashToUid(SquashToId);
|
||||
|
||||
impl Deref for SquashToUid {
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl_wrapper_struct!(SquashToUid(SquashToId), ParseSquashToUidError);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SquashToGid(SquashToId);
|
||||
|
||||
impl Deref for SquashToGid {
|
||||
type Target = SquashToId;
|
||||
impl_wrapper_struct!(SquashToGid(SquashToId), ParseSquashToGidError);
|
||||
|
||||
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)]
|
||||
pub struct GidMapping(IdMapping);
|
||||
pub struct IdMapping(OriginalIdMapping);
|
||||
|
||||
impl Deref for GidMapping {
|
||||
type Target = IdMapping;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
impl IdMapping {
|
||||
pub fn new(ranges: impl IntoIterator<Item = impl Into<IdRange>>) -> Self {
|
||||
Self(OriginalIdMapping::new(ranges.into_iter(), ','))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GidMapping {
|
||||
type Err = ParseGidMappingError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(IdMapping::from_str(s)?))
|
||||
impl From<OriginalIdMapping> for IdMapping {
|
||||
fn from(value: OriginalIdMapping) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GidMapping {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
impl_wrapper_struct!(IdMapping(OriginalIdMapping), ParseIdRangeError);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct UidMapping(IdMapping);
|
||||
|
||||
impl_wrapper_struct!(UidMapping(IdMapping), ParseUidMappingError);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct GidMapping(IdMapping);
|
||||
|
||||
impl Deref for GidMapping {
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl_wrapper_struct!(GidMapping(IdMapping), ParseGidMappingError);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ParseFuseOverlayOptionError {
|
||||
|
@ -317,6 +291,7 @@ pub enum ParseFuseOverlayOptionError {
|
|||
SquashToGid(ParseSquashToGidError),
|
||||
UidMapping(ParseUidMappingError),
|
||||
GidMapping(ParseGidMappingError),
|
||||
UnknownOption(String),
|
||||
}
|
||||
|
||||
impl Display for ParseFuseOverlayOptionError {
|
||||
|
@ -328,6 +303,7 @@ impl Display for ParseFuseOverlayOptionError {
|
|||
Self::SquashToGid(e) => write!(f, "{e}"),
|
||||
Self::UidMapping(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!(
|
||||
ParseFuseOverlayOptionError,
|
||||
MaxIdleThreads(ParseMaxIdleThreadsError),
|
||||
MaxThreads(ParseMaxThreadsError)
|
||||
MaxThreads(ParseMaxThreadsError),
|
||||
SquashToUid(ParseSquashToUidError),
|
||||
SquashToGid(ParseSquashToGidError),
|
||||
UidMapping(ParseUidMappingError),
|
||||
GidMapping(ParseGidMappingError)
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -355,8 +335,8 @@ pub enum FuseOverlayOption {
|
|||
SquashToGid(SquashToGid),
|
||||
StaticNLink,
|
||||
NoAcl,
|
||||
UidMapping(IdMapping),
|
||||
GidMapping(IdMapping),
|
||||
UidMapping(UidMapping),
|
||||
GidMapping(GidMapping),
|
||||
}
|
||||
|
||||
impl_from_variants!(
|
||||
|
@ -365,7 +345,9 @@ impl_from_variants!(
|
|||
MaxIdleThreads(MaxIdleThreads),
|
||||
MaxThreads(MaxThreads),
|
||||
SquashToUid(SquashToUid),
|
||||
SquashToGid(SquashToGid)
|
||||
SquashToGid(SquashToGid),
|
||||
UidMapping(UidMapping),
|
||||
GidMapping(GidMapping)
|
||||
);
|
||||
|
||||
impl FromStr for FuseOverlayOption {
|
||||
|
@ -374,7 +356,7 @@ impl FromStr for FuseOverlayOption {
|
|||
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 == '=') {
|
||||
let option = match s.split_once([' ', '=']) {
|
||||
Some((option, value)) => (option, Some(value)),
|
||||
None => (s, None),
|
||||
};
|
||||
|
@ -384,17 +366,18 @@ impl FromStr for FuseOverlayOption {
|
|||
("upperdir", Some(args)) => Ok(Self::UpperDir(PathBuf::from(args))),
|
||||
("workdir", Some(args)) => Ok(Self::WorkDir(PathBuf::from(args))),
|
||||
("clonefd", None) => Ok(Self::CloneFd),
|
||||
("max_idle_threads", Some(args)) => Ok(MaxIdleThreads::from_str(args).into()?),
|
||||
("max_threads", Some(args)) => Ok(MaxThreads::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()),
|
||||
("allow_other", None) => Ok(Self::AllowOther),
|
||||
("allow_root", None) => Ok(Self::AllowRoot),
|
||||
("squash_to_root", None) => Ok(Self::SquashToRoot),
|
||||
("squash_to_uid", Some(uid)) => Ok(SquashToUid::from_str(uid).into()?),
|
||||
("squash_to_gid", Some(gid)) => Ok(SquashToGid::from_str(gid).into()?),
|
||||
("squash_to_uid", Some(uid)) => Ok(SquashToUid::from_str(uid)?.into()),
|
||||
("squash_to_gid", Some(gid)) => Ok(SquashToGid::from_str(gid)?.into()),
|
||||
("static_nlink", None) => Ok(Self::StaticNLink),
|
||||
("noacl", None) => Ok(Self::NoAcl),
|
||||
("uidmapping", Some(ids)) => Ok(Self::UidMapping(IdMapping::from_str(ids)?)),
|
||||
("gidmapping", Some(ids)) => Ok(Self::GidMapping(IdMapping::from_str(ids)?)),
|
||||
("uidmapping", Some(ids)) => Ok(UidMapping::from_str(ids)?.into()),
|
||||
("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 ")?;
|
||||
|
||||
match self {
|
||||
FuseOverlayOption::LowerDir(lower) => write!(f, "{lower}"),
|
||||
FuseOverlayOption::UpperDir(path) => write!(f, "{}", path.to_string_lossy()),
|
||||
FuseOverlayOption::WorkDir(path) => write!(f, "{}", path.to_string_lossy()),
|
||||
FuseOverlayOption::LowerDir(lower) => write!(f, "lowerdir={lower}"),
|
||||
FuseOverlayOption::UpperDir(path) => write!(f, "upperdir={}", path.to_string_lossy()),
|
||||
FuseOverlayOption::WorkDir(path) => write!(f, "workdir={}", path.to_string_lossy()),
|
||||
FuseOverlayOption::CloneFd => write!(f, "clone_fd"),
|
||||
FuseOverlayOption::MaxIdleThreads(n) => write!(f, "max_idle_threads={n}"),
|
||||
FuseOverlayOption::MaxThreads(n) => write!(f, "max_threads={n}"),
|
||||
|
@ -425,47 +408,59 @@ impl Display for FuseOverlayOption {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct FuseOverlay {
|
||||
lower_dir: usize,
|
||||
upper_dir: Option<usize>,
|
||||
work_dir: Option<usize>,
|
||||
mount_target: PathBuf,
|
||||
options: MountOptions<FuseOverlayOption>,
|
||||
}
|
||||
|
||||
impl FuseOverlay {
|
||||
pub fn new(lowerdir: impl Into<LowerDirs>) -> Self {
|
||||
pub fn new(target: impl Into<PathBuf>) -> Self {
|
||||
Self {
|
||||
lower_dir: 0,
|
||||
upper_dir: None,
|
||||
work_dir: None,
|
||||
options: MountOptions(vec![FuseOverlayOption::LowerDir(lowerdir.into())]),
|
||||
mount_target: target.into(),
|
||||
options: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||
match self.options.get(self.lower_dir) {
|
||||
Some(FuseOverlayOption::LowerDir(lower)) => lower.as_iter().map(AsRef::as_ref),
|
||||
_ => panic!("invalid lowerdir option"),
|
||||
let result = self.find_option(|opt| match opt {
|
||||
FuseOverlayOption::LowerDir(dirs) => Some(dirs),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
match result {
|
||||
Some(dirs) => dirs.into_iter(),
|
||||
None => DirIter::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
||||
let upper = self.upper_dir.and_then(|i| self.options.get(i));
|
||||
|
||||
match upper {
|
||||
Some(FuseOverlayOption::UpperDir(upper)) => Some(upper),
|
||||
self.find_option(|opt| match opt {
|
||||
FuseOverlayOption::UpperDir(dir) => Some(dir.as_path()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||
let work = self.work_dir.and_then(|i| self.options.get(i));
|
||||
|
||||
match work {
|
||||
Some(FuseOverlayOption::WorkDir(work)) => Some(work),
|
||||
self.find_option(|opt| match opt {
|
||||
FuseOverlayOption::WorkDir(dir) => Some(dir.as_path()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,29 +472,18 @@ impl Mountpoint for FuseOverlay {
|
|||
|
||||
impl FileSystem for FuseOverlay {
|
||||
fn mount(&mut self) -> std::io::Result<()> {
|
||||
let mut cmd = Command::new("fuse-overlayfs").arg(self.options.to_string());
|
||||
|
||||
if self.fs.lower.len() > 0 {
|
||||
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()?;
|
||||
Command::new("fuse-overlayfs")
|
||||
.arg(self.options.to_string())
|
||||
.output()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmount(&mut self) -> std::io::Result<()> {
|
||||
if let Some(upper) = &self.fs.upper {
|
||||
Command::new("fusermount").arg("-u").arg(upper).output()?;
|
||||
}
|
||||
Command::new("fusermount")
|
||||
.arg("-u")
|
||||
.arg(&self.mount_target)
|
||||
.output()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -507,6 +491,165 @@ impl FileSystem for FuseOverlay {
|
|||
|
||||
impl Display for FuseOverlay {
|
||||
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::iter;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::fs::{AsIter, Mountpoint};
|
||||
|
||||
use super::{Stack, Stackable};
|
||||
use super::Stack;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum RedirectDir {
|
||||
|
@ -96,21 +98,22 @@ impl Display for OverlayOptions {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Overlay {
|
||||
fs: Stackable,
|
||||
mount_target: PathBuf,
|
||||
options: Vec<OverlayOptions>,
|
||||
}
|
||||
|
||||
impl Stack for Overlay {
|
||||
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> {
|
||||
self.fs.upper_dir()
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||
self.fs.work_dir()
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue