diff --git a/src/fs/mount.rs b/src/fs/mount.rs index b4f2a46..c9ead8b 100644 --- a/src/fs/mount.rs +++ b/src/fs/mount.rs @@ -7,13 +7,15 @@ use std::{ ops::{Deref, DerefMut}, path::PathBuf, process::Command, + slice::Iter, str::FromStr, + vec::IntoIter, }; use crate::utils::DisplayString; use super::{ - id_mapping::{ParseUntypedIdRangeError, UntypedIdRange}, + id_mapping::{Delimiter, ParseUntypedIdRangeError, UntypedIdRange}, permission::Permissions, FileSystem, }; @@ -374,18 +376,69 @@ impl From for GidRange { } } -#[derive(Debug)] -pub struct Options(Vec); +// TODO: finish implementation to solve options delimiter problem +#[derive(Clone, Debug)] +pub struct DelimitedString { + inner: Vec, + delimiter: char, +} -impl Default for Options { - fn default() -> Self { - Self(Vec::new()) +impl DelimitedString { + pub fn new(inner: I, delimiter: char) -> Self + where + T: Into, + I: IntoIterator, + { + Self { + inner: inner.into_iter().map(Into::into).collect(), + delimiter, + } + } + + pub fn from_str(s: &str, delimiter: char) -> Self { + Self::from_iter(s.split(delimiter), delimiter) + } + + pub fn from_iter(iter: I, delimiter: char) -> Self + where + T: Into, + I: IntoIterator, + { + Self::new(iter, delimiter) } } -impl AsRef> for Options { - fn as_ref(&self) -> &Vec { - &self.0 +impl AsRef for DelimitedString { + fn as_ref(&self) -> &OsStr { + &self.inner.join(self.delimiter) + } +} + +impl Display for DelimitedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut iter = self.inner.iter(); + + if let Some(head) = iter.next() { + write!(f, "{}", head.display())?; + + for item in iter { + write!(f, "{}", item.display())?; + } + } + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Options { + options: Vec, + delimiter: char, +} + +impl Options { + pub fn new(options: Vec, delimiter: char) -> Self { + Self { options, delimiter } } } @@ -393,25 +446,43 @@ impl Deref for Options { type Target = Vec; fn deref(&self) -> &Self::Target { - &self.0 + &self.options } } impl DerefMut for Options { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + &mut self.options + } +} + +impl Default for Options { + fn default() -> Self { + Self { + options: Default::default(), + delimiter: ' ', + } + } +} + +impl AsRef> for Options { + fn as_ref(&self) -> &Vec { + &self.options } } impl Clone for Options { fn clone(&self) -> Self { - Self(self.0.clone()) + Self { + options: self.options.clone(), + delimiter: self.delimiter, + } } } impl PartialEq for Options { fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) + self.options.eq(&other.options) && self.delimiter.eq(&other.delimiter) } } @@ -429,12 +500,12 @@ impl FromStr for Options { impl Display for Options { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut iter = self.0.iter(); + let mut iter = self.options.iter(); if let Some(head) = iter.next() { write!(f, "{head}")?; for item in iter { - write!(f, ",{item}")?; + write!(f, "{}{item}", self.delimiter)?; } } @@ -444,19 +515,41 @@ impl Display for Options { impl FromIterator for Options { fn from_iter>(iter: I) -> Self { - Self(iter.into_iter().collect()) + Self { + options: iter.into_iter().collect(), + ..Default::default() + } + } +} + +impl IntoIterator for Options { + type Item = T; + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.options.into_iter() + } +} +impl<'a, T> IntoIterator for &'a Options { + type Item = &'a T; + + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.options.as_slice().into_iter() } } impl From> for Options { fn from(value: Vec) -> Self { - Self(value) + Self::from_iter(value) } } impl From for Options { fn from(value: T) -> Self { - Self(vec![value]) + Self::from_iter([value]) } } @@ -868,9 +961,9 @@ impl Mount { impl FileSystem for Mount { fn mount(&mut self) -> std::io::Result<()> { mount( - self.device_id, + self.device_id.clone(), &self.mountpoint, - self.options.map(Into::into), + self.options.clone(), )?; Ok(()) } @@ -881,13 +974,13 @@ impl FileSystem for Mount { } } -pub fn mount, I: IntoIterator>( +pub fn mount, I: IntoIterator>( source: impl Into, target: impl Into, options: I, ) -> std::io::Result { Command::new("mount") - .args(options.into_iter().map(|s| s.as_ref().to_string())) + .args(options.into_iter().map(|v| v.into().to_string())) .arg(source.into().to_string()) .arg(target.into()) .output() @@ -1027,10 +1120,13 @@ mod tests { assert_eq!( opts, - Options(vec![ - KeyValuePair::new("key", Some("value")), - KeyValuePair::new("keyword", None::<&str>) - ]) + Options::new( + vec![ + KeyValuePair::new("key", Some("value")), + KeyValuePair::new("keyword", None::<&str>) + ], + ',' + ) ); assert_eq!(opts.to_string(), "key=value,keyword"); @@ -1153,7 +1249,7 @@ mod tests { #[test] fn mount_options() { Opts::from((["-L", "--label"].as_slice(), "LABEL=label")) - .assert(MountOption::Label(DeviceId::Label("label".to_string()))); + .assert(MountOption::Label(DeviceId::Label("label".into()))); Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs")).assert(MountOption::FsType( UncheckedOptions::from_iter(vec!["nomsdos", "smbs"]), diff --git a/src/fs/stackable/fuse_overlay.rs b/src/fs/stackable/fuse_overlay.rs index a04ffcd..d2bce01 100644 --- a/src/fs/stackable/fuse_overlay.rs +++ b/src/fs/stackable/fuse_overlay.rs @@ -278,7 +278,11 @@ impl_deref!(FuseOverlay(StackFs)); impl_deref_mut!(FuseOverlay(StackFs)); impl FuseOverlay { - pub fn new( + pub fn new(target: impl Into) -> Self { + Self(StackFs::new(target)) + } + + pub fn readonly( target: impl Into, lowerdir: impl IntoIterator>, ) -> Self { @@ -286,6 +290,26 @@ impl FuseOverlay { fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir))); Self(fs) } + + pub fn writable( + target: impl Into, + lowerdir: impl IntoIterator>, + upperdir: impl Into, + ) -> Self { + let mut fs = StackFs::new(target); + + fs.extend_options([ + FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)), + FuseOverlayOption::UpperDir(upperdir.into()), + ]); + + Self(fs) + } + + pub fn set_workdir(&mut self, workdir: impl Into) -> &mut Self { + self.push_option(FuseOverlayOption::WorkDir(workdir.into())); + self + } } impl Stack for FuseOverlay { @@ -362,7 +386,7 @@ mod tests { assert_eq!(one.to_string(), "/lower"); let two = LowerDirs::from_iter(["/first", "/second"]); - assert_eq!(two.to_string(), "/first,/second"); + assert_eq!(two.to_string(), "/first:/second"); let from_str_none = LowerDirs::from_str("").unwrap(); assert_eq!(from_str_none, empty); @@ -370,7 +394,7 @@ mod tests { 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(); + let from_str_two = LowerDirs::from_str("/first:/second").unwrap(); assert_eq!(from_str_two, two); } @@ -441,36 +465,29 @@ mod tests { let basic = FuseOverlay::new("/mnt"); assert_eq!(basic.to_string(), "fuse-overlayfs /mnt"); - let mut with_options = FuseOverlay::new("~/.config"); - with_options.extend_options([ - FuseOverlayOption::NoAcl, - LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(), - ]); + let mut with_options = FuseOverlay::readonly("~/.config", ["~/.themes", "~/.colors"]); + with_options.push_option(FuseOverlayOption::NoAcl); assert_eq!( with_options.to_string(), - "fuse-overlayfs -o noacl -o lowerdir=~/.themes,~/.colors ~/.config" + "fuse-overlayfs -olowerdir=~/.themes:~/.colors -onoacl ~/.config" ); } #[test] fn mountpoint_trait_impl() { - let mut all = FuseOverlay::new("/mnt"); + let mut all = FuseOverlay::writable("/mnt", ["/doesnt", "/matter"], "/upper"); - all.extend_options([ - LowerDirs::from_iter(["/doesnt", "/matter"]).into(), - FuseOverlayOption::UpperDir("/upper".into()), - FuseOverlayOption::WorkDir("/work".into()), - ]); + all.set_workdir("/work"); let opts: Vec = all.options().map(|x| x.to_string()).collect(); assert_eq!( opts, [ - "-o lowerdir=/doesnt,/matter", - "-o upperdir=/upper", - "-o workdir=/work" + "-olowerdir=/doesnt:/matter", + "-oupperdir=/upper", + "-oworkdir=/work" ] ); } diff --git a/src/fs/stackable/mod.rs b/src/fs/stackable/mod.rs index 8e435d6..345bd70 100644 --- a/src/fs/stackable/mod.rs +++ b/src/fs/stackable/mod.rs @@ -67,7 +67,7 @@ impl FromStr for LowerDirs { fn from_str(s: &str) -> Result { Ok(Self( - s.split(',') + s.split(':') .filter(|s| !s.is_empty()) .map(Into::into) .collect(), diff --git a/src/fs/stackable/overlay.rs b/src/fs/stackable/overlay.rs index b0ab85b..9527b57 100644 --- a/src/fs/stackable/overlay.rs +++ b/src/fs/stackable/overlay.rs @@ -1,4 +1,3 @@ -use std::ffi::OsString; use std::fmt::Display; use std::path::PathBuf; use std::str::FromStr; @@ -277,7 +276,7 @@ impl Stack for Overlay { impl FileSystem for Overlay { fn mount(&mut self) -> std::io::Result<()> { let opts = self.options.iter().map(ToString::to_string); - let options = MountOption::Options(UncheckedOptions::from_iter(opts)); + let options = [MountOption::Options(UncheckedOptions::from_iter(opts))]; mount("overlay", &self.mountpoint, options)?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 0423a7a..9a39bc8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use vahanafs::fs::FileSystem; use vahanafs::prelude::*; fn main() { - let mut fs = FuseOverlay::new("/home/rowan/vfs/mnt", ["/home/rowan/vfs/lower1"]); + let mut fs = FuseOverlay::readonly("/home/rowan/vfs/mnt", ["/home/rowan/vfs/lower1"]); println!("{fs}"); fs.mount().expect("could not mount overlayfs"); }