initial commit
This commit is contained in:
commit
2f1fc9ba90
18 changed files with 2101 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target/
|
98
Cargo.lock
generated
Normal file
98
Cargo.lock
generated
Normal file
|
@ -0,0 +1,98 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "char_enum"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"char_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "char_enum_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "vahanafs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"char_enum",
|
||||
"enumflags2",
|
||||
"itertools",
|
||||
]
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "vahanafs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
char_enum = { version = "0.1.0", path = "crates/char_enum", features = ["derive"] }
|
||||
enumflags2 = "0.7.11"
|
||||
itertools = "0.14.0"
|
54
crates/char_enum/Cargo.lock
generated
Normal file
54
crates/char_enum/Cargo.lock
generated
Normal file
|
@ -0,0 +1,54 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "char_enum"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"char_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "char_enum_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
11
crates/char_enum/Cargo.toml
Normal file
11
crates/char_enum/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "char_enum"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
derive = ["dep:char_enum_derive"]
|
||||
|
||||
[dependencies]
|
||||
char_enum_derive = { version = "0.1.0", path = "../char_enum_derive", optional = true }
|
||||
|
65
crates/char_enum/src/lib.rs
Normal file
65
crates/char_enum/src/lib.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
#[cfg(feature = "derive")]
|
||||
pub use char_enum_derive;
|
||||
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FromCharError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl FromCharError {
|
||||
pub fn new(message: String) -> Self {
|
||||
Self { message }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FromCharError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FromCharError {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FromStrError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl FromStrError {
|
||||
pub fn new(message: String) -> Self {
|
||||
Self { message }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FromStrError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FromStrError {}
|
||||
|
||||
impl From<FromCharError> for FromStrError {
|
||||
fn from(value: FromCharError) -> Self {
|
||||
Self::new(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromChar
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
type Err;
|
||||
|
||||
fn from_char(c: char) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
pub trait ToChar {
|
||||
fn to_char(&self) -> char;
|
||||
}
|
||||
|
||||
pub trait AsIter<T> {
|
||||
fn iter() -> impl Iterator<Item = T>;
|
||||
}
|
47
crates/char_enum_derive/Cargo.lock
generated
Normal file
47
crates/char_enum_derive/Cargo.lock
generated
Normal file
|
@ -0,0 +1,47 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "char_enum_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
13
crates/char_enum_derive/Cargo.toml
Normal file
13
crates/char_enum_derive/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "char_enum_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.93"
|
||||
quote = "1.0.38"
|
||||
syn = "2.0.98"
|
||||
|
99
crates/char_enum_derive/src/lib.rs
Normal file
99
crates/char_enum_derive/src/lib.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput, Expr, Lit, Meta};
|
||||
|
||||
#[proc_macro_derive(FromChar, attributes(value))]
|
||||
pub fn as_char_derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = input.ident;
|
||||
|
||||
let variants = match input.data {
|
||||
syn::Data::Enum(data) => data.variants,
|
||||
_ => panic!("FromChar can only be derived for enums"),
|
||||
};
|
||||
|
||||
let variant_matches = variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let char_attr = variant
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("value"))
|
||||
.expect("Each variant must have a #[value = \"...\"] attribute");
|
||||
|
||||
let char_value = match &char_attr.meta {
|
||||
Meta::NameValue(meta) => match &meta.value {
|
||||
Expr::Lit(expr) => match &expr.lit {
|
||||
Lit::Str(str) => str.value(),
|
||||
Lit::Byte(byte) => byte.value().to_string(),
|
||||
Lit::Char(char) => char.value().to_string(),
|
||||
_ => panic!("#[value] attribute must have a string literal value"),
|
||||
},
|
||||
_ => panic!("#[value] attribute must have a string literal value"),
|
||||
},
|
||||
_ => panic!("Invalid #[value] attribute format"),
|
||||
};
|
||||
|
||||
let char_literal = syn::LitChar::new(
|
||||
char_value.chars().next().unwrap(),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
|
||||
(variant_name, char_literal)
|
||||
});
|
||||
|
||||
let from_char_variants = variant_matches.clone().map(|(variant, ch)| {
|
||||
quote! { #ch => Ok(#name::#variant), }
|
||||
});
|
||||
|
||||
let to_char_variants = variant_matches.clone().map(|(variant, ch)| {
|
||||
quote! { #name::#variant => #ch, }
|
||||
});
|
||||
|
||||
let chars = variant_matches.map(|(_, ch)| quote! { #ch, });
|
||||
|
||||
let expanded = quote! {
|
||||
impl char_enum::FromChar for #name {
|
||||
type Err = char_enum::FromCharError;
|
||||
|
||||
fn from_char(c: char) -> Result<Self, Self::Err> {
|
||||
match c {
|
||||
#(#from_char_variants)*
|
||||
ch => Err(char_enum::FromCharError::new(format!("{ch} is not a valid variant. expected one of: #(#chars)*")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for #name {
|
||||
type Err = char_enum::FromStrError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.chars().next() {
|
||||
Some(c) => Ok(Self::from_char(c)?),
|
||||
None => Err(char_enum::FromStrError::new(format!("{s} does not correspond to a valid #name variant"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl char_enum::ToChar for #name {
|
||||
fn to_char(&self) -> char {
|
||||
match self {
|
||||
#(#to_char_variants)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for #name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_char())
|
||||
}
|
||||
}
|
||||
|
||||
impl char_enum::AsIter<char> for #name {
|
||||
fn iter() -> impl Iterator<Item = char> {
|
||||
[#(#chars)*].into_iter()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
135
src/fs/id_mapping.rs
Normal file
135
src/fs/id_mapping.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
|
||||
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, FromChar)]
|
||||
pub enum IdKind {
|
||||
#[value = "u"]
|
||||
Uid,
|
||||
#[value = "g"]
|
||||
Gid,
|
||||
#[value = "b"]
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypedIdRangeError(String);
|
||||
|
||||
impl From<FromStrError> for TypedIdRangeError {
|
||||
fn from(value: FromStrError) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIdRangeError> for TypedIdRangeError {
|
||||
fn from(value: ParseIdRangeError) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TypedIdRange(IdKind, IdRange);
|
||||
|
||||
impl FromStr for TypedIdRange {
|
||||
type Err = TypedIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once(":") {
|
||||
Some((kind, rest)) => Ok(Self(IdKind::from_str(kind)?, IdRange::from_str(rest)?)),
|
||||
None => Err(FromStrError::new(s.to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TypedIdRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParseIdRangeError {
|
||||
InvalidArguments(usize),
|
||||
NotANumber(ParseIntError),
|
||||
}
|
||||
|
||||
impl Display for ParseIdRangeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ParseIdRangeError::InvalidArguments(args) => write!(
|
||||
f,
|
||||
"invalid number of arguments given (got {args}, expected 3)"
|
||||
),
|
||||
ParseIdRangeError::NotANumber(err) => {
|
||||
write!(f, "could not parse argument: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseIdRangeError {}
|
||||
|
||||
impl From<ParseIntError> for ParseIdRangeError {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::NotANumber(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct IdRange(usize, usize, usize);
|
||||
|
||||
impl FromStr for IdRange {
|
||||
type Err = ParseIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split(':').collect();
|
||||
|
||||
let len = parts.len();
|
||||
if len > 3 {
|
||||
return Err(ParseIdRangeError::InvalidArguments(len));
|
||||
}
|
||||
|
||||
let range: Vec<usize> = parts
|
||||
.iter()
|
||||
.map(|n| usize::from_str_radix(n, 10))
|
||||
.process_results(|iter| iter.collect())?;
|
||||
|
||||
Ok(Self(range[0], range[1], range[2]))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IdRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{}:{}", self.0, self.1, self.2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct IdMapping(Vec<IdRange>);
|
||||
|
||||
impl FromStr for IdMapping {
|
||||
type Err = ParseIdRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split(',')
|
||||
.map(|s| IdRange::from_str(s))
|
||||
.process_results(|iter| Self(iter.collect()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IdMapping {
|
||||
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}")?;
|
||||
|
||||
for item in iter {
|
||||
write!(f, ",{item}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
40
src/fs/mod.rs
Normal file
40
src/fs/mod.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
mod id_mapping;
|
||||
mod mount;
|
||||
mod permission;
|
||||
pub mod stackable;
|
||||
|
||||
use std::{fmt::Display, slice::Iter};
|
||||
|
||||
pub trait AsIter<'a> {
|
||||
type Item: 'a;
|
||||
type IntoIter: Iterator<Item = &'a Self::Item>;
|
||||
|
||||
fn as_iter(&'a self) -> Self::IntoIter;
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> AsIter<'a> for Vec<T> {
|
||||
type Item = T;
|
||||
type IntoIter = Iter<'a, T>;
|
||||
|
||||
fn as_iter(&'a self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> AsIter<'a> for &'a [T] {
|
||||
type Item = T;
|
||||
type IntoIter = Iter<'a, T>;
|
||||
|
||||
fn as_iter(&'a self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FileSystem {
|
||||
fn mount(&mut self) -> std::io::Result<()>;
|
||||
fn unmount(&mut self) -> std::io::Result<()>;
|
||||
}
|
||||
|
||||
pub trait Mountpoint {
|
||||
fn options(&self) -> impl Iterator<Item = impl Display>;
|
||||
}
|
818
src/fs/mount.rs
Normal file
818
src/fs/mount.rs
Normal file
|
@ -0,0 +1,818 @@
|
|||
use itertools::Itertools;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::{
|
||||
id_mapping::{IdRange, ParseIdRangeError},
|
||||
permission::Permissions,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParseKeyValuePairError;
|
||||
|
||||
impl Display for ParseKeyValuePairError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "could not parse key value pair")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseKeyValuePairError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeyValuePair {
|
||||
name: String,
|
||||
value: Option<String>,
|
||||
}
|
||||
|
||||
impl KeyValuePair {
|
||||
pub fn new(name: impl Into<String>, value: Option<impl Into<String>>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
value: value.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_tuple(self) -> (String, Option<String>) {
|
||||
(self.name, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyValuePair> for (String, Option<String>) {
|
||||
fn from(kvp: KeyValuePair) -> Self {
|
||||
kvp.into_tuple()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for KeyValuePair {
|
||||
type Err = ParseKeyValuePairError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once('=') {
|
||||
Some((name, value)) => Ok(Self::new(name, Some(value))),
|
||||
None => Ok(Self::new(s, None::<&str>)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KeyValuePair {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.value {
|
||||
Some(value) => write!(f, "{}={}", self.name, value),
|
||||
None => write!(f, "{}", self.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MountOptions(Vec<MountOption>);
|
||||
|
||||
impl Deref for MountOptions {
|
||||
type Target = Vec<MountOption>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MountOptions {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MountOptions {
|
||||
type Err = ParseMountOptionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split(',')
|
||||
.map(MountOption::from_str)
|
||||
.process_results(|it| MountOptions(it.collect()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MountOptions {
|
||||
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, Default)]
|
||||
pub enum Access {
|
||||
ReadOnly,
|
||||
#[default]
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
impl Display for Access {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Access::ReadOnly => "read-only",
|
||||
Access::ReadWrite => "read-write",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParseDeviceIdTypeError(String);
|
||||
|
||||
impl Display for ParseDeviceIdTypeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} is not a valid device type", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseDeviceIdTypeError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DeviceId {
|
||||
Label(String),
|
||||
Uuid(String),
|
||||
PartLabel(String),
|
||||
PartUuid(String),
|
||||
Id(String),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl FromStr for DeviceId {
|
||||
type Err = ParseDeviceIdTypeError;
|
||||
|
||||
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())),
|
||||
key => Err(ParseDeviceIdTypeError(key.to_string())),
|
||||
},
|
||||
None => Ok(Self::Other(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DeviceId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
DeviceId::Label(label) => format!("LABEL={label}"),
|
||||
DeviceId::Uuid(uuid) => format!("UUID={uuid}"),
|
||||
DeviceId::PartLabel(label) => format!("PARTLABEL={label}"),
|
||||
DeviceId::PartUuid(uuid) => format!("PARTUUID={uuid}"),
|
||||
DeviceId::Id(id) => format!("ID={id}"),
|
||||
DeviceId::Other(other) => other.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DeviceId {
|
||||
fn from(value: String) -> Self {
|
||||
Self::from_str(&value).unwrap_or_else(|_v| Self::Other(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for DeviceId {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::from_str(value).unwrap_or_else(|_v| Self::Other(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ParseOptionsModeError;
|
||||
|
||||
impl Display for ParseOptionsModeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "could not parse options mode")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseOptionsModeError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OptionsMode {
|
||||
Ignore,
|
||||
Append,
|
||||
Prepend,
|
||||
Replace,
|
||||
}
|
||||
|
||||
impl FromStr for OptionsMode {
|
||||
type Err = ParseOptionsModeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.trim() {
|
||||
"ignore" => Ok(Self::Ignore),
|
||||
"append" => Ok(Self::Append),
|
||||
"prepend" => Ok(Self::Prepend),
|
||||
"replace" => Ok(Self::Replace),
|
||||
_ => Err(ParseOptionsModeError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OptionsMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
OptionsMode::Ignore => "ignore",
|
||||
OptionsMode::Append => "append",
|
||||
OptionsMode::Prepend => "prepend",
|
||||
OptionsMode::Replace => "replace",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParseOptionsSourceError(String);
|
||||
|
||||
impl Display for ParseOptionsSourceError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "received an invalid option source: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseOptionsSourceError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OptionsSource {
|
||||
Mtab,
|
||||
Fstab,
|
||||
Disable,
|
||||
}
|
||||
|
||||
impl FromStr for OptionsSource {
|
||||
type Err = ParseOptionsSourceError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"mtab" => Ok(Self::Mtab),
|
||||
"fstab" => Ok(Self::Fstab),
|
||||
"disable" => Ok(Self::Disable),
|
||||
e => Err(ParseOptionsSourceError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OptionsSource {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
OptionsSource::Mtab => "mtab",
|
||||
OptionsSource::Fstab => "fstab",
|
||||
OptionsSource::Disable => "disable",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ParseUserMapError;
|
||||
|
||||
impl Display for ParseUserMapError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "could not parse user-map arguments")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseUserMapError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MapUsers {
|
||||
Uid(IdRange),
|
||||
Namespace(PathBuf),
|
||||
}
|
||||
|
||||
impl From<IdRange> for MapUsers {
|
||||
fn from(value: IdRange) -> Self {
|
||||
Self::Uid(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for MapUsers {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
Self::Namespace(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MapUsers {
|
||||
type Err = ParseUserMapError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match IdRange::from_str(s) {
|
||||
Ok(range) => Ok(Self::from(range)),
|
||||
Err(_) => match PathBuf::from_str(s) {
|
||||
Ok(path) => Ok(Self::from(path)),
|
||||
Err(_) => Err(ParseUserMapError),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MapUsers {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
MapUsers::Uid(range) => range.to_string(),
|
||||
MapUsers::Namespace(path) => path.to_string_lossy().to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseGidRangeError;
|
||||
|
||||
impl Display for ParseGidRangeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "could not parse gid map")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseGidRangeError {}
|
||||
|
||||
impl From<ParseIdRangeError> for ParseGidRangeError {
|
||||
fn from(_value: ParseIdRangeError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GidRange(IdRange);
|
||||
|
||||
impl FromStr for GidRange {
|
||||
type Err = ParseGidRangeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(IdRange::from_str(s).map(Self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GidRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options<T>(Vec<T>);
|
||||
|
||||
impl<T: FromStr> FromStr for Options<T> {
|
||||
type Err = T::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split(',')
|
||||
.map(T::from_str)
|
||||
.process_results(|iter| Options::from(iter.collect::<Vec<_>>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Display for Options<T> {
|
||||
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}")?;
|
||||
|
||||
for item in iter {
|
||||
write!(f, ",{item}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vec<T>> for Options<T> {
|
||||
fn from(value: Vec<T>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sources(Options<OptionsSource>);
|
||||
|
||||
impl FromStr for Sources {
|
||||
type Err = ParseOptionsSourceError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(Options::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseUncheckedOptionsError;
|
||||
|
||||
impl Display for ParseUncheckedOptionsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseUncheckedOptionsError {}
|
||||
|
||||
impl From<Infallible> for ParseUncheckedOptionsError {
|
||||
fn from(_value: Infallible) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UncheckedOptions(Options<String>);
|
||||
|
||||
impl FromStr for UncheckedOptions {
|
||||
type Err = ParseUncheckedOptionsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(Options::<String>::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UncheckedOptions {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for UncheckedOptions {
|
||||
fn from(value: String) -> Self {
|
||||
Self::from_str(&value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for UncheckedOptions {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::from_str(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
fn from(value: UncheckedOptions) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Options<String>> for UncheckedOptions {
|
||||
fn from(value: Options<String>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseMountOptionError {
|
||||
DeviceId(ParseDeviceIdTypeError),
|
||||
MapUsers(ParseUserMapError),
|
||||
MapGroups(ParseGidRangeError),
|
||||
OptionsMode(ParseOptionsModeError),
|
||||
OptionsSource(ParseOptionsSourceError),
|
||||
UnknownOption(String),
|
||||
}
|
||||
|
||||
impl Display for ParseMountOptionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::DeviceId(e) => write!(f, "{e}"),
|
||||
Self::MapUsers(e) => write!(f, "{e}"),
|
||||
Self::MapGroups(e) => write!(f, "{e}"),
|
||||
Self::OptionsMode(e) => write!(f, "{e}"),
|
||||
Self::OptionsSource(e) => write!(f, "{e}"),
|
||||
Self::UnknownOption(opt) => write!(f, "unknown option: {opt}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseMountOptionError {}
|
||||
|
||||
impl From<ParseDeviceIdTypeError> for ParseMountOptionError {
|
||||
fn from(value: ParseDeviceIdTypeError) -> Self {
|
||||
Self::DeviceId(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseUserMapError> for ParseMountOptionError {
|
||||
fn from(value: ParseUserMapError) -> Self {
|
||||
Self::MapUsers(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseGidRangeError> for ParseMountOptionError {
|
||||
fn from(value: ParseGidRangeError) -> Self {
|
||||
Self::MapGroups(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseOptionsModeError> for ParseMountOptionError {
|
||||
fn from(value: ParseOptionsModeError) -> Self {
|
||||
Self::OptionsMode(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseUncheckedOptionsError> for ParseMountOptionError {
|
||||
fn from(_value: ParseUncheckedOptionsError) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseOptionsSourceError> for ParseMountOptionError {
|
||||
fn from(value: ParseOptionsSourceError) -> Self {
|
||||
Self::OptionsSource(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MountOption {
|
||||
Label(DeviceId),
|
||||
FsType(String),
|
||||
All,
|
||||
NoCanonicalize,
|
||||
Fake,
|
||||
Fork,
|
||||
Fstab(String),
|
||||
InternalOnly,
|
||||
ShowLabels,
|
||||
MapGroups(GidRange),
|
||||
MapUsers(MapUsers),
|
||||
MakeDir(Option<Permissions>),
|
||||
NoMtab,
|
||||
OptionsMode(OptionsMode),
|
||||
OptionsSource(Options<OptionsSource>),
|
||||
OptionsSourceForce,
|
||||
OnlyOnce,
|
||||
Options(UncheckedOptions),
|
||||
TestOptions(UncheckedOptions),
|
||||
Types(UncheckedOptions),
|
||||
Source(UncheckedOptions),
|
||||
Target(PathBuf),
|
||||
TargetPrefix(PathBuf),
|
||||
Verbose,
|
||||
Access(Access),
|
||||
Namespace(String),
|
||||
Operation(MountOperation),
|
||||
Subtree(Subtree),
|
||||
}
|
||||
|
||||
impl FromStr for MountOption {
|
||||
type Err = ParseMountOptionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.trim().trim_start_matches('-');
|
||||
|
||||
let option = match s.split_once(|delim| delim == ' ' || delim == '=') {
|
||||
Some((option, value)) => (option, Some(value)),
|
||||
None => (s, None),
|
||||
};
|
||||
|
||||
match option {
|
||||
("L" | "label", Some(id)) => Ok(Self::Label(DeviceId::from_str(id)?)),
|
||||
("t" | "types", Some(types)) => Ok(Self::FsType(types.to_string())),
|
||||
("a" | "all", None) => Ok(Self::All),
|
||||
("c" | "no-canonicalize", None) => Ok(Self::NoCanonicalize),
|
||||
("f" | "fake", None) => Ok(Self::Fake),
|
||||
("F" | "fork", None) => Ok(Self::Fork),
|
||||
("T" | "fstab", Some(path)) => Ok(Self::Fstab(path.into())),
|
||||
("i" | "internal-only", None) => Ok(Self::InternalOnly),
|
||||
("l" | "show-labels", None) => Ok(Self::ShowLabels),
|
||||
("map-groups", Some(map)) => Ok(Self::MapGroups(GidRange::from_str(map)?)),
|
||||
("map-users", Some(map)) => Ok(Self::MapUsers(MapUsers::from_str(map)?)),
|
||||
("m" | "mkdir", arg) => Ok(Self::MakeDir(
|
||||
arg.and_then(|arg| Permissions::from_str(arg).ok()),
|
||||
)),
|
||||
("n" | "no-mtab", None) => Ok(Self::NoMtab),
|
||||
("options-mode", Some(mode)) => Ok(Self::OptionsMode(OptionsMode::from_str(mode)?)),
|
||||
("options-source", Some(source)) => Ok(Self::OptionsSource(Options::from_str(source)?)),
|
||||
("options-source-force", None) => Ok(Self::OptionsSourceForce),
|
||||
("onlyonce", None) => Ok(Self::OnlyOnce),
|
||||
("o" | "options", Some(options)) => {
|
||||
Ok(Self::Options(UncheckedOptions::from_str(options)?))
|
||||
}
|
||||
("O" | "test-opts", Some(options)) => {
|
||||
Ok(Self::TestOptions(UncheckedOptions::from_str(options)?))
|
||||
}
|
||||
("r" | "read-only", None) => Ok(Self::Access(Access::ReadOnly)),
|
||||
("source", Some(src)) => Ok(Self::Source(UncheckedOptions::from_str(src)?)),
|
||||
("target", Some(target)) => Ok(Self::Target(target.into())),
|
||||
("target-prefix", Some(path)) => Ok(Self::TargetPrefix(path.into())),
|
||||
("w" | "rw" | "read-write", None) => Ok(Self::Access(Access::ReadWrite)),
|
||||
("N" | "namespace", Some(ns)) => Ok(Self::Namespace(ns.into())),
|
||||
("U" | "uuid", Some(uuid)) => Ok(Self::Label(DeviceId::Uuid(uuid.into()))),
|
||||
("B" | "bind", None) => Ok(Self::Operation(MountOperation::Bind)),
|
||||
("M" | "move", None) => Ok(Self::Operation(MountOperation::Move)),
|
||||
("R" | "rbind", None) => Ok(Self::Operation(MountOperation::RecursiveBind)),
|
||||
("make-shared", None) => Ok(Self::Subtree(Subtree::Shared)),
|
||||
("make-slave", None) => Ok(Self::Subtree(Subtree::Replica)),
|
||||
("make-private", None) => Ok(Self::Subtree(Subtree::Private)),
|
||||
("make-unbindable", None) => Ok(Self::Subtree(Subtree::Unbindable)),
|
||||
("make-rshared", None) => Ok(Self::Subtree(Subtree::Shared.recursive())),
|
||||
("make-rslave", None) => Ok(Self::Subtree(Subtree::Replica.recursive())),
|
||||
("make-rprivate", None) => Ok(Self::Subtree(Subtree::Private.recursive())),
|
||||
("make-runbindable", None) => Ok(Self::Subtree(Subtree::Unbindable.recursive())),
|
||||
(opt, _) => Err(ParseMountOptionError::UnknownOption(opt.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MountOption {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Label(id) => format!("--label {id}"),
|
||||
Self::FsType(t) => format!("--types {t}"),
|
||||
Self::All => "--all".to_string(),
|
||||
Self::NoCanonicalize => "--no-canonicalize".to_string(),
|
||||
Self::Fake => "--fake".to_string(),
|
||||
Self::Fork => "--fork".to_string(),
|
||||
Self::Fstab(path) => format!("--fstab {path}"),
|
||||
Self::InternalOnly => "--internal-only".to_string(),
|
||||
Self::ShowLabels => "--show-labels".to_string(),
|
||||
Self::MapGroups(range) => format!("--map-groups {range}"),
|
||||
Self::MapUsers(map) => format!("--map-users {map}"),
|
||||
Self::MakeDir(mode) => format!(
|
||||
"--mkdir {}",
|
||||
mode.clone()
|
||||
.map(|m| m.to_string())
|
||||
.unwrap_or_else(String::new)
|
||||
),
|
||||
Self::NoMtab => "--no-mtab".to_string(),
|
||||
Self::OptionsMode(mode) => format!("--options-mode {mode}"),
|
||||
Self::OptionsSource(source) => format!("--options-source {source}"),
|
||||
Self::OptionsSourceForce => "--options-source-force".to_string(),
|
||||
Self::OnlyOnce => "--onlyonce".to_string(),
|
||||
Self::Options(opts) => format!("--options {opts}"),
|
||||
Self::TestOptions(opts) => format!("--test-opts {opts}"),
|
||||
Self::Types(types) => format!("--types {types}"),
|
||||
Self::Source(src) => format!("--source {src}"),
|
||||
Self::Target(path) => format!("--target {}", path.to_string_lossy()),
|
||||
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::Operation(op) => format!("--{op}"),
|
||||
Self::Subtree(op) => format!("--make-{op}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Subtree {
|
||||
Shared,
|
||||
Replica,
|
||||
Private,
|
||||
Unbindable,
|
||||
Recursive(Box<Subtree>),
|
||||
}
|
||||
|
||||
impl Subtree {
|
||||
pub fn recursive(self) -> Self {
|
||||
Self::Recursive(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
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}"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseMountOperationError(String);
|
||||
|
||||
impl Display for ParseMountOperationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "could not parse operation: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseMountOperationError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MountOperation {
|
||||
Bind,
|
||||
RecursiveBind,
|
||||
Move,
|
||||
}
|
||||
|
||||
impl FromStr for MountOperation {
|
||||
type Err = ParseMountOperationError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"bind" => Ok(Self::Bind),
|
||||
"rbind" => Ok(Self::RecursiveBind),
|
||||
"move" => Ok(Self::Move),
|
||||
v => Err(ParseMountOperationError(v.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MountOperation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
MountOperation::Bind => "bind",
|
||||
MountOperation::RecursiveBind => "rbind",
|
||||
MountOperation::Move => "move",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mount {
|
||||
source: DeviceId,
|
||||
target: PathBuf,
|
||||
options: MountOptions,
|
||||
}
|
||||
|
||||
impl Mount {
|
||||
pub fn new(
|
||||
src: impl Into<DeviceId>,
|
||||
target: impl Into<PathBuf>,
|
||||
options: impl Into<MountOptions>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source: src.into(),
|
||||
target: target.into(),
|
||||
options: options.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(&self) -> std::io::Result<std::process::Output> {
|
||||
Command::new("mount")
|
||||
.arg(self.options.to_string())
|
||||
.arg(self.source.to_string())
|
||||
.arg(&self.target)
|
||||
.output()
|
||||
}
|
||||
|
||||
pub fn test(&self) -> Command {
|
||||
let mut cmd = Command::new("mount");
|
||||
cmd.arg(self.options.to_string())
|
||||
.arg(self.source.to_string())
|
||||
.arg(&self.target);
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Mount, MountOption, MountOptions, Options};
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut options = MountOptions::default();
|
||||
options.push(MountOption::Types(vec!["overlay"].into()));
|
||||
let mount = Mount::new("/test", "/target", options);
|
||||
println!("{:?}", mount.test());
|
||||
}
|
||||
}
|
399
src/fs/permission.rs
Normal file
399
src/fs/permission.rs
Normal file
|
@ -0,0 +1,399 @@
|
|||
use char_enum::{char_enum_derive::FromChar, AsIter, FromChar, FromStrError, ToChar};
|
||||
use enumflags2::{bitflags, BitFlag, FromBitsError};
|
||||
use std::{error::Error, fmt::Display, num::ParseIntError, ops::BitOr, str::FromStr};
|
||||
|
||||
use enumflags2::BitFlags;
|
||||
|
||||
use crate::utils::MapWhileRefExt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseModeError(String);
|
||||
|
||||
impl ParseModeError {
|
||||
pub fn from_bits(bits: &str) -> Self {
|
||||
Self(format!("invalid mode: `{bits}`"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseModeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseModeError {}
|
||||
|
||||
impl From<ParseIntError> for ParseModeError {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromBitsError<Mode>> for ParseModeError {
|
||||
fn from(value: FromBitsError<Mode>) -> Self {
|
||||
Self::from_bits(&value.invalid_bits().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[bitflags]
|
||||
#[repr(u16)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Mode {
|
||||
IXOTH = 0x0001,
|
||||
IWOTH = 0x0002,
|
||||
IROTH = 0x0004,
|
||||
IXGRP = 0x0010,
|
||||
IWGRP = 0x0020,
|
||||
IRGRP = 0x0040,
|
||||
IXUSR = 0x0100,
|
||||
IWUSR = 0x0200,
|
||||
IRUSR = 0x0400,
|
||||
ISVTX = 0x1000,
|
||||
ISGID = 0x2000,
|
||||
ISUID = 0x4000,
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ModeFlags(BitFlags<Mode>);
|
||||
|
||||
impl FromStr for ModeFlags {
|
||||
type Err = ParseModeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let bits = u16::from_str(s)?;
|
||||
|
||||
Ok(Self(BitFlags::from_bits(bits)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ModeFlags {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromChar)]
|
||||
pub enum Operator {
|
||||
#[value = "-"]
|
||||
Sub,
|
||||
#[value = "+"]
|
||||
Add,
|
||||
#[value = "="]
|
||||
Eq,
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
pub fn split(s: &str) -> Option<(&str, char, &str)> {
|
||||
let mut result = None;
|
||||
|
||||
for d in Operator::iter() {
|
||||
if let Some((a, b)) = s.split_once(d) {
|
||||
result = Some((a, d, b));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, FromChar)]
|
||||
pub enum SymbolicMode {
|
||||
#[value = 'r']
|
||||
Read,
|
||||
#[value = 'w']
|
||||
Write,
|
||||
#[value = 'x']
|
||||
Execute,
|
||||
#[value = 'X']
|
||||
Search,
|
||||
#[value = 's']
|
||||
SetId,
|
||||
#[value = 't']
|
||||
Sticky,
|
||||
}
|
||||
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, FromChar)]
|
||||
pub enum GroupId {
|
||||
#[value = 'u']
|
||||
User,
|
||||
#[value = 'g']
|
||||
Group,
|
||||
#[value = 'o']
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Modes<T: BitFlag>(BitFlags<T>);
|
||||
|
||||
impl<T: BitFlag> Default for Modes<T> {
|
||||
fn default() -> Self {
|
||||
Modes(BitFlags::<T>::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BitFlag + FromChar> FromStr for Modes<T> {
|
||||
type Err = FromStrError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let flags = s
|
||||
.chars()
|
||||
.map_while_ref(|c: &char| T::from_char(*c))
|
||||
.fold(BitFlags::default(), BitOr::bitor);
|
||||
|
||||
Ok(Self(flags))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BitFlag + Display> Display for Modes<T> {
|
||||
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}")?;
|
||||
|
||||
for item in iter {
|
||||
write!(f, "{item}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseSymbolicArgsError(FromStrError);
|
||||
|
||||
impl Display for ParseSymbolicArgsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseSymbolicArgsError {}
|
||||
|
||||
impl From<ParseSymbolicArgsError> for FromStrError {
|
||||
fn from(value: ParseSymbolicArgsError) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromStrError> for ParseSymbolicArgsError {
|
||||
fn from(value: FromStrError) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SymbolicArgs {
|
||||
Group(Modes<GroupId>),
|
||||
Mode(Modes<SymbolicMode>),
|
||||
}
|
||||
|
||||
impl FromStr for SymbolicArgs {
|
||||
type Err = ParseSymbolicArgsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match Modes::<SymbolicMode>::from_str(s) {
|
||||
Ok(perms) => Ok(Self::Mode(perms)),
|
||||
Err(_) => match Modes::<GroupId>::from_str(s) {
|
||||
Ok(perms) => Ok(Self::Group(perms)),
|
||||
Err(_) => Err(FromStrError::new(format!("{} is not a valid argument", s)).into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SymbolicArgs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SymbolicArgs::Group(modes) => write!(f, "{modes}"),
|
||||
SymbolicArgs::Mode(modes) => write!(f, "{modes}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ParseSymbolicError {
|
||||
Group(String),
|
||||
Operator(String),
|
||||
Mode(String),
|
||||
}
|
||||
|
||||
impl Display for ParseSymbolicError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Group(e) => e,
|
||||
Self::Operator(e) => e,
|
||||
Self::Mode(e) => e,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseSymbolicError {}
|
||||
|
||||
impl From<ParseSymbolicArgsError> for ParseSymbolicError {
|
||||
fn from(value: ParseSymbolicArgsError) -> Self {
|
||||
Self::Mode(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SymbolicPermissions {
|
||||
groups: Modes<GroupId>,
|
||||
operator: Operator,
|
||||
mode: SymbolicArgs,
|
||||
}
|
||||
|
||||
impl SymbolicPermissions {
|
||||
pub fn new(groups: Modes<GroupId>, operator: Operator, mode: SymbolicArgs) -> Self {
|
||||
Self {
|
||||
groups,
|
||||
operator,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SymbolicPermissions {
|
||||
type Err = ParseSymbolicError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match Operator::split(s) {
|
||||
Some((groups, operator, mode)) => {
|
||||
let groups =
|
||||
Modes::from_str(groups).map_err(|e| ParseSymbolicError::Group(e.to_string()));
|
||||
|
||||
let operator = Operator::from_char(operator)
|
||||
.map_err(|o| ParseSymbolicError::Operator(o.to_string()));
|
||||
|
||||
let mode = SymbolicArgs::from_str(mode)?;
|
||||
|
||||
Ok(Self::new(groups?, operator?, mode))
|
||||
}
|
||||
None => Err(ParseSymbolicError::Operator(format!(
|
||||
"received invalid permission syntax: {s}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SymbolicPermissions {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}{}{}", self.groups, self.operator, self.mode)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParseOctalError(String);
|
||||
|
||||
impl Display for ParseOctalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseOctalError {}
|
||||
|
||||
impl From<ParseModeError> for ParseOctalError {
|
||||
fn from(value: ParseModeError) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OctalPermissions(ModeFlags);
|
||||
|
||||
impl Display for OctalPermissions {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for OctalPermissions {
|
||||
type Err = ParseOctalError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(ModeFlags::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParsePermissionsError {
|
||||
Symbolic(ParseSymbolicError),
|
||||
Octal(ParseOctalError),
|
||||
}
|
||||
|
||||
impl Display for ParsePermissionsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ParsePermissionsError::Symbolic(e) => write!(f, "{e}"),
|
||||
ParsePermissionsError::Octal(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParsePermissionsError {}
|
||||
|
||||
impl From<ParseSymbolicError> for ParsePermissionsError {
|
||||
fn from(value: ParseSymbolicError) -> Self {
|
||||
Self::Symbolic(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseOctalError> for ParsePermissionsError {
|
||||
fn from(value: ParseOctalError) -> Self {
|
||||
Self::Octal(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Permissions {
|
||||
Symbolic(SymbolicPermissions),
|
||||
Octal(OctalPermissions),
|
||||
}
|
||||
|
||||
impl FromStr for Permissions {
|
||||
type Err = ParsePermissionsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(SymbolicPermissions::from_str(s)
|
||||
.map(Self::Symbolic)
|
||||
.or_else(|_| OctalPermissions::from_str(s).map(Self::Octal))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Permissions {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Permissions::Symbolic(v) => write!(f, "{v}"),
|
||||
Permissions::Octal(v) => write!(f, "{v}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::Permissions;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let a = Permissions::from_str("ug+w");
|
||||
println!("{}", a.unwrap());
|
||||
}
|
||||
}
|
80
src/fs/stackable/fuse_overlay.rs
Normal file
80
src/fs/stackable/fuse_overlay.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use crate::fs::{id_mapping::IdMapping, AsIter, FileSystem, Mountpoint};
|
||||
|
||||
use super::{Stack, Stackable};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum FuseOverlayOption {
|
||||
CloneFd,
|
||||
MaxIdleThreads(isize),
|
||||
MaxThreads(usize),
|
||||
AllowOther,
|
||||
AllowRoot,
|
||||
SquashToRoot,
|
||||
SquashToUid(usize),
|
||||
SquashToGid(usize),
|
||||
StaticNLink,
|
||||
NoAcl,
|
||||
UidMapping(IdMapping),
|
||||
GidMapping(IdMapping),
|
||||
}
|
||||
|
||||
impl Display for FuseOverlayOption {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
FuseOverlayOption::CloneFd => "clone_fd".to_string(),
|
||||
FuseOverlayOption::MaxIdleThreads(n) => format!("max_idle_threads={n}"),
|
||||
FuseOverlayOption::MaxThreads(n) => format!("max_threads={n}"),
|
||||
FuseOverlayOption::AllowOther => "allow_other".to_string(),
|
||||
FuseOverlayOption::AllowRoot => "allow_root".to_string(),
|
||||
FuseOverlayOption::SquashToRoot => "squash_to_root".to_string(),
|
||||
FuseOverlayOption::SquashToUid(uid) => format!("squash_to_uid={uid}"),
|
||||
FuseOverlayOption::SquashToGid(gid) => format!("squash_to_gid={gid}"),
|
||||
FuseOverlayOption::StaticNLink => "static_nlink".to_string(),
|
||||
FuseOverlayOption::NoAcl => "noacl".to_string(),
|
||||
FuseOverlayOption::UidMapping(map) => format!("uidmapping={map}"),
|
||||
FuseOverlayOption::GidMapping(map) => format!("gidmapping={map}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FuseOverlay {
|
||||
fs: Stackable,
|
||||
options: Vec<FuseOverlayOption>,
|
||||
}
|
||||
|
||||
impl Stack for FuseOverlay {
|
||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||
self.fs.lower_dirs()
|
||||
}
|
||||
|
||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
||||
self.fs.upper_dir()
|
||||
}
|
||||
|
||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||
self.fs.work_dir()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountpoint for FuseOverlay {
|
||||
fn options(&self) -> impl Iterator<Item = impl Display> {
|
||||
self.options.as_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystem for FuseOverlay {
|
||||
fn mount(&mut self) -> std::io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn unmount(&mut self) -> std::io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
51
src/fs/stackable/mod.rs
Normal file
51
src/fs/stackable/mod.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
pub mod fuse_overlay;
|
||||
pub mod overlay;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait Stack {
|
||||
fn lower_dirs(&self) -> impl Iterator<Item = &Path>;
|
||||
fn upper_dir(&self) -> Option<&Path>;
|
||||
fn work_dir(&self) -> Option<&Path>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Stackable {
|
||||
lower: Vec<PathBuf>,
|
||||
upper: Option<PathBuf>,
|
||||
work: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Stackable {
|
||||
pub fn new(lower: Vec<PathBuf>) -> Self {
|
||||
Self {
|
||||
lower,
|
||||
upper: None,
|
||||
work: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_upper_dir(&mut self, upper: impl Into<PathBuf>) -> &mut Self {
|
||||
self.upper = Some(upper.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_work_dir(&mut self, work: impl Into<PathBuf>) -> &mut Self {
|
||||
self.work = Some(work.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Stack for Stackable {
|
||||
fn lower_dirs(&self) -> impl Iterator<Item = &Path> {
|
||||
self.lower.iter().map(AsRef::as_ref)
|
||||
}
|
||||
|
||||
fn upper_dir(&self) -> Option<&Path> {
|
||||
self.upper.as_deref()
|
||||
}
|
||||
|
||||
fn work_dir(&self) -> Option<&Path> {
|
||||
self.work.as_deref()
|
||||
}
|
||||
}
|
121
src/fs/stackable/overlay.rs
Normal file
121
src/fs/stackable/overlay.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use crate::fs::{AsIter, Mountpoint};
|
||||
|
||||
use super::{Stack, Stackable};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum RedirectDir {
|
||||
On,
|
||||
Follow,
|
||||
NoFollow,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Uuid {
|
||||
On,
|
||||
Auto,
|
||||
Null,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Verity {
|
||||
On,
|
||||
Require,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Xino {
|
||||
On,
|
||||
Auto,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OverlayOptions {
|
||||
Index(bool),
|
||||
Metacopy(bool),
|
||||
NfsExport(bool),
|
||||
RedirectDir(RedirectDir),
|
||||
UserXAttr,
|
||||
Uuid(Uuid),
|
||||
Verity(Verity),
|
||||
Volatile,
|
||||
Xino(Xino),
|
||||
}
|
||||
|
||||
impl Display for OverlayOptions {
|
||||
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",
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Overlay {
|
||||
fs: Stackable,
|
||||
options: Vec<OverlayOptions>,
|
||||
}
|
||||
|
||||
impl Stack for Overlay {
|
||||
fn lower_dirs(&self) -> impl Iterator<Item = &std::path::Path> {
|
||||
self.fs.lower_dirs()
|
||||
}
|
||||
|
||||
fn upper_dir(&self) -> Option<&std::path::Path> {
|
||||
self.fs.upper_dir()
|
||||
}
|
||||
|
||||
fn work_dir(&self) -> Option<&std::path::Path> {
|
||||
self.fs.work_dir()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountpoint for Overlay {
|
||||
fn options(&self) -> impl Iterator<Item = impl Display> {
|
||||
self.options.as_iter()
|
||||
}
|
||||
}
|
17
src/lib.rs
Normal file
17
src/lib.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
pub mod fs;
|
||||
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);
|
||||
}
|
||||
}
|
43
src/utils.rs
Normal file
43
src/utils.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use core::fmt::Debug;
|
||||
|
||||
pub trait MapWhileRefExt<'a, I: 'a> {
|
||||
fn map_while_ref<F>(&'a mut self, f: F) -> MapWhileRef<'a, I, F>;
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator + 'a> MapWhileRefExt<'a, I> for I {
|
||||
fn map_while_ref<F>(&mut self, f: F) -> MapWhileRef<I, F> {
|
||||
MapWhileRef { iter: self, f }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MapWhileRef<'a, I: 'a, F> {
|
||||
iter: &'a mut I,
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<I, F, U, E> Iterator for MapWhileRef<'_, I, F>
|
||||
where
|
||||
I: Iterator + Clone,
|
||||
F: FnMut(&I::Item) -> Result<U, E>,
|
||||
{
|
||||
type Item = U;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let old = self.iter.clone();
|
||||
match self.iter.next() {
|
||||
None => None,
|
||||
Some(elt) => match (self.f)(&elt) {
|
||||
Ok(val) => Some(val),
|
||||
Err(_) => {
|
||||
*self.iter = old;
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, self.iter.size_hint().1)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue