wip: options delimter
This commit is contained in:
parent
947ff86015
commit
b459d08a74
5 changed files with 161 additions and 49 deletions
148
src/fs/mount.rs
148
src/fs/mount.rs
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)]
|
#[derive(Debug)]
|
||||||
pub struct Options<T>(Vec<T>);
|
pub struct Options<T> {
|
||||||
|
options: Vec<T>,
|
||||||
impl<T> Default for Options<T> {
|
delimiter: char,
|
||||||
fn default() -> Self {
|
|
||||||
Self(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsRef<Vec<T>> for Options<T> {
|
impl<T> Options<T> {
|
||||||
fn as_ref(&self) -> &Vec<T> {
|
pub fn new(options: Vec<T>, delimiter: char) -> Self {
|
||||||
&self.0
|
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"]),
|
||||||
|
|
|
@ -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,34 +465,27 @@ 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",
|
||||||
"-oupperdir=/upper",
|
"-oupperdir=/upper",
|
||||||
"-oworkdir=/work"
|
"-oworkdir=/work"
|
||||||
]
|
]
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue