diff --git a/crates/char_enum_derive/src/lib.rs b/crates/char_enum_derive/src/lib.rs index 499777c..027a39b 100644 --- a/crates/char_enum_derive/src/lib.rs +++ b/crates/char_enum_derive/src/lib.rs @@ -58,7 +58,7 @@ pub fn as_char_derive(input: TokenStream) -> TokenStream { fn from_char(c: char) -> Result { 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 { 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())), } } } diff --git a/src/fs/id_mapping.rs b/src/fs/id_mapping.rs index aa02dd8..d3c6ee4 100644 --- a/src/fs/id_mapping.rs +++ b/src/fs/id_mapping.rs @@ -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 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 { 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)?) + } } } } diff --git a/src/fs/permission.rs b/src/fs/permission.rs index 87d7532..9658628 100644 --- a/src/fs/permission.rs +++ b/src/fs/permission.rs @@ -219,9 +219,7 @@ impl FromStr for SymbolicArgs { Ok(perms) if perms.0.bits() > 0 => Ok(Self::Mode(perms)), Ok(_) | Err(_) => match Modes::::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()), }, } } diff --git a/src/fs/stackable/fuse_overlay.rs b/src/fs/stackable/fuse_overlay.rs index 8f633b6..4bfa876 100644 --- a/src/fs/stackable/fuse_overlay.rs +++ b/src/fs/stackable/fuse_overlay.rs @@ -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 { - $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.inner.next().map(PathBuf::as_path) + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct LowerDirs(Vec); +impl> FromIterator for LowerDirs { + fn from_iter>(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; @@ -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 { - 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> for LowerDirs { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct MaxIdleThreads(Option); -impl Default for MaxIdleThreads { - fn default() -> Self { - Self(None) - } -} - impl FromStr for MaxIdleThreads { type Err = ParseMaxIdleThreadsError; fn from_str(s: &str) -> Result { match s { "-1" => Ok(Self(None)), - s => Ok(usize::from_str_radix(s, 10).map(Some).map(Self)?), + s => Ok(s.parse::().map(Some).map(Self)?), } } } @@ -170,6 +210,8 @@ impl Display for MaxIdleThreads { } } +impl_deref!(MaxIdleThreads(Option)); + #[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 { - 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::()?)) } } #[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 { - 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 { - 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>) -> Self { + Self(OriginalIdMapping::new(ranges.into_iter(), ',')) } } -impl FromStr for GidMapping { - type Err = ParseGidMappingError; - - fn from_str(s: &str) -> Result { - Ok(Self(IdMapping::from_str(s)?)) +impl From 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 { - 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 { 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, - work_dir: Option, + mount_target: PathBuf, options: MountOptions, } impl FuseOverlay { - pub fn new(lowerdir: impl Into) -> Self { + pub fn new(target: impl Into) -> 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) { + self.options.extend(options) + } + + fn find_option<'a, T, F>(&'a self, f: F) -> Option + where + F: Fn(&'a FuseOverlayOption) -> Option, + { + self.options.iter().filter_map(f).take(1).next() + } } impl Stack for FuseOverlay { fn lower_dirs(&self) -> impl Iterator { - 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 = 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"))); } } diff --git a/src/fs/stackable/overlay.rs b/src/fs/stackable/overlay.rs index b6a7b36..e980091 100644 --- a/src/fs/stackable/overlay.rs +++ b/src/fs/stackable/overlay.rs @@ -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, } impl Stack for Overlay { fn lower_dirs(&self) -> impl Iterator { - 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!() } }