initial commit

This commit is contained in:
Rowan 2025-02-28 02:50:43 -06:00
commit 2f1fc9ba90
18 changed files with 2101 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

98
Cargo.lock generated Normal file
View 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
View 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
View 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"

View 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 }

View 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
View 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"

View 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"

View 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
View 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
View 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
View 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
View 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());
}
}

View 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
View 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
View 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
View 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
View 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)
}
}