wip overlay refactor
This commit is contained in:
parent
af4af462bd
commit
947ff86015
9 changed files with 708 additions and 448 deletions
|
@ -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};
|
||||
|
|
323
src/fs/mount.rs
323
src/fs/mount.rs
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -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::*;
|
||||
}
|
||||
|
|
|
@ -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
8
src/main.rs
Normal 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");
|
||||
}
|
119
src/utils.rs
119
src/utils.rs
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue