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;
|
pub(crate) mod id_mapping;
|
||||||
mod mount;
|
pub(crate) mod mount;
|
||||||
mod permission;
|
pub mod permission;
|
||||||
pub mod stackable;
|
pub mod stackable;
|
||||||
|
|
||||||
use std::{fmt::Display, slice::Iter};
|
use std::{fmt::Display, slice::Iter};
|
||||||
|
|
321
src/fs/mount.rs
321
src/fs/mount.rs
|
@ -2,6 +2,7 @@ use itertools::Itertools;
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
error::Error,
|
error::Error,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
@ -9,9 +10,12 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::utils::DisplayString;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
id_mapping::{ParseUntypedIdRangeError, UntypedIdRange},
|
id_mapping::{ParseUntypedIdRangeError, UntypedIdRange},
|
||||||
permission::Permissions,
|
permission::Permissions,
|
||||||
|
FileSystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -27,24 +31,24 @@ impl Error for ParseKeyValuePairError {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct KeyValuePair {
|
pub struct KeyValuePair {
|
||||||
name: String,
|
name: DisplayString,
|
||||||
value: Option<String>,
|
value: Option<DisplayString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyValuePair {
|
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 {
|
Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
value: value.map(Into::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)
|
(self.name, self.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<KeyValuePair> for (String, Option<String>) {
|
impl From<KeyValuePair> for (DisplayString, Option<DisplayString>) {
|
||||||
fn from(kvp: KeyValuePair) -> Self {
|
fn from(kvp: KeyValuePair) -> Self {
|
||||||
kvp.into_tuple()
|
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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct ParseAccessError;
|
pub struct ParseAccessError;
|
||||||
|
|
||||||
|
@ -178,12 +130,12 @@ impl Error for ParseDeviceIdTypeError {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum DeviceId {
|
pub enum DeviceId {
|
||||||
Label(String),
|
Label(DisplayString),
|
||||||
Uuid(String),
|
Uuid(DisplayString),
|
||||||
PartLabel(String),
|
PartLabel(DisplayString),
|
||||||
PartUuid(String),
|
PartUuid(DisplayString),
|
||||||
Id(String),
|
Id(DisplayString),
|
||||||
Other(String),
|
Other(DisplayString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for DeviceId {
|
impl FromStr for DeviceId {
|
||||||
|
@ -192,14 +144,14 @@ impl FromStr for DeviceId {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.split_once('=') {
|
match s.split_once('=') {
|
||||||
Some((key, value)) => match key {
|
Some((key, value)) => match key {
|
||||||
"LABEL" => Ok(Self::Label(value.to_string())),
|
"LABEL" => Ok(Self::Label(value.into())),
|
||||||
"UUID" => Ok(Self::Uuid(value.to_string())),
|
"UUID" => Ok(Self::Uuid(value.into())),
|
||||||
"PARTLABEL" => Ok(Self::PartLabel(value.to_string())),
|
"PARTLABEL" => Ok(Self::PartLabel(value.into())),
|
||||||
"PARTUUID" => Ok(Self::PartUuid(value.to_string())),
|
"PARTUUID" => Ok(Self::PartUuid(value.into())),
|
||||||
"ID" => Ok(Self::Id(value.to_string())),
|
"ID" => Ok(Self::Id(value.into())),
|
||||||
key => Err(ParseDeviceIdTypeError(key.to_string())),
|
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 {
|
impl From<String> for DeviceId {
|
||||||
fn from(value: String) -> Self {
|
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 {
|
impl From<&str> for DeviceId {
|
||||||
fn from(value: &str) -> Self {
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Options<T>(Vec<T>);
|
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> {
|
impl<T: Clone> Clone for Options<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self(self.0.clone())
|
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> {
|
impl<T> From<Vec<T>> for Options<T> {
|
||||||
fn from(value: Vec<T>) -> Self {
|
fn from(value: Vec<T>) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Options<T> {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(vec![value])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Sources(Options<OptionsSource>);
|
pub struct Sources(Options<OptionsSource>);
|
||||||
|
|
||||||
|
@ -499,13 +489,13 @@ impl From<Infallible> for ParseUncheckedOptionsError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct UncheckedOptions(Options<String>);
|
pub struct UncheckedOptions(Options<DisplayString>);
|
||||||
|
|
||||||
impl FromStr for UncheckedOptions {
|
impl FromStr for UncheckedOptions {
|
||||||
type Err = ParseUncheckedOptionsError;
|
type Err = ParseUncheckedOptionsError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
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 {
|
impl<T: AsRef<OsStr>> FromIterator<T> for UncheckedOptions {
|
||||||
fn from(value: &str) -> Self {
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
Self::from_str(value).unwrap()
|
Self(Options::from_iter(iter.into_iter().map(Into::into)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<String>> for UncheckedOptions {
|
impl From<UncheckedOptions> for Options<DisplayString> {
|
||||||
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> {
|
|
||||||
fn from(value: UncheckedOptions) -> Self {
|
fn from(value: UncheckedOptions) -> Self {
|
||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Options<String>> for UncheckedOptions {
|
impl AsRef<Options<DisplayString>> for UncheckedOptions {
|
||||||
fn from(value: Options<String>) -> Self {
|
fn as_ref(&self) -> &Options<DisplayString> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Options<DisplayString>> for UncheckedOptions {
|
||||||
|
fn from(value: Options<DisplayString>) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -564,7 +544,7 @@ pub enum ParseMountOptionError {
|
||||||
OptionsSource(ParseOptionsSourceError),
|
OptionsSource(ParseOptionsSourceError),
|
||||||
Operation(ParseMountOperationError),
|
Operation(ParseMountOperationError),
|
||||||
Subtree(ParseSubtreeError),
|
Subtree(ParseSubtreeError),
|
||||||
UnknownOption(String),
|
UnknownOption(DisplayString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ParseMountOptionError {
|
impl Display for ParseMountOptionError {
|
||||||
|
@ -640,7 +620,7 @@ pub enum MountOption {
|
||||||
NoCanonicalize,
|
NoCanonicalize,
|
||||||
Fake,
|
Fake,
|
||||||
Fork,
|
Fork,
|
||||||
Fstab(String),
|
Fstab(OsString),
|
||||||
InternalOnly,
|
InternalOnly,
|
||||||
ShowLabels,
|
ShowLabels,
|
||||||
MapGroups(GidRange),
|
MapGroups(GidRange),
|
||||||
|
@ -658,7 +638,7 @@ pub enum MountOption {
|
||||||
TargetPrefix(PathBuf),
|
TargetPrefix(PathBuf),
|
||||||
Verbose,
|
Verbose,
|
||||||
Access(Access),
|
Access(Access),
|
||||||
Namespace(String),
|
Namespace(OsString),
|
||||||
Operation(MountOperation),
|
Operation(MountOperation),
|
||||||
Subtree(Subtree),
|
Subtree(Subtree),
|
||||||
}
|
}
|
||||||
|
@ -713,7 +693,7 @@ impl FromStr for MountOption {
|
||||||
(op, None) if op.starts_with("make-") => Ok(Self::Subtree(Subtree::from_str(
|
(op, None) if op.starts_with("make-") => Ok(Self::Subtree(Subtree::from_str(
|
||||||
op.strip_prefix("make-").unwrap(),
|
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::NoCanonicalize => "--no-canonicalize".to_string(),
|
||||||
Self::Fake => "--fake".to_string(),
|
Self::Fake => "--fake".to_string(),
|
||||||
Self::Fork => "--fork".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::InternalOnly => "--internal-only".to_string(),
|
||||||
Self::ShowLabels => "--show-labels".to_string(),
|
Self::ShowLabels => "--show-labels".to_string(),
|
||||||
Self::MapGroups(range) => format!("--map-groups {range}"),
|
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::TargetPrefix(path) => format!("--target-prefix {}", path.to_string_lossy()),
|
||||||
Self::Verbose => "--verbose".to_string(),
|
Self::Verbose => "--verbose".to_string(),
|
||||||
Self::Access(access) => format!("--{access}"),
|
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::Operation(op) => format!("--{op}"),
|
||||||
Self::Subtree(op) => format!("--make-{op}"),
|
Self::Subtree(op) => format!("--make-{op}"),
|
||||||
}
|
}
|
||||||
|
@ -762,7 +742,7 @@ impl Display for MountOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ParseSubtreeError(String);
|
pub struct ParseSubtreeError(DisplayString);
|
||||||
|
|
||||||
impl Display for ParseSubtreeError {
|
impl Display for ParseSubtreeError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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)),
|
"rprivate" => Ok(Self::recursive(Self::Private)),
|
||||||
"runbindable" => Ok(Self::recursive(Self::Unbindable)),
|
"runbindable" => Ok(Self::recursive(Self::Unbindable)),
|
||||||
|
|
||||||
_ => Err(ParseSubtreeError(s.to_string())),
|
_ => Err(ParseSubtreeError(s.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Subtree {
|
impl Display for Subtree {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
match self {
|
||||||
Subtree::Shared => "shared".to_string(),
|
Subtree::Shared => write!(f, "shared"),
|
||||||
Subtree::Replica => "slave".to_string(),
|
Subtree::Replica => write!(f, "slave"),
|
||||||
Subtree::Private => "private".to_string(),
|
Subtree::Private => write!(f, "private"),
|
||||||
Subtree::Unbindable => "unbindable".to_string(),
|
Subtree::Unbindable => write!(f, "unbindable"),
|
||||||
Subtree::Recursive(op) => format!("r{op}"),
|
Subtree::Recursive(op) => write!(f, "r{op}"),
|
||||||
},
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ParseMountOperationError(String);
|
pub struct ParseMountOperationError(DisplayString);
|
||||||
|
|
||||||
impl Display for ParseMountOperationError {
|
impl Display for ParseMountOperationError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
@ -849,7 +825,7 @@ impl FromStr for MountOperation {
|
||||||
"bind" => Ok(Self::Bind),
|
"bind" => Ok(Self::Bind),
|
||||||
"rbind" => Ok(Self::RecursiveBind),
|
"rbind" => Ok(Self::RecursiveBind),
|
||||||
"move" => Ok(Self::Move),
|
"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 {
|
pub struct Mount {
|
||||||
source: DeviceId,
|
device_id: DeviceId,
|
||||||
target: PathBuf,
|
mountpoint: PathBuf,
|
||||||
options: MountOptions<MountOption>,
|
options: Options<MountOption>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mount {
|
impl Mount {
|
||||||
pub fn new(
|
pub fn new<T, I>(source: impl Into<DeviceId>, target: impl Into<PathBuf>, options: I) -> Self
|
||||||
src: impl Into<DeviceId>,
|
where
|
||||||
target: impl Into<PathBuf>,
|
T: Into<MountOption>,
|
||||||
options: impl Into<MountOptions<MountOption>>,
|
I: IntoIterator<Item = T>,
|
||||||
) -> Self {
|
{
|
||||||
Self {
|
Self {
|
||||||
source: src.into(),
|
device_id: source.into(),
|
||||||
target: target.into(),
|
mountpoint: target.into(),
|
||||||
options: options.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 {
|
impl FileSystem for Mount {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn mount(&mut self) -> std::io::Result<()> {
|
||||||
write!(
|
mount(
|
||||||
f,
|
self.device_id,
|
||||||
"mount {} {} {}",
|
&self.mountpoint,
|
||||||
self.options,
|
self.options.map(Into::into),
|
||||||
self.source,
|
)?;
|
||||||
self.target.to_string_lossy()
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -914,7 +902,6 @@ mod tests {
|
||||||
use std::{borrow::Cow, path::PathBuf, str::FromStr};
|
use std::{borrow::Cow, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use enumflags2::BitFlags;
|
use enumflags2::BitFlags;
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::fs::{
|
use crate::fs::{
|
||||||
id_mapping::UntypedIdRange,
|
id_mapping::UntypedIdRange,
|
||||||
|
@ -923,8 +910,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Access, DeviceId, KeyValuePair, MapUsers, Mount, MountOperation, MountOption, MountOptions,
|
Access, DeviceId, KeyValuePair, MapUsers, MountOperation, MountOption, Options,
|
||||||
Options, OptionsMode, OptionsSource, ParseMountOptionError, Subtree, UncheckedOptions,
|
OptionsMode, OptionsSource, ParseMountOptionError, Subtree, UncheckedOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -962,12 +949,12 @@ mod tests {
|
||||||
let other = DeviceId::from_str("awawa").unwrap();
|
let other = DeviceId::from_str("awawa").unwrap();
|
||||||
let should_err = DeviceId::from_str("GARBAGE=trash");
|
let should_err = DeviceId::from_str("GARBAGE=trash");
|
||||||
|
|
||||||
assert_eq!(label, DeviceId::Label("test".to_string()));
|
assert_eq!(label, DeviceId::Label("test".into()));
|
||||||
assert_eq!(uuid, DeviceId::Uuid("1234".to_string()));
|
assert_eq!(uuid, DeviceId::Uuid("1234".into()));
|
||||||
assert_eq!(pl, DeviceId::PartLabel("test".to_string()));
|
assert_eq!(pl, DeviceId::PartLabel("test".into()));
|
||||||
assert_eq!(pu, DeviceId::PartUuid("98776543".to_string()));
|
assert_eq!(pu, DeviceId::PartUuid("98776543".into()));
|
||||||
assert_eq!(id, DeviceId::Id("awawa".to_string()));
|
assert_eq!(id, DeviceId::Id("awawa".into()));
|
||||||
assert_eq!(other, DeviceId::Other("awawa".to_string()));
|
assert_eq!(other, DeviceId::Other("awawa".into()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
should_err,
|
should_err,
|
||||||
Err(ParseDeviceIdTypeError("GARBAGE".to_string()))
|
Err(ParseDeviceIdTypeError("GARBAGE".to_string()))
|
||||||
|
@ -1110,7 +1097,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
cmds.iter().map(|s| to_args(s)).fold(
|
cmds.iter().map(|s| to_args(s)).fold(
|
||||||
Err(ParseMountOptionError::UnknownOption("".to_string())),
|
Err(ParseMountOptionError::UnknownOption("".into())),
|
||||||
|_, c| MountOption::from_str(&c),
|
|_, c| MountOption::from_str(&c),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1168,8 +1155,9 @@ mod tests {
|
||||||
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".to_string())));
|
||||||
|
|
||||||
Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs"))
|
Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs")).assert(MountOption::FsType(
|
||||||
.assert(MountOption::FsType(vec!["nomsdos", "smbs"].into()));
|
UncheckedOptions::from_iter(vec!["nomsdos", "smbs"]),
|
||||||
|
));
|
||||||
|
|
||||||
Opts::from("-a").assert(MountOption::All);
|
Opts::from("-a").assert(MountOption::All);
|
||||||
Opts::from(["-c", "--no-canonicalize"].as_slice()).assert(MountOption::NoCanonicalize);
|
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("--options-source-force").assert(MountOption::OptionsSourceForce);
|
||||||
Opts::from("--onlyonce").assert(MountOption::OnlyOnce);
|
Opts::from("--onlyonce").assert(MountOption::OnlyOnce);
|
||||||
|
|
||||||
let opts: UncheckedOptions = vec!["kbity", "dogy", "aaaaaa=awawa"]
|
let opts = UncheckedOptions::from_iter(vec!["kbity", "dogy", "aaaaaa=awawa"]);
|
||||||
.iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect_vec()
|
|
||||||
.into();
|
|
||||||
|
|
||||||
Opts::from((["-o", "--options"].as_slice(), "kbity,dogy,aaaaaa=awawa"))
|
Opts::from((["-o", "--options"].as_slice(), "kbity,dogy,aaaaaa=awawa"))
|
||||||
.assert(MountOption::Options(opts.clone()));
|
.assert(MountOption::Options(opts.clone()));
|
||||||
|
@ -1264,19 +1248,4 @@ mod tests {
|
||||||
Subtree::Unbindable,
|
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::{
|
use std::{error::Error, fmt::Display, num::ParseIntError, path::PathBuf, str::FromStr};
|
||||||
convert::Infallible,
|
|
||||||
error::Error,
|
|
||||||
fmt::Display,
|
|
||||||
num::ParseIntError,
|
|
||||||
ops::Deref,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
|
||||||
slice::Iter,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::fs::{
|
|
||||||
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
|
|
||||||
mount::MountOptions,
|
|
||||||
AsIter, FileSystem, Mountpoint,
|
|
||||||
};
|
|
||||||
use crate::macros::*;
|
use crate::macros::*;
|
||||||
|
use crate::{
|
||||||
|
fs::{
|
||||||
|
id_mapping::{IdMapping as OriginalIdMapping, IdRange, ParseIdRangeError},
|
||||||
|
FileSystem,
|
||||||
|
},
|
||||||
|
utils::Command,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Stack;
|
use super::{DirIter, LowerDirs, Stack, StackFs};
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_wrapper_struct {
|
macro_rules! impl_wrapper_struct {
|
||||||
($name:ident($inner:ty), $err:ty) => {
|
($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!(ParseUidMappingError(ParseIdRangeError), "invalid uid");
|
||||||
impl_wrapper_err_for!(ParseGidMappingError(ParseIdRangeError), "invalid gid");
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
pub struct MaxIdleThreads(Option<usize>);
|
pub struct MaxIdleThreads(Option<usize>);
|
||||||
|
|
||||||
|
@ -225,7 +90,7 @@ impl FromStr for MaxThreads {
|
||||||
type Err = ParseMaxThreadsError;
|
type Err = ParseMaxThreadsError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
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 {
|
impl Display for FuseOverlayOption {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "-o ")?;
|
write!(f, "-o")?;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
FuseOverlayOption::LowerDir(lower) => write!(f, "lowerdir={lower}"),
|
FuseOverlayOption::LowerDir(lower) => write!(f, "lowerdir={lower}"),
|
||||||
|
@ -407,41 +272,25 @@ impl Display for FuseOverlayOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FuseOverlay {
|
pub struct FuseOverlay(StackFs<FuseOverlayOption>);
|
||||||
mount_target: PathBuf,
|
|
||||||
options: MountOptions<FuseOverlayOption>,
|
impl_deref!(FuseOverlay(StackFs<FuseOverlayOption>));
|
||||||
}
|
impl_deref_mut!(FuseOverlay(StackFs<FuseOverlayOption>));
|
||||||
|
|
||||||
impl FuseOverlay {
|
impl FuseOverlay {
|
||||||
pub fn new(target: impl Into<PathBuf>) -> Self {
|
pub fn new(
|
||||||
Self {
|
target: impl Into<PathBuf>,
|
||||||
mount_target: target.into(),
|
lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
|
||||||
options: Default::default(),
|
) -> Self {
|
||||||
}
|
let mut fs = StackFs::new(target);
|
||||||
}
|
fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)));
|
||||||
|
Self(fs)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stack for FuseOverlay {
|
impl Stack for FuseOverlay {
|
||||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||||
let result = self.find_option(|opt| match opt {
|
let result = self.find_option(|v| unwrap_enum!(v, FuseOverlayOption::LowerDir));
|
||||||
FuseOverlayOption::LowerDir(dirs) => Some(dirs),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(dirs) => dirs.into_iter(),
|
Some(dirs) => dirs.into_iter(),
|
||||||
|
@ -450,23 +299,13 @@ impl Stack for FuseOverlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
fn upper_dir(&self) -> Option<&std::path::Path> {
|
||||||
self.find_option(|opt| match opt {
|
self.find_option(|v| unwrap_enum!(v, FuseOverlayOption::UpperDir))
|
||||||
FuseOverlayOption::UpperDir(dir) => Some(dir.as_path()),
|
.map(PathBuf::as_path)
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||||
self.find_option(|opt| match opt {
|
self.find_option(|v| unwrap_enum!(v, FuseOverlayOption::WorkDir))
|
||||||
FuseOverlayOption::WorkDir(dir) => Some(dir.as_path()),
|
.map(PathBuf::as_path)
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mountpoint for FuseOverlay {
|
|
||||||
fn options(&self) -> impl Iterator<Item = impl Display> {
|
|
||||||
self.options.as_iter()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,6 +313,7 @@ impl FileSystem for FuseOverlay {
|
||||||
fn mount(&mut self) -> std::io::Result<()> {
|
fn mount(&mut self) -> std::io::Result<()> {
|
||||||
Command::new("fuse-overlayfs")
|
Command::new("fuse-overlayfs")
|
||||||
.arg(self.options.to_string())
|
.arg(self.options.to_string())
|
||||||
|
.arg(&self.mountpoint)
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -482,7 +322,7 @@ impl FileSystem for FuseOverlay {
|
||||||
fn unmount(&mut self) -> std::io::Result<()> {
|
fn unmount(&mut self) -> std::io::Result<()> {
|
||||||
Command::new("fusermount")
|
Command::new("fusermount")
|
||||||
.arg("-u")
|
.arg("-u")
|
||||||
.arg(&self.mount_target)
|
.arg(&self.mountpoint)
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -495,11 +335,11 @@ impl Display for FuseOverlay {
|
||||||
|
|
||||||
// INFO: this is to get rid of the extraneous space if
|
// INFO: this is to get rid of the extraneous space if
|
||||||
// self.options is empty
|
// self.options is empty
|
||||||
if self.options.len() > 0 {
|
if !self.options.is_empty() {
|
||||||
write!(f, " {}", self.options)?;
|
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");
|
assert_eq!(basic.to_string(), "fuse-overlayfs /mnt");
|
||||||
|
|
||||||
let mut with_options = FuseOverlay::new("~/.config");
|
let mut with_options = FuseOverlay::new("~/.config");
|
||||||
with_options.push_options([
|
with_options.extend_options([
|
||||||
FuseOverlayOption::NoAcl,
|
FuseOverlayOption::NoAcl,
|
||||||
LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
|
LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
|
||||||
]);
|
]);
|
||||||
|
@ -617,7 +457,7 @@ mod tests {
|
||||||
fn mountpoint_trait_impl() {
|
fn mountpoint_trait_impl() {
|
||||||
let mut all = FuseOverlay::new("/mnt");
|
let mut all = FuseOverlay::new("/mnt");
|
||||||
|
|
||||||
all.push_options([
|
all.extend_options([
|
||||||
LowerDirs::from_iter(["/doesnt", "/matter"]).into(),
|
LowerDirs::from_iter(["/doesnt", "/matter"]).into(),
|
||||||
FuseOverlayOption::UpperDir("/upper".into()),
|
FuseOverlayOption::UpperDir("/upper".into()),
|
||||||
FuseOverlayOption::WorkDir("/work".into()),
|
FuseOverlayOption::WorkDir("/work".into()),
|
||||||
|
|
|
@ -1,7 +1,115 @@
|
||||||
pub mod fuse_overlay;
|
pub mod fuse_overlay;
|
||||||
pub mod 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 {
|
pub trait Stack {
|
||||||
fn lower_dirs(&self) -> impl Iterator<Item = &Path>;
|
fn lower_dirs(&self) -> impl Iterator<Item = &Path>;
|
||||||
|
|
|
@ -1,10 +1,46 @@
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::iter;
|
|
||||||
use std::path::PathBuf;
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum RedirectDir {
|
pub enum RedirectDir {
|
||||||
|
@ -14,6 +50,35 @@ pub enum RedirectDir {
|
||||||
Off,
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Uuid {
|
pub enum Uuid {
|
||||||
On,
|
On,
|
||||||
|
@ -22,6 +87,35 @@ pub enum Uuid {
|
||||||
Off,
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Verity {
|
pub enum Verity {
|
||||||
On,
|
On,
|
||||||
|
@ -29,6 +123,33 @@ pub enum Verity {
|
||||||
Off,
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Xino {
|
pub enum Xino {
|
||||||
On,
|
On,
|
||||||
|
@ -36,89 +157,133 @@ pub enum Xino {
|
||||||
Off,
|
Off,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl FromStr for Xino {
|
||||||
pub enum OverlayOptions {
|
type Err = FromStrError;
|
||||||
Index(bool),
|
|
||||||
Metacopy(bool),
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
NfsExport(bool),
|
match s.to_lowercase().as_str() {
|
||||||
RedirectDir(RedirectDir),
|
"on" => Ok(Self::On),
|
||||||
UserXAttr,
|
"auto" => Ok(Self::Auto),
|
||||||
Uuid(Uuid),
|
"off" => Ok(Self::Off),
|
||||||
Verity(Verity),
|
s => Err(FromStrError(s.to_string())),
|
||||||
Volatile,
|
}
|
||||||
Xino(Xino),
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for OverlayOptions {
|
impl Display for Xino {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
OverlayOptions::Index(enabled) => match enabled {
|
Self::On => "on",
|
||||||
true => "index=on",
|
Self::Auto => "Auto",
|
||||||
false => "index=off",
|
Self::Off => "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",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Overlay {
|
pub enum OverlayOption {
|
||||||
mount_target: PathBuf,
|
Index(Switch),
|
||||||
options: Vec<OverlayOptions>,
|
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 {
|
impl Stack for Overlay {
|
||||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||||
todo!();
|
let result = self.find_option(|v| unwrap_enum!(v, OverlayOption::LowerDir));
|
||||||
iter::empty()
|
|
||||||
|
match result {
|
||||||
|
Some(dirs) => dirs.into_iter(),
|
||||||
|
None => Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
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> {
|
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 {
|
impl FileSystem for Overlay {
|
||||||
fn options(&self) -> impl Iterator<Item = impl Display> {
|
fn mount(&mut self) -> std::io::Result<()> {
|
||||||
self.options.as_iter()
|
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 macros;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub fn add(left: u64, right: u64) -> u64 {
|
pub mod prelude {
|
||||||
left + right
|
pub use crate::fs::mount::*;
|
||||||
}
|
pub use crate::fs::stackable::fuse_overlay::*;
|
||||||
|
pub use crate::fs::stackable::overlay::*;
|
||||||
#[cfg(test)]
|
pub use crate::fs::stackable::*;
|
||||||
mod tests {
|
pub use crate::fs::*;
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = add(2, 2);
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
macro_rules! impl_from_variants {
|
||||||
($enum_type:ident, $($variant:ident($inner_type:ty)),*) => {
|
($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_ty;
|
||||||
pub(crate) use impl_from_variants;
|
pub(crate) use impl_from_variants;
|
||||||
|
pub(crate) use impl_fromstr;
|
||||||
pub(crate) use impl_wrapper_err;
|
pub(crate) use impl_wrapper_err;
|
||||||
pub(crate) use impl_wrapper_err_for;
|
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 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> {
|
pub trait MapWhileRefExt<'a, I: 'a> {
|
||||||
fn map_while_ref<F>(&'a mut self, f: F) -> MapWhileRef<'a, I, F>;
|
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)
|
(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