wip overlay refactor

This commit is contained in:
Rowan 2025-03-04 22:45:56 -06:00
parent af4af462bd
commit 947ff86015
9 changed files with 708 additions and 448 deletions

View file

@ -1,6 +1,6 @@
mod id_mapping;
mod mount;
mod permission;
pub(crate) mod id_mapping;
pub(crate) mod mount;
pub mod permission;
pub mod stackable;
use std::{fmt::Display, slice::Iter};

View file

@ -2,6 +2,7 @@ use itertools::Itertools;
use std::{
convert::Infallible,
error::Error,
ffi::{OsStr, OsString},
fmt::Display,
ops::{Deref, DerefMut},
path::PathBuf,
@ -9,9 +10,12 @@ use std::{
str::FromStr,
};
use crate::utils::DisplayString;
use super::{
id_mapping::{ParseUntypedIdRangeError, UntypedIdRange},
permission::Permissions,
FileSystem,
};
#[derive(Debug, Clone, PartialEq, Eq)]
@ -27,24 +31,24 @@ impl Error for ParseKeyValuePairError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyValuePair {
name: String,
value: Option<String>,
name: DisplayString,
value: Option<DisplayString>,
}
impl KeyValuePair {
pub fn new(name: impl Into<String>, value: Option<impl Into<String>>) -> Self {
pub fn new(name: impl Into<DisplayString>, value: Option<impl Into<DisplayString>>) -> Self {
Self {
name: name.into(),
value: value.map(Into::into),
}
}
pub fn into_tuple(self) -> (String, Option<String>) {
pub fn into_tuple(self) -> (DisplayString, Option<DisplayString>) {
(self.name, self.value)
}
}
impl From<KeyValuePair> for (String, Option<String>) {
impl From<KeyValuePair> for (DisplayString, Option<DisplayString>) {
fn from(kvp: KeyValuePair) -> Self {
kvp.into_tuple()
}
@ -70,58 +74,6 @@ impl Display for KeyValuePair {
}
}
#[derive(Debug)]
pub struct MountOptions<T>(pub Vec<T>);
impl<T: Clone> Clone for MountOptions<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> Default for MountOptions<T> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<T> Deref for MountOptions<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for MountOptions<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> FromIterator<T> for MountOptions<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
impl<T: FromStr> FromStr for MountOptions<T> {
type Err = T::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(',')
.map(T::from_str)
.process_results(|it| MountOptions(it.collect()))
}
}
impl<T: Display> Display for MountOptions<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let options = self.0.iter().map(|x| x.to_string()).join(" ");
write!(f, "{options}")
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseAccessError;
@ -178,12 +130,12 @@ impl Error for ParseDeviceIdTypeError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeviceId {
Label(String),
Uuid(String),
PartLabel(String),
PartUuid(String),
Id(String),
Other(String),
Label(DisplayString),
Uuid(DisplayString),
PartLabel(DisplayString),
PartUuid(DisplayString),
Id(DisplayString),
Other(DisplayString),
}
impl FromStr for DeviceId {
@ -192,14 +144,14 @@ impl FromStr for DeviceId {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once('=') {
Some((key, value)) => match key {
"LABEL" => Ok(Self::Label(value.to_string())),
"UUID" => Ok(Self::Uuid(value.to_string())),
"PARTLABEL" => Ok(Self::PartLabel(value.to_string())),
"PARTUUID" => Ok(Self::PartUuid(value.to_string())),
"ID" => Ok(Self::Id(value.to_string())),
"LABEL" => Ok(Self::Label(value.into())),
"UUID" => Ok(Self::Uuid(value.into())),
"PARTLABEL" => Ok(Self::PartLabel(value.into())),
"PARTUUID" => Ok(Self::PartUuid(value.into())),
"ID" => Ok(Self::Id(value.into())),
key => Err(ParseDeviceIdTypeError(key.to_string())),
},
None => Ok(Self::Other(s.to_string())),
None => Ok(Self::Other(s.into())),
}
}
}
@ -223,13 +175,13 @@ impl Display for DeviceId {
impl From<String> for DeviceId {
fn from(value: String) -> Self {
Self::from_str(&value).unwrap_or_else(|_v| Self::Other(value))
Self::from_str(&value).unwrap_or_else(|_v| Self::Other(value.into()))
}
}
impl From<&str> for DeviceId {
fn from(value: &str) -> Self {
Self::from_str(value).unwrap_or_else(|_v| Self::Other(value.to_string()))
Self::from_str(value).unwrap_or_else(|_v| Self::Other(value.into()))
}
}
@ -425,6 +377,32 @@ impl From<UntypedIdRange> for GidRange {
#[derive(Debug)]
pub struct Options<T>(Vec<T>);
impl<T> Default for Options<T> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<T> AsRef<Vec<T>> for Options<T> {
fn as_ref(&self) -> &Vec<T> {
&self.0
}
}
impl<T> Deref for Options<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Options<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: Clone> Clone for Options<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
@ -464,12 +442,24 @@ 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())
}
}
impl<T> From<Vec<T>> for Options<T> {
fn from(value: Vec<T>) -> Self {
Self(value)
}
}
impl<T> From<T> for Options<T> {
fn from(value: T) -> Self {
Self(vec![value])
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Sources(Options<OptionsSource>);
@ -499,13 +489,13 @@ impl From<Infallible> for ParseUncheckedOptionsError {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UncheckedOptions(Options<String>);
pub struct UncheckedOptions(Options<DisplayString>);
impl FromStr for UncheckedOptions {
type Err = ParseUncheckedOptionsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(Options::<String>::from_str(s)?))
Ok(Self(Options::<DisplayString>::from_str(s)?))
}
}
@ -521,36 +511,26 @@ impl From<String> for UncheckedOptions {
}
}
impl From<&str> for UncheckedOptions {
fn from(value: &str) -> Self {
Self::from_str(value).unwrap()
impl<T: AsRef<OsStr>> FromIterator<T> for UncheckedOptions {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self(Options::from_iter(iter.into_iter().map(Into::into)))
}
}
impl From<Vec<String>> for UncheckedOptions {
fn from(value: Vec<String>) -> Self {
Self(Options::from(value))
}
}
impl From<Vec<&str>> for UncheckedOptions {
fn from(value: Vec<&str>) -> Self {
value
.into_iter()
.map(String::from)
.collect::<Vec<String>>()
.into()
}
}
impl From<UncheckedOptions> for Options<String> {
impl From<UncheckedOptions> for Options<DisplayString> {
fn from(value: UncheckedOptions) -> Self {
value.0
}
}
impl From<Options<String>> for UncheckedOptions {
fn from(value: Options<String>) -> Self {
impl AsRef<Options<DisplayString>> for UncheckedOptions {
fn as_ref(&self) -> &Options<DisplayString> {
&self.0
}
}
impl From<Options<DisplayString>> for UncheckedOptions {
fn from(value: Options<DisplayString>) -> Self {
Self(value)
}
}
@ -564,7 +544,7 @@ pub enum ParseMountOptionError {
OptionsSource(ParseOptionsSourceError),
Operation(ParseMountOperationError),
Subtree(ParseSubtreeError),
UnknownOption(String),
UnknownOption(DisplayString),
}
impl Display for ParseMountOptionError {
@ -640,7 +620,7 @@ pub enum MountOption {
NoCanonicalize,
Fake,
Fork,
Fstab(String),
Fstab(OsString),
InternalOnly,
ShowLabels,
MapGroups(GidRange),
@ -658,7 +638,7 @@ pub enum MountOption {
TargetPrefix(PathBuf),
Verbose,
Access(Access),
Namespace(String),
Namespace(OsString),
Operation(MountOperation),
Subtree(Subtree),
}
@ -713,7 +693,7 @@ impl FromStr for MountOption {
(op, None) if op.starts_with("make-") => Ok(Self::Subtree(Subtree::from_str(
op.strip_prefix("make-").unwrap(),
)?)),
(opt, _) => Err(ParseMountOptionError::UnknownOption(opt.to_string())),
(opt, _) => Err(ParseMountOptionError::UnknownOption(opt.into())),
}
}
}
@ -730,7 +710,7 @@ impl Display for MountOption {
Self::NoCanonicalize => "--no-canonicalize".to_string(),
Self::Fake => "--fake".to_string(),
Self::Fork => "--fork".to_string(),
Self::Fstab(path) => format!("--fstab {path}"),
Self::Fstab(path) => format!("--fstab {}", path.to_string_lossy()),
Self::InternalOnly => "--internal-only".to_string(),
Self::ShowLabels => "--show-labels".to_string(),
Self::MapGroups(range) => format!("--map-groups {range}"),
@ -753,7 +733,7 @@ impl Display for MountOption {
Self::TargetPrefix(path) => format!("--target-prefix {}", path.to_string_lossy()),
Self::Verbose => "--verbose".to_string(),
Self::Access(access) => format!("--{access}"),
Self::Namespace(ns) => format!("--namespace {ns}"),
Self::Namespace(ns) => format!("--namespace {}", ns.to_string_lossy()),
Self::Operation(op) => format!("--{op}"),
Self::Subtree(op) => format!("--make-{op}"),
}
@ -762,7 +742,7 @@ impl Display for MountOption {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseSubtreeError(String);
pub struct ParseSubtreeError(DisplayString);
impl Display for ParseSubtreeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -802,29 +782,25 @@ impl FromStr for Subtree {
"rprivate" => Ok(Self::recursive(Self::Private)),
"runbindable" => Ok(Self::recursive(Self::Unbindable)),
_ => Err(ParseSubtreeError(s.to_string())),
_ => Err(ParseSubtreeError(s.into())),
}
}
}
impl Display for Subtree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Subtree::Shared => "shared".to_string(),
Subtree::Replica => "slave".to_string(),
Subtree::Private => "private".to_string(),
Subtree::Unbindable => "unbindable".to_string(),
Subtree::Recursive(op) => format!("r{op}"),
},
)
match self {
Subtree::Shared => write!(f, "shared"),
Subtree::Replica => write!(f, "slave"),
Subtree::Private => write!(f, "private"),
Subtree::Unbindable => write!(f, "unbindable"),
Subtree::Recursive(op) => write!(f, "r{op}"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseMountOperationError(String);
pub struct ParseMountOperationError(DisplayString);
impl Display for ParseMountOperationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -849,7 +825,7 @@ impl FromStr for MountOperation {
"bind" => Ok(Self::Bind),
"rbind" => Ok(Self::RecursiveBind),
"move" => Ok(Self::Move),
v => Err(ParseMountOperationError(v.to_string())),
v => Err(ParseMountOperationError(v.into())),
}
}
}
@ -868,45 +844,57 @@ impl Display for MountOperation {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Mount {
source: DeviceId,
target: PathBuf,
options: MountOptions<MountOption>,
device_id: DeviceId,
mountpoint: PathBuf,
options: Options<MountOption>,
}
impl Mount {
pub fn new(
src: impl Into<DeviceId>,
target: impl Into<PathBuf>,
options: impl Into<MountOptions<MountOption>>,
) -> Self {
pub fn new<T, I>(source: impl Into<DeviceId>, target: impl Into<PathBuf>, options: I) -> Self
where
T: Into<MountOption>,
I: IntoIterator<Item = T>,
{
Self {
source: src.into(),
target: target.into(),
options: options.into(),
device_id: source.into(),
mountpoint: target.into(),
options: Options::from_iter(options.into_iter().map(Into::into)),
}
}
pub fn mount(&self) -> std::io::Result<std::process::Output> {
Command::new("mount")
.arg(self.options.to_string())
.arg(self.source.to_string())
.arg(&self.target)
.output()
}
}
impl Display for Mount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"mount {} {} {}",
self.options,
self.source,
self.target.to_string_lossy()
)
impl FileSystem for Mount {
fn mount(&mut self) -> std::io::Result<()> {
mount(
self.device_id,
&self.mountpoint,
self.options.map(Into::into),
)?;
Ok(())
}
fn unmount(&mut self) -> std::io::Result<()> {
unmount(&self.mountpoint)?;
Ok(())
}
}
pub fn mount<T: AsRef<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()))
.arg(source.into().to_string())
.arg(target.into())
.output()
}
pub fn unmount(target: impl Into<PathBuf>) -> std::io::Result<std::process::Output> {
Command::new("umount").arg(target.into()).output()
}
#[cfg(test)]
@ -914,7 +902,6 @@ mod tests {
use std::{borrow::Cow, path::PathBuf, str::FromStr};
use enumflags2::BitFlags;
use itertools::Itertools;
use crate::fs::{
id_mapping::UntypedIdRange,
@ -923,8 +910,8 @@ mod tests {
};
use super::{
Access, DeviceId, KeyValuePair, MapUsers, Mount, MountOperation, MountOption, MountOptions,
Options, OptionsMode, OptionsSource, ParseMountOptionError, Subtree, UncheckedOptions,
Access, DeviceId, KeyValuePair, MapUsers, MountOperation, MountOption, Options,
OptionsMode, OptionsSource, ParseMountOptionError, Subtree, UncheckedOptions,
};
#[test]
@ -962,12 +949,12 @@ mod tests {
let other = DeviceId::from_str("awawa").unwrap();
let should_err = DeviceId::from_str("GARBAGE=trash");
assert_eq!(label, DeviceId::Label("test".to_string()));
assert_eq!(uuid, DeviceId::Uuid("1234".to_string()));
assert_eq!(pl, DeviceId::PartLabel("test".to_string()));
assert_eq!(pu, DeviceId::PartUuid("98776543".to_string()));
assert_eq!(id, DeviceId::Id("awawa".to_string()));
assert_eq!(other, DeviceId::Other("awawa".to_string()));
assert_eq!(label, DeviceId::Label("test".into()));
assert_eq!(uuid, DeviceId::Uuid("1234".into()));
assert_eq!(pl, DeviceId::PartLabel("test".into()));
assert_eq!(pu, DeviceId::PartUuid("98776543".into()));
assert_eq!(id, DeviceId::Id("awawa".into()));
assert_eq!(other, DeviceId::Other("awawa".into()));
assert_eq!(
should_err,
Err(ParseDeviceIdTypeError("GARBAGE".to_string()))
@ -1110,7 +1097,7 @@ mod tests {
};
cmds.iter().map(|s| to_args(s)).fold(
Err(ParseMountOptionError::UnknownOption("".to_string())),
Err(ParseMountOptionError::UnknownOption("".into())),
|_, c| MountOption::from_str(&c),
)
}
@ -1168,8 +1155,9 @@ mod tests {
Opts::from((["-L", "--label"].as_slice(), "LABEL=label"))
.assert(MountOption::Label(DeviceId::Label("label".to_string())));
Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs"))
.assert(MountOption::FsType(vec!["nomsdos", "smbs"].into()));
Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs")).assert(MountOption::FsType(
UncheckedOptions::from_iter(vec!["nomsdos", "smbs"]),
));
Opts::from("-a").assert(MountOption::All);
Opts::from(["-c", "--no-canonicalize"].as_slice()).assert(MountOption::NoCanonicalize);
@ -1211,11 +1199,7 @@ mod tests {
Opts::from("--options-source-force").assert(MountOption::OptionsSourceForce);
Opts::from("--onlyonce").assert(MountOption::OnlyOnce);
let opts: UncheckedOptions = vec!["kbity", "dogy", "aaaaaa=awawa"]
.iter()
.map(ToString::to_string)
.collect_vec()
.into();
let opts = UncheckedOptions::from_iter(vec!["kbity", "dogy", "aaaaaa=awawa"]);
Opts::from((["-o", "--options"].as_slice(), "kbity,dogy,aaaaaa=awawa"))
.assert(MountOption::Options(opts.clone()));
@ -1264,19 +1248,4 @@ mod tests {
Subtree::Unbindable,
)));
}
#[test]
fn mount() {
let mut options = MountOptions::default();
options.push(MountOption::FsType(vec!["overlay"].into()));
options.push(MountOption::MakeDir(Some(Permissions::Octal(
BitFlags::from_bits(0x7777).unwrap().into(),
))));
let mount = Mount::new("/test", "/target", options);
assert_eq!(
mount.to_string(),
"mount --types overlay --mkdir 7777 /test /target"
);
}
}

View file

@ -1,57 +1,15 @@
use std::{
convert::Infallible,
error::Error,
fmt::Display,
num::ParseIntError,
ops::Deref,
path::{Path, PathBuf},
process::Command,
slice::Iter,
str::FromStr,
};
use std::{error::Error, fmt::Display, num::ParseIntError, path::PathBuf, str::FromStr};
use crate::fs::{
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
mount::MountOptions,
AsIter, FileSystem, Mountpoint,
};
use crate::macros::*;
use crate::{
fs::{
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
FileSystem,
},
utils::Command,
};
use super::Stack;
macro_rules! impl_deref {
($name:ident($inner:ty)) => {
impl Deref for $name {
type Target = $inner;
fn deref(&self) -> &Self::Target {
&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<Self, Self::Err> {
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)
}
}
};
}
use super::{DirIter, LowerDirs, Stack, StackFs};
macro_rules! impl_wrapper_struct {
($name:ident($inner:ty), $err:ty) => {
@ -94,99 +52,6 @@ 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::Item> {
self.inner.next().map(PathBuf::as_path)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LowerDirs(Vec<PathBuf>);
impl<A: Into<PathBuf>> FromIterator<A> for LowerDirs {
fn from_iter<T: IntoIterator<Item = A>>(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<PathBuf>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for LowerDirs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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(())
}
}
impl FromStr for LowerDirs {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(
s.split(',')
.filter(|s| !s.is_empty())
.map(Into::into)
.collect(),
))
}
}
impl From<Vec<&str>> for LowerDirs {
fn from(value: Vec<&str>) -> Self {
Self(value.into_iter().map(PathBuf::from).collect())
}
}
impl From<Vec<String>> for LowerDirs {
fn from(value: Vec<String>) -> Self {
Self(value.into_iter().map(PathBuf::from).collect())
}
}
impl From<Vec<&Path>> for LowerDirs {
fn from(value: Vec<&Path>) -> Self {
Self(value.into_iter().map(Path::to_path_buf).collect())
}
}
impl From<Vec<PathBuf>> for LowerDirs {
fn from(value: Vec<PathBuf>) -> Self {
Self(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct MaxIdleThreads(Option<usize>);
@ -225,7 +90,7 @@ impl FromStr for MaxThreads {
type Err = ParseMaxThreadsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(usize::from_str_radix(s, 10).map(Self)?)
Ok(s.parse::<usize>().map(Self)?)
}
}
@ -384,7 +249,7 @@ impl FromStr for FuseOverlayOption {
impl Display for FuseOverlayOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "-o ")?;
write!(f, "-o")?;
match self {
FuseOverlayOption::LowerDir(lower) => write!(f, "lowerdir={lower}"),
@ -407,41 +272,25 @@ impl Display for FuseOverlayOption {
}
#[derive(Debug)]
pub struct FuseOverlay {
mount_target: PathBuf,
options: MountOptions<FuseOverlayOption>,
}
pub struct FuseOverlay(StackFs<FuseOverlayOption>);
impl_deref!(FuseOverlay(StackFs<FuseOverlayOption>));
impl_deref_mut!(FuseOverlay(StackFs<FuseOverlayOption>));
impl FuseOverlay {
pub fn new(target: impl Into<PathBuf>) -> Self {
Self {
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<Item = FuseOverlayOption>) {
self.options.extend(options)
}
fn find_option<'a, T, F>(&'a self, f: F) -> Option<T>
where
F: Fn(&'a FuseOverlayOption) -> Option<T>,
{
self.options.iter().filter_map(f).take(1).next()
pub fn new(
target: impl Into<PathBuf>,
lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
) -> Self {
let mut fs = StackFs::new(target);
fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)));
Self(fs)
}
}
impl Stack for FuseOverlay {
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
let result = self.find_option(|opt| match opt {
FuseOverlayOption::LowerDir(dirs) => Some(dirs),
_ => None,
});
let result = self.find_option(|v| unwrap_enum!(v, FuseOverlayOption::LowerDir));
match result {
Some(dirs) => dirs.into_iter(),
@ -450,23 +299,13 @@ impl Stack for FuseOverlay {
}
fn upper_dir(&self) -> Option<&std::path::Path> {
self.find_option(|opt| match opt {
FuseOverlayOption::UpperDir(dir) => Some(dir.as_path()),
_ => None,
})
self.find_option(|v| unwrap_enum!(v, FuseOverlayOption::UpperDir))
.map(PathBuf::as_path)
}
fn work_dir(&self) -> Option<&std::path::Path> {
self.find_option(|opt| match opt {
FuseOverlayOption::WorkDir(dir) => Some(dir.as_path()),
_ => None,
})
}
}
impl Mountpoint for FuseOverlay {
fn options(&self) -> impl Iterator<Item = impl Display> {
self.options.as_iter()
self.find_option(|v| unwrap_enum!(v, FuseOverlayOption::WorkDir))
.map(PathBuf::as_path)
}
}
@ -474,6 +313,7 @@ impl FileSystem for FuseOverlay {
fn mount(&mut self) -> std::io::Result<()> {
Command::new("fuse-overlayfs")
.arg(self.options.to_string())
.arg(&self.mountpoint)
.output()?;
Ok(())
@ -482,7 +322,7 @@ impl FileSystem for FuseOverlay {
fn unmount(&mut self) -> std::io::Result<()> {
Command::new("fusermount")
.arg("-u")
.arg(&self.mount_target)
.arg(&self.mountpoint)
.output()?;
Ok(())
@ -495,11 +335,11 @@ impl Display for FuseOverlay {
// INFO: this is to get rid of the extraneous space if
// self.options is empty
if self.options.len() > 0 {
if !self.options.is_empty() {
write!(f, " {}", self.options)?;
}
write!(f, " {}", self.mount_target.to_string_lossy())
write!(f, " {}", self.mountpoint.to_string_lossy())
}
}
@ -602,7 +442,7 @@ mod tests {
assert_eq!(basic.to_string(), "fuse-overlayfs /mnt");
let mut with_options = FuseOverlay::new("~/.config");
with_options.push_options([
with_options.extend_options([
FuseOverlayOption::NoAcl,
LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
]);
@ -617,7 +457,7 @@ mod tests {
fn mountpoint_trait_impl() {
let mut all = FuseOverlay::new("/mnt");
all.push_options([
all.extend_options([
LowerDirs::from_iter(["/doesnt", "/matter"]).into(),
FuseOverlayOption::UpperDir("/upper".into()),
FuseOverlayOption::WorkDir("/work".into()),

View file

@ -1,7 +1,115 @@
pub mod fuse_overlay;
pub mod overlay;
use std::path::Path;
use std::convert::Infallible;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::slice::Iter;
use std::str::FromStr;
use crate::macros::impl_deref;
use super::mount::Options;
use super::Mountpoint;
#[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::Item> {
self.inner.next().map(PathBuf::as_path)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LowerDirs(Vec<PathBuf>);
impl_deref!(LowerDirs(Vec<PathBuf>));
impl<A: Into<PathBuf>> FromIterator<A> for LowerDirs {
fn from_iter<T: IntoIterator<Item = A>>(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 Display for LowerDirs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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(())
}
}
impl FromStr for LowerDirs {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(
s.split(',')
.filter(|s| !s.is_empty())
.map(Into::into)
.collect(),
))
}
}
#[derive(Debug)]
pub struct StackFs<T> {
mountpoint: PathBuf,
options: Options<T>,
}
impl<T> StackFs<T> {
pub fn new(target: impl Into<PathBuf>) -> Self {
Self {
mountpoint: target.into(),
options: Default::default(),
}
}
pub fn push_option(&mut self, value: T) {
self.options.push(value);
}
pub fn extend_options(&mut self, iter: impl IntoIterator<Item = T>) {
self.options.extend(iter);
}
pub fn find_option<'a, R, F>(&'a self, f: F) -> Option<R>
where
F: Fn(&'a T) -> Option<R>,
{
self.options.iter().filter_map(f).take(1).next()
}
}
impl<T: Display> Mountpoint for StackFs<T> {
fn options(&self) -> impl Iterator<Item = impl Display> {
self.options.iter()
}
}
pub trait Stack {
fn lower_dirs(&self) -> impl Iterator<Item = &Path>;

View file

@ -1,10 +1,46 @@
use std::ffi::OsString;
use std::fmt::Display;
use std::iter;
use std::path::PathBuf;
use std::str::FromStr;
use crate::fs::{AsIter, Mountpoint};
use char_enum::FromStrError;
use super::Stack;
use crate::fs::mount::{mount, unmount, MountOption, UncheckedOptions};
use crate::fs::FileSystem;
use crate::macros::{impl_deref, impl_deref_mut, unwrap_enum};
use super::{LowerDirs, Stack, StackFs};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Switch {
On,
Off,
}
impl FromStr for Switch {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"on" => Ok(Self::On),
"off" => Ok(Self::Off),
s => Err(FromStrError(s.to_string())),
}
}
}
impl Display for Switch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Switch::On => "on",
Switch::Off => "off",
}
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum RedirectDir {
@ -14,6 +50,35 @@ pub enum RedirectDir {
Off,
}
impl FromStr for RedirectDir {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"on" => Ok(Self::On),
"follow" => Ok(Self::Follow),
"nofollow" => Ok(Self::NoFollow),
"off" => Ok(Self::Off),
s => Err(FromStrError(s.to_string())),
}
}
}
impl Display for RedirectDir {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
RedirectDir::On => "on",
RedirectDir::Follow => "follow",
RedirectDir::NoFollow => "nofollow",
RedirectDir::Off => "off",
}
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Uuid {
On,
@ -22,6 +87,35 @@ pub enum Uuid {
Off,
}
impl FromStr for Uuid {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"on" => Ok(Self::On),
"auto" => Ok(Self::Auto),
"null" => Ok(Self::Null),
"off" => Ok(Self::Off),
s => Err(FromStrError(s.to_string())),
}
}
}
impl Display for Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::On => "on",
Self::Auto => "auto",
Self::Null => "null",
Self::Off => "off",
}
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verity {
On,
@ -29,6 +123,33 @@ pub enum Verity {
Off,
}
impl FromStr for Verity {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"on" => Ok(Self::On),
"require" => Ok(Self::Require),
"off" => Ok(Self::Off),
s => Err(FromStrError(s.to_string())),
}
}
}
impl Display for Verity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::On => "on",
Self::Require => "require",
Self::Off => "off",
}
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Xino {
On,
@ -36,89 +157,133 @@ pub enum Xino {
Off,
}
#[derive(Debug)]
pub enum OverlayOptions {
Index(bool),
Metacopy(bool),
NfsExport(bool),
RedirectDir(RedirectDir),
UserXAttr,
Uuid(Uuid),
Verity(Verity),
Volatile,
Xino(Xino),
impl FromStr for Xino {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"on" => Ok(Self::On),
"auto" => Ok(Self::Auto),
"off" => Ok(Self::Off),
s => Err(FromStrError(s.to_string())),
}
}
}
impl Display for OverlayOptions {
impl Display for Xino {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
OverlayOptions::Index(enabled) => match enabled {
true => "index=on",
false => "index=off",
},
OverlayOptions::Metacopy(enabled) => match enabled {
true => "metacopy=on",
false => "metacopy=off",
},
OverlayOptions::NfsExport(enabled) => match enabled {
true => "nfs_export=on",
false => "nfs_export=off",
},
OverlayOptions::RedirectDir(redirect_dir) => match redirect_dir {
RedirectDir::On => "redirect_dir=on",
RedirectDir::Follow => "redirect_dir=follow",
RedirectDir::NoFollow => "redirect_dir=nofollow",
RedirectDir::Off => "redirect_dir=off",
},
OverlayOptions::UserXAttr => "userxattr",
OverlayOptions::Uuid(uuid) => match uuid {
Uuid::On => "uuid=on",
Uuid::Auto => "uuid=auto",
Uuid::Null => "uuid=null",
Uuid::Off => "uuid=off",
},
OverlayOptions::Verity(verity) => match verity {
Verity::On => "verity=on",
Verity::Require => "verity=require",
Verity::Off => "verity=off",
},
OverlayOptions::Volatile => "volatile",
OverlayOptions::Xino(xino) => match xino {
Xino::On => "xino=on",
Xino::Auto => "xino=auto",
Xino::Off => "xino=off",
},
Self::On => "on",
Self::Auto => "Auto",
Self::Off => "off",
}
)
}
}
#[derive(Debug)]
pub struct Overlay {
mount_target: PathBuf,
options: Vec<OverlayOptions>,
pub enum OverlayOption {
Index(Switch),
LowerDir(LowerDirs),
Metacopy(Switch),
NfsExport(Switch),
RedirectDir(RedirectDir),
UpperDir(PathBuf),
UserXAttr,
Uuid(Uuid),
Verity(Verity),
Volatile,
WorkDir(PathBuf),
Xino(Xino),
}
impl FromStr for OverlayOption {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let arg = match s.split_once('=') {
Some((k, v)) => (k, Some(v)),
None => (s, None),
};
match arg {
("index", Some(enabled)) => Ok(Self::Index(Switch::from_str(enabled)?)),
("lowerdir", Some(dirs)) => Ok(Self::LowerDir(LowerDirs::from_str(dirs).unwrap())),
("metacopy", Some(enabled)) => Ok(Self::Metacopy(Switch::from_str(enabled)?)),
("nfs_export", Some(enabled)) => Ok(Self::NfsExport(Switch::from_str(enabled)?)),
("redirect_dir", Some(dir)) => Ok(Self::RedirectDir(RedirectDir::from_str(dir)?)),
("upperdir", Some(dir)) => Ok(Self::UpperDir(PathBuf::from_str(dir).unwrap())),
("userxattr", None) => Ok(Self::UserXAttr),
("uuid", Some(uuid)) => Ok(Self::Uuid(Uuid::from_str(uuid)?)),
("verity", Some(verity)) => Ok(Self::Verity(Verity::from_str(verity)?)),
("volatile", None) => Ok(Self::Volatile),
("workdir", Some(dir)) => Ok(Self::WorkDir(PathBuf::from_str(dir).unwrap())),
("xino", Some(xino)) => Ok(Self::Xino(Xino::from_str(xino)?)),
(arg, _) => Err(FromStrError(arg.to_string())),
}
}
}
impl Display for OverlayOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Index(enabled) => write!(f, "index={enabled}"),
Self::LowerDir(dirs) => write!(f, "lowerdir={dirs}"),
Self::Metacopy(enabled) => write!(f, "metacopy={enabled}"),
Self::NfsExport(enabled) => write!(f, "nfs_export={enabled}"),
Self::RedirectDir(option) => write!(f, "redirect_dir={option}"),
Self::UpperDir(dir) => write!(f, "upperdir={}", dir.to_string_lossy()),
Self::UserXAttr => write!(f, "userxattr"),
Self::Uuid(uuid) => write!(f, "uid={uuid}"),
Self::Verity(verity) => write!(f, "verity={verity}"),
Self::Volatile => write!(f, "volatile"),
Self::WorkDir(dir) => write!(f, "workdir={}", dir.to_string_lossy()),
Self::Xino(xino) => write!(f, "xino={xino}"),
}
}
}
#[derive(Debug)]
pub struct Overlay(StackFs<OverlayOption>);
impl_deref!(Overlay(StackFs<OverlayOption>));
impl_deref_mut!(Overlay(StackFs<OverlayOption>));
impl Stack for Overlay {
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
todo!();
iter::empty()
let result = self.find_option(|v| unwrap_enum!(v, OverlayOption::LowerDir));
match result {
Some(dirs) => dirs.into_iter(),
None => Default::default(),
}
}
fn upper_dir(&self) -> Option<&std::path::Path> {
todo!()
self.find_option(|v| unwrap_enum!(v, OverlayOption::UpperDir))
.map(PathBuf::as_path)
}
fn work_dir(&self) -> Option<&std::path::Path> {
todo!()
self.find_option(|v| unwrap_enum!(v, OverlayOption::WorkDir))
.map(PathBuf::as_path)
}
}
impl Mountpoint for Overlay {
fn options(&self) -> impl Iterator<Item = impl Display> {
self.options.as_iter()
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));
mount("overlay", &self.mountpoint, options)?;
Ok(())
}
fn unmount(&mut self) -> std::io::Result<()> {
unmount(&self.mountpoint)?;
Ok(())
}
}

View file

@ -2,17 +2,10 @@ pub mod fs;
mod macros;
mod utils;
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
pub mod prelude {
pub use crate::fs::mount::*;
pub use crate::fs::stackable::fuse_overlay::*;
pub use crate::fs::stackable::overlay::*;
pub use crate::fs::stackable::*;
pub use crate::fs::*;
}

View file

@ -1,3 +1,47 @@
macro_rules! impl_fromstr {
($name:ident($inner:ty), $err:ty) => {
impl std::str::FromStr for $name {
type Err = $err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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)
}
}
};
}
macro_rules! impl_deref {
($name:ident($inner:ty)) => {
impl std::ops::Deref for $name {
type Target = $inner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
};
}
macro_rules! impl_deref_mut {
($name:ident($inner:ty)) => {
impl std::ops::DerefMut for $name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
};
}
macro_rules! impl_from_variants {
($enum_type:ident, $($variant:ident($inner_type:ty)),*) => {
$(
@ -39,7 +83,21 @@ macro_rules! impl_wrapper_err {
};
}
macro_rules! unwrap_enum {
($value:ident, $enum:ident::$variant:ident) => {
match $value {
$enum::$variant(v) => Some(v),
_ => None,
}
};
}
pub(crate) use impl_deref;
pub(crate) use impl_deref_mut;
pub(crate) use impl_display;
pub(crate) use impl_from_ty;
pub(crate) use impl_from_variants;
pub(crate) use impl_fromstr;
pub(crate) use impl_wrapper_err;
pub(crate) use impl_wrapper_err_for;
pub(crate) use unwrap_enum;

8
src/main.rs Normal file
View file

@ -0,0 +1,8 @@
use vahanafs::fs::FileSystem;
use vahanafs::prelude::*;
fn main() {
let mut fs = FuseOverlay::new("/home/rowan/vfs/mnt", ["/home/rowan/vfs/lower1"]);
println!("{fs}");
fs.mount().expect("could not mount overlayfs");
}

View file

@ -1,4 +1,11 @@
use core::fmt::Debug;
use std::convert::Infallible;
use std::ffi::{OsStr, OsString};
use std::fmt::Display;
use std::path::Path;
use std::process::{Command as StdCommand, Output};
use crate::macros::{impl_deref, impl_deref_mut, impl_fromstr};
pub trait MapWhileRefExt<'a, I: 'a> {
fn map_while_ref<F>(&'a mut self, f: F) -> MapWhileRef<'a, I, F>;
@ -41,3 +48,115 @@ where
(0, self.iter.size_hint().1)
}
}
#[derive(Debug)]
pub enum CommandError {
Fail(Output),
Error(std::io::Error),
}
impl Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CommandError::Fail(output) => match output.status.code() {
Some(code) => {
write!(
f,
"exit {code}: {}",
String::from_utf8_lossy(&output.stderr)
)
}
None => write!(f, "process terminated by signal"),
},
CommandError::Error(error) => write!(f, "{error:?}"),
}
}
}
impl From<std::io::Error> for CommandError {
fn from(value: std::io::Error) -> Self {
Self::Error(value)
}
}
impl From<CommandError> for std::io::Error {
fn from(value: CommandError) -> Self {
match value {
CommandError::Fail(output) => {
std::io::Error::other(String::from_utf8_lossy(&output.stderr))
}
CommandError::Error(error) => error,
}
}
}
#[derive(Debug)]
pub struct Command(StdCommand);
impl_deref!(Command(StdCommand));
impl_deref_mut!(Command(StdCommand));
impl Command {
pub fn new(cmd: impl AsRef<OsStr>) -> Self {
Self(StdCommand::new(cmd))
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.0.arg(arg);
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.0.args(args);
self
}
pub fn env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
self.0.env(key, value);
self
}
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
self.0.env_remove(key);
self
}
pub fn current_dir(&mut self, path: impl AsRef<Path>) -> &mut Self {
self.0.current_dir(path);
self
}
pub fn output(&mut self) -> Result<Output, CommandError> {
let output = self.0.output()?;
if output.status.success() {
Ok(output)
} else {
Err(CommandError::Fail(output))
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DisplayString(OsString);
impl_deref!(DisplayString(OsString));
impl_deref_mut!(DisplayString(OsString));
impl_fromstr!(DisplayString(OsString), Infallible);
impl<T: AsRef<OsStr>> From<T> for DisplayString {
fn from(value: T) -> Self {
Self(value.as_ref().into())
}
}
impl Display for DisplayString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.display())
}
}