commit 2f1fc9ba909282b538a1f04320c0cf1123f951ae Author: rowan Date: Fri Feb 28 02:50:43 2025 -0600 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1c1e7db --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..01e4706 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/crates/char_enum/Cargo.lock b/crates/char_enum/Cargo.lock new file mode 100644 index 0000000..4bc5f61 --- /dev/null +++ b/crates/char_enum/Cargo.lock @@ -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" diff --git a/crates/char_enum/Cargo.toml b/crates/char_enum/Cargo.toml new file mode 100644 index 0000000..2d6ccfb --- /dev/null +++ b/crates/char_enum/Cargo.toml @@ -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 } + diff --git a/crates/char_enum/src/lib.rs b/crates/char_enum/src/lib.rs new file mode 100644 index 0000000..a380683 --- /dev/null +++ b/crates/char_enum/src/lib.rs @@ -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 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; +} + +pub trait ToChar { + fn to_char(&self) -> char; +} + +pub trait AsIter { + fn iter() -> impl Iterator; +} diff --git a/crates/char_enum_derive/Cargo.lock b/crates/char_enum_derive/Cargo.lock new file mode 100644 index 0000000..30c1a3e --- /dev/null +++ b/crates/char_enum_derive/Cargo.lock @@ -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" diff --git a/crates/char_enum_derive/Cargo.toml b/crates/char_enum_derive/Cargo.toml new file mode 100644 index 0000000..b5c5906 --- /dev/null +++ b/crates/char_enum_derive/Cargo.toml @@ -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" + diff --git a/crates/char_enum_derive/src/lib.rs b/crates/char_enum_derive/src/lib.rs new file mode 100644 index 0000000..499777c --- /dev/null +++ b/crates/char_enum_derive/src/lib.rs @@ -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 { + 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 { + 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 for #name { + fn iter() -> impl Iterator { + [#(#chars)*].into_iter() + } + } + }; + + TokenStream::from(expanded) +} diff --git a/src/fs/id_mapping.rs b/src/fs/id_mapping.rs new file mode 100644 index 0000000..0f18ef5 --- /dev/null +++ b/src/fs/id_mapping.rs @@ -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 for TypedIdRangeError { + fn from(value: FromStrError) -> Self { + Self(value.to_string()) + } +} + +impl From 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 { + 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 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 { + let parts: Vec<&str> = s.split(':').collect(); + + let len = parts.len(); + if len > 3 { + return Err(ParseIdRangeError::InvalidArguments(len)); + } + + let range: Vec = 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); + +impl FromStr for IdMapping { + type Err = ParseIdRangeError; + + fn from_str(s: &str) -> Result { + 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(()) + } +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000..68d5dba --- /dev/null +++ b/src/fs/mod.rs @@ -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; + + fn as_iter(&'a self) -> Self::IntoIter; +} + +impl<'a, T: 'a> AsIter<'a> for Vec { + 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; +} diff --git a/src/fs/mount.rs b/src/fs/mount.rs new file mode 100644 index 0000000..2c54db6 --- /dev/null +++ b/src/fs/mount.rs @@ -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, +} + +impl KeyValuePair { + pub fn new(name: impl Into, value: Option>) -> Self { + Self { + name: name.into(), + value: value.map(Into::into), + } + } + + pub fn into_tuple(self) -> (String, Option) { + (self.name, self.value) + } +} + +impl From for (String, Option) { + fn from(kvp: KeyValuePair) -> Self { + kvp.into_tuple() + } +} + +impl FromStr for KeyValuePair { + type Err = ParseKeyValuePairError; + + fn from_str(s: &str) -> Result { + 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); + +impl Deref for MountOptions { + type Target = Vec; + + 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 { + 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 { + 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 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 { + 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 { + 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 for MapUsers { + fn from(value: IdRange) -> Self { + Self::Uid(value) + } +} + +impl From for MapUsers { + fn from(value: PathBuf) -> Self { + Self::Namespace(value) + } +} + +impl FromStr for MapUsers { + type Err = ParseUserMapError; + + fn from_str(s: &str) -> Result { + 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 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 { + 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(Vec); + +impl FromStr for Options { + type Err = T::Err; + + fn from_str(s: &str) -> Result { + s.split(',') + .map(T::from_str) + .process_results(|iter| Options::from(iter.collect::>())) + } +} + +impl Display for Options { + 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 From> for Options { + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[derive(Debug)] +pub struct Sources(Options); + +impl FromStr for Sources { + type Err = ParseOptionsSourceError; + + fn from_str(s: &str) -> Result { + 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 for ParseUncheckedOptionsError { + fn from(_value: Infallible) -> Self { + Self + } +} + +#[derive(Debug)] +pub struct UncheckedOptions(Options); + +impl FromStr for UncheckedOptions { + type Err = ParseUncheckedOptionsError; + + fn from_str(s: &str) -> Result { + Ok(Self(Options::::from_str(s)?)) + } +} + +impl Display for UncheckedOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From 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> for UncheckedOptions { + fn from(value: Vec) -> Self { + Self(Options::from(value)) + } +} + +impl From> for UncheckedOptions { + fn from(value: Vec<&str>) -> Self { + value + .into_iter() + .map(String::from) + .collect::>() + .into() + } +} + +impl From for Options { + fn from(value: UncheckedOptions) -> Self { + value.0 + } +} + +impl From> for UncheckedOptions { + fn from(value: Options) -> 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 for ParseMountOptionError { + fn from(value: ParseDeviceIdTypeError) -> Self { + Self::DeviceId(value) + } +} + +impl From for ParseMountOptionError { + fn from(value: ParseUserMapError) -> Self { + Self::MapUsers(value) + } +} + +impl From for ParseMountOptionError { + fn from(value: ParseGidRangeError) -> Self { + Self::MapGroups(value) + } +} + +impl From for ParseMountOptionError { + fn from(value: ParseOptionsModeError) -> Self { + Self::OptionsMode(value) + } +} + +impl From for ParseMountOptionError { + fn from(_value: ParseUncheckedOptionsError) -> Self { + unreachable!() + } +} + +impl From 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), + NoMtab, + OptionsMode(OptionsMode), + OptionsSource(Options), + 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 { + 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), +} + +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 { + 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, + target: impl Into, + options: impl Into, + ) -> Self { + Self { + source: src.into(), + target: target.into(), + options: options.into(), + } + } + + pub fn exec(&self) -> std::io::Result { + 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()); + } +} diff --git a/src/fs/permission.rs b/src/fs/permission.rs new file mode 100644 index 0000000..ea70bfb --- /dev/null +++ b/src/fs/permission.rs @@ -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 for ParseModeError { + fn from(value: ParseIntError) -> Self { + Self(value.to_string()) + } +} + +impl From> for ParseModeError { + fn from(value: FromBitsError) -> 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); + +impl FromStr for ModeFlags { + type Err = ParseModeError; + + fn from_str(s: &str) -> Result { + 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(BitFlags); + +impl Default for Modes { + fn default() -> Self { + Modes(BitFlags::::default()) + } +} + +impl FromStr for Modes { + type Err = FromStrError; + + fn from_str(s: &str) -> Result { + let flags = s + .chars() + .map_while_ref(|c: &char| T::from_char(*c)) + .fold(BitFlags::default(), BitOr::bitor); + + Ok(Self(flags)) + } +} + +impl Display for Modes { + 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 for FromStrError { + fn from(value: ParseSymbolicArgsError) -> Self { + value.0 + } +} + +impl From for ParseSymbolicArgsError { + fn from(value: FromStrError) -> Self { + Self(value) + } +} + +#[derive(Clone, Debug)] +pub enum SymbolicArgs { + Group(Modes), + Mode(Modes), +} + +impl FromStr for SymbolicArgs { + type Err = ParseSymbolicArgsError; + + fn from_str(s: &str) -> Result { + match Modes::::from_str(s) { + Ok(perms) => Ok(Self::Mode(perms)), + Err(_) => match Modes::::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 for ParseSymbolicError { + fn from(value: ParseSymbolicArgsError) -> Self { + Self::Mode(value.to_string()) + } +} + +#[derive(Clone, Debug)] +pub struct SymbolicPermissions { + groups: Modes, + operator: Operator, + mode: SymbolicArgs, +} + +impl SymbolicPermissions { + pub fn new(groups: Modes, operator: Operator, mode: SymbolicArgs) -> Self { + Self { + groups, + operator, + mode, + } + } +} + +impl FromStr for SymbolicPermissions { + type Err = ParseSymbolicError; + + fn from_str(s: &str) -> Result { + 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 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 { + 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 for ParsePermissionsError { + fn from(value: ParseSymbolicError) -> Self { + Self::Symbolic(value) + } +} + +impl From 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 { + 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()); + } +} diff --git a/src/fs/stackable/fuse_overlay.rs b/src/fs/stackable/fuse_overlay.rs new file mode 100644 index 0000000..48944d0 --- /dev/null +++ b/src/fs/stackable/fuse_overlay.rs @@ -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, +} + +impl Stack for FuseOverlay { + fn lower_dirs(&self) -> impl Iterator { + 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 { + self.options.as_iter() + } +} + +impl FileSystem for FuseOverlay { + fn mount(&mut self) -> std::io::Result<()> { + todo!() + } + + fn unmount(&mut self) -> std::io::Result<()> { + todo!() + } +} diff --git a/src/fs/stackable/mod.rs b/src/fs/stackable/mod.rs new file mode 100644 index 0000000..b4981eb --- /dev/null +++ b/src/fs/stackable/mod.rs @@ -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; + fn upper_dir(&self) -> Option<&Path>; + fn work_dir(&self) -> Option<&Path>; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stackable { + lower: Vec, + upper: Option, + work: Option, +} + +impl Stackable { + pub fn new(lower: Vec) -> Self { + Self { + lower, + upper: None, + work: None, + } + } + + pub fn with_upper_dir(&mut self, upper: impl Into) -> &mut Self { + self.upper = Some(upper.into()); + self + } + + pub fn with_work_dir(&mut self, work: impl Into) -> &mut Self { + self.work = Some(work.into()); + self + } +} + +impl Stack for Stackable { + fn lower_dirs(&self) -> impl Iterator { + 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() + } +} diff --git a/src/fs/stackable/overlay.rs b/src/fs/stackable/overlay.rs new file mode 100644 index 0000000..b6a7b36 --- /dev/null +++ b/src/fs/stackable/overlay.rs @@ -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, +} + +impl Stack for Overlay { + fn lower_dirs(&self) -> impl Iterator { + 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 { + self.options.as_iter() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b3b52a9 --- /dev/null +++ b/src/lib.rs @@ -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); + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..d2dee23 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,43 @@ +use core::fmt::Debug; + +pub trait MapWhileRefExt<'a, I: 'a> { + fn map_while_ref(&'a mut self, f: F) -> MapWhileRef<'a, I, F>; +} + +impl<'a, I: Iterator + 'a> MapWhileRefExt<'a, I> for I { + fn map_while_ref(&mut self, f: F) -> MapWhileRef { + MapWhileRef { iter: self, f } + } +} + +#[derive(Debug)] +pub struct MapWhileRef<'a, I: 'a, F> { + iter: &'a mut I, + f: F, +} + +impl Iterator for MapWhileRef<'_, I, F> +where + I: Iterator + Clone, + F: FnMut(&I::Item) -> Result, +{ + type Item = U; + + fn next(&mut self) -> Option { + 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) { + (0, self.iter.size_hint().1) + } +}