wip: options delimter

This commit is contained in:
Rowan 2025-03-05 04:30:44 -06:00
parent 947ff86015
commit b459d08a74
5 changed files with 161 additions and 49 deletions

View file

@ -7,13 +7,15 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::PathBuf, path::PathBuf,
process::Command, process::Command,
slice::Iter,
str::FromStr, str::FromStr,
vec::IntoIter,
}; };
use crate::utils::DisplayString; use crate::utils::DisplayString;
use super::{ use super::{
id_mapping::{ParseUntypedIdRangeError, UntypedIdRange}, id_mapping::{Delimiter, ParseUntypedIdRangeError, UntypedIdRange},
permission::Permissions, permission::Permissions,
FileSystem, FileSystem,
}; };
@ -374,18 +376,69 @@ impl From<UntypedIdRange> for GidRange {
} }
} }
#[derive(Debug)] // TODO: finish implementation to solve options delimiter problem
pub struct Options<T>(Vec<T>); #[derive(Clone, Debug)]
pub struct DelimitedString {
inner: Vec<OsString>,
delimiter: char,
}
impl<T> Default for Options<T> { impl DelimitedString {
fn default() -> Self { pub fn new<T, I>(inner: I, delimiter: char) -> Self
Self(Vec::new()) where
T: Into<OsString>,
I: IntoIterator<Item = T>,
{
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<I, T>(iter: I, delimiter: char) -> Self
where
T: Into<OsString>,
I: IntoIterator<Item = T>,
{
Self::new(iter, delimiter)
} }
} }
impl<T> AsRef<Vec<T>> for Options<T> { impl AsRef<OsStr> for DelimitedString {
fn as_ref(&self) -> &Vec<T> { fn as_ref(&self) -> &OsStr {
&self.0 &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<T> {
options: Vec<T>,
delimiter: char,
}
impl<T> Options<T> {
pub fn new(options: Vec<T>, delimiter: char) -> Self {
Self { options, delimiter }
} }
} }
@ -393,25 +446,43 @@ impl<T> Deref for Options<T> {
type Target = Vec<T>; type Target = Vec<T>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.options
} }
} }
impl<T> DerefMut for Options<T> { impl<T> DerefMut for Options<T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.options
}
}
impl<T> Default for Options<T> {
fn default() -> Self {
Self {
options: Default::default(),
delimiter: ' ',
}
}
}
impl<T> AsRef<Vec<T>> for Options<T> {
fn as_ref(&self) -> &Vec<T> {
&self.options
} }
} }
impl<T: Clone> Clone for Options<T> { impl<T: Clone> Clone for Options<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.clone()) Self {
options: self.options.clone(),
delimiter: self.delimiter,
}
} }
} }
impl<T: PartialEq> PartialEq for Options<T> { impl<T: PartialEq> PartialEq for Options<T> {
fn eq(&self, other: &Self) -> bool { 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<T: FromStr> FromStr for Options<T> {
impl<T: Display> Display for Options<T> { impl<T: Display> Display for Options<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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() { if let Some(head) = iter.next() {
write!(f, "{head}")?; write!(f, "{head}")?;
for item in iter { for item in iter {
write!(f, ",{item}")?; write!(f, "{}{item}", self.delimiter)?;
} }
} }
@ -444,19 +515,41 @@ impl<T: Display> Display for Options<T> {
impl<T> FromIterator<T> for Options<T> { impl<T> FromIterator<T> for Options<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self(iter.into_iter().collect()) Self {
options: iter.into_iter().collect(),
..Default::default()
}
}
}
impl<T> IntoIterator for Options<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.options.into_iter()
}
}
impl<'a, T> IntoIterator for &'a Options<T> {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.options.as_slice().into_iter()
} }
} }
impl<T> From<Vec<T>> for Options<T> { impl<T> From<Vec<T>> for Options<T> {
fn from(value: Vec<T>) -> Self { fn from(value: Vec<T>) -> Self {
Self(value) Self::from_iter(value)
} }
} }
impl<T> From<T> for Options<T> { impl<T> From<T> for Options<T> {
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self(vec![value]) Self::from_iter([value])
} }
} }
@ -868,9 +961,9 @@ impl Mount {
impl FileSystem for Mount { impl FileSystem for Mount {
fn mount(&mut self) -> std::io::Result<()> { fn mount(&mut self) -> std::io::Result<()> {
mount( mount(
self.device_id, self.device_id.clone(),
&self.mountpoint, &self.mountpoint,
self.options.map(Into::into), self.options.clone(),
)?; )?;
Ok(()) Ok(())
} }
@ -881,13 +974,13 @@ impl FileSystem for Mount {
} }
} }
pub fn mount<T: AsRef<MountOption>, I: IntoIterator<Item = T>>( pub fn mount<T: Into<MountOption>, I: IntoIterator<Item = T>>(
source: impl Into<DeviceId>, source: impl Into<DeviceId>,
target: impl Into<PathBuf>, target: impl Into<PathBuf>,
options: I, options: I,
) -> std::io::Result<std::process::Output> { ) -> std::io::Result<std::process::Output> {
Command::new("mount") 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(source.into().to_string())
.arg(target.into()) .arg(target.into())
.output() .output()
@ -1027,10 +1120,13 @@ mod tests {
assert_eq!( assert_eq!(
opts, opts,
Options(vec![ Options::new(
vec![
KeyValuePair::new("key", Some("value")), KeyValuePair::new("key", Some("value")),
KeyValuePair::new("keyword", None::<&str>) KeyValuePair::new("keyword", None::<&str>)
]) ],
','
)
); );
assert_eq!(opts.to_string(), "key=value,keyword"); assert_eq!(opts.to_string(), "key=value,keyword");
@ -1153,7 +1249,7 @@ mod tests {
#[test] #[test]
fn mount_options() { fn mount_options() {
Opts::from((["-L", "--label"].as_slice(), "LABEL=label")) 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( Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs")).assert(MountOption::FsType(
UncheckedOptions::from_iter(vec!["nomsdos", "smbs"]), UncheckedOptions::from_iter(vec!["nomsdos", "smbs"]),

View file

@ -278,7 +278,11 @@ impl_deref!(FuseOverlay(StackFs<FuseOverlayOption>));
impl_deref_mut!(FuseOverlay(StackFs<FuseOverlayOption>)); impl_deref_mut!(FuseOverlay(StackFs<FuseOverlayOption>));
impl FuseOverlay { impl FuseOverlay {
pub fn new( pub fn new(target: impl Into<PathBuf>) -> Self {
Self(StackFs::new(target))
}
pub fn readonly(
target: impl Into<PathBuf>, target: impl Into<PathBuf>,
lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>, lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
) -> Self { ) -> Self {
@ -286,6 +290,26 @@ impl FuseOverlay {
fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir))); fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)));
Self(fs) Self(fs)
} }
pub fn writable(
target: impl Into<PathBuf>,
lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
upperdir: impl Into<PathBuf>,
) -> 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<PathBuf>) -> &mut Self {
self.push_option(FuseOverlayOption::WorkDir(workdir.into()));
self
}
} }
impl Stack for FuseOverlay { impl Stack for FuseOverlay {
@ -362,7 +386,7 @@ mod tests {
assert_eq!(one.to_string(), "/lower"); assert_eq!(one.to_string(), "/lower");
let two = LowerDirs::from_iter(["/first", "/second"]); 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(); let from_str_none = LowerDirs::from_str("").unwrap();
assert_eq!(from_str_none, empty); assert_eq!(from_str_none, empty);
@ -370,7 +394,7 @@ mod tests {
let from_str_one = LowerDirs::from_str("/lower").unwrap(); let from_str_one = LowerDirs::from_str("/lower").unwrap();
assert_eq!(from_str_one, one); 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); assert_eq!(from_str_two, two);
} }
@ -441,36 +465,29 @@ mod tests {
let basic = FuseOverlay::new("/mnt"); let basic = FuseOverlay::new("/mnt");
assert_eq!(basic.to_string(), "fuse-overlayfs /mnt"); assert_eq!(basic.to_string(), "fuse-overlayfs /mnt");
let mut with_options = FuseOverlay::new("~/.config"); let mut with_options = FuseOverlay::readonly("~/.config", ["~/.themes", "~/.colors"]);
with_options.extend_options([ with_options.push_option(FuseOverlayOption::NoAcl);
FuseOverlayOption::NoAcl,
LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
]);
assert_eq!( assert_eq!(
with_options.to_string(), with_options.to_string(),
"fuse-overlayfs -o noacl -o lowerdir=~/.themes,~/.colors ~/.config" "fuse-overlayfs -olowerdir=~/.themes:~/.colors -onoacl ~/.config"
); );
} }
#[test] #[test]
fn mountpoint_trait_impl() { fn mountpoint_trait_impl() {
let mut all = FuseOverlay::new("/mnt"); let mut all = FuseOverlay::writable("/mnt", ["/doesnt", "/matter"], "/upper");
all.extend_options([ all.set_workdir("/work");
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(); let opts: Vec<String> = all.options().map(|x| x.to_string()).collect();
assert_eq!( assert_eq!(
opts, opts,
[ [
"-o lowerdir=/doesnt,/matter", "-olowerdir=/doesnt:/matter",
"-o upperdir=/upper", "-oupperdir=/upper",
"-o workdir=/work" "-oworkdir=/work"
] ]
); );
} }

View file

@ -67,7 +67,7 @@ impl FromStr for LowerDirs {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self( Ok(Self(
s.split(',') s.split(':')
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.map(Into::into) .map(Into::into)
.collect(), .collect(),

View file

@ -1,4 +1,3 @@
use std::ffi::OsString;
use std::fmt::Display; use std::fmt::Display;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
@ -277,7 +276,7 @@ impl Stack for Overlay {
impl FileSystem for Overlay { impl FileSystem for Overlay {
fn mount(&mut self) -> std::io::Result<()> { fn mount(&mut self) -> std::io::Result<()> {
let opts = self.options.iter().map(ToString::to_string); 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)?; mount("overlay", &self.mountpoint, options)?;
Ok(()) Ok(())
} }

View file

@ -2,7 +2,7 @@ use vahanafs::fs::FileSystem;
use vahanafs::prelude::*; use vahanafs::prelude::*;
fn main() { 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}"); println!("{fs}");
fs.mount().expect("could not mount overlayfs"); fs.mount().expect("could not mount overlayfs");
} }