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},
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<UntypedIdRange> for GidRange {
}
}
// TODO: finish implementation to solve options delimiter problem
#[derive(Clone, Debug)]
pub struct DelimitedString {
inner: Vec<OsString>,
delimiter: char,
}
impl DelimitedString {
pub fn new<T, I>(inner: I, delimiter: char) -> Self
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 AsRef<OsStr> 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<T>(Vec<T>);
impl<T> Default for Options<T> {
fn default() -> Self {
Self(Vec::new())
}
pub struct Options<T> {
options: Vec<T>,
delimiter: char,
}
impl<T> AsRef<Vec<T>> for Options<T> {
fn as_ref(&self) -> &Vec<T> {
&self.0
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>;
fn deref(&self) -> &Self::Target {
&self.0
&self.options
}
}
impl<T> DerefMut for Options<T> {
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> {
fn clone(&self) -> Self {
Self(self.0.clone())
Self {
options: self.options.clone(),
delimiter: self.delimiter,
}
}
}
impl<T: PartialEq> PartialEq for Options<T> {
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> {
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<T: Display> Display for Options<T> {
impl<T> FromIterator<T> for Options<T> {
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> {
fn from(value: Vec<T>) -> Self {
Self(value)
Self::from_iter(value)
}
}
impl<T> From<T> for Options<T> {
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<T: AsRef<MountOption>, I: IntoIterator<Item = T>>(
pub fn mount<T: Into<MountOption>, I: IntoIterator<Item = T>>(
source: impl Into<DeviceId>,
target: impl Into<PathBuf>,
options: I,
) -> std::io::Result<std::process::Output> {
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![
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"]),

View file

@ -278,7 +278,11 @@ impl_deref!(FuseOverlay(StackFs<FuseOverlayOption>));
impl_deref_mut!(FuseOverlay(StackFs<FuseOverlayOption>));
impl FuseOverlay {
pub fn new(
pub fn new(target: impl Into<PathBuf>) -> Self {
Self(StackFs::new(target))
}
pub fn readonly(
target: impl Into<PathBuf>,
lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
) -> Self {
@ -286,6 +290,26 @@ impl FuseOverlay {
fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)));
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 {
@ -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,34 +465,27 @@ 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<String> = all.options().map(|x| x.to_string()).collect();
assert_eq!(
opts,
[
"-o lowerdir=/doesnt,/matter",
"-olowerdir=/doesnt:/matter",
"-oupperdir=/upper",
"-oworkdir=/work"
]

View file

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

View file

@ -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(())
}

View file

@ -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");
}