Ghee/src/xattr.rs

197 lines
5 KiB
Rust

use std::{
ffi::{OsStr, OsString},
fmt::{Display, Formatter},
os::unix::prelude::OsStringExt,
};
use clap::{builder::TypedValueParser, Arg, Command};
use nom::{
branch::alt,
bytes::complete::{tag, take_while1},
character::complete::char,
character::is_alphabetic,
sequence::separated_pair,
IResult,
};
use serde::Serialize;
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
pub struct Xattr {
pub namespace: Vec<u8>,
pub attr: Vec<u8>,
all: Vec<u8>,
}
impl Xattr {
pub fn new(namespace: Vec<u8>, attr: Vec<u8>) -> Self {
let all: Vec<u8> = namespace
.iter()
.copied()
.chain(std::iter::once(b'.'))
.chain(attr.iter().copied())
.collect();
Self {
namespace,
attr,
all,
}
}
pub fn to_osstring(&self) -> OsString {
OsString::from_vec(self.all.clone())
}
}
// impl AsRef<OsStr> for Xattr {
// fn as_ref(&self) -> &OsStr {
// OsString::from_vec(self.all.clone()).as_os_str()
// }
// }
impl From<&str> for Xattr {
fn from(s: &str) -> Self {
parse_xattr(s.as_bytes()).unwrap().1
}
}
impl From<&[u8]> for Xattr {
fn from(b: &[u8]) -> Self {
parse_xattr(b).unwrap().1
}
}
impl Display for Xattr {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"{}.{}",
String::from_utf8_lossy(self.namespace.as_slice()),
String::from_utf8_lossy(self.attr.as_slice())
)
}
}
/// Parse the `namespace.attr` (or just bare `attr`) xattr names, filling in the `user` namespace
/// when none is provided.
// pub fn parse_xattr<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], Xattr, E> {
// pair(
// alt((tag(""), terminated(take_while(is_alphabetic), tag(".")))),
// take_while(is_alphabetic),
// )(i)
// .map(|(i, (namespace, attr))| {
// (
// i,
// Xattr {
// namespace: if namespace.is_empty() {
// b"user.".into_iter().copied().collect()
// } else {
// namespace.iter().copied().collect()
// },
// attr: attr.iter().copied().collect(),
// },
// )
// })
// }
fn is_alphabetic_or_dot(c: u8) -> bool {
is_alphabetic(c) || c == '.' as u8
}
fn parse_xattr_complete(i: &[u8]) -> IResult<&[u8], Xattr> {
separated_pair(
alt((tag("trusted"), tag("security"), tag("system"), tag("user"))),
char('.'),
take_while1(is_alphabetic_or_dot),
)(i)
.map(|(i, (namespace, attr))| {
(
i,
Xattr::new(
namespace.iter().copied().collect(),
attr.iter().copied().collect(),
),
)
})
}
fn parse_xattr_bare(i: &[u8]) -> IResult<&[u8], Xattr> {
take_while1(is_alphabetic_or_dot)(i).map(|(i, attr)| {
(
i,
Xattr::new(b"user".to_vec(), attr.iter().copied().collect()),
)
})
}
// pub fn parse_xattr(i: &[u8]) -> IResult<&[u8], Xattr> {
// pair(
// alt((tag(""), terminated(take_while(is_alphabetic), tag(".")))),
// take_while1(is_alphabetic),
// )(i)
// .map(|(i, (namespace, attr))| {
// (
// i,
// Xattr {
// namespace: if namespace.is_empty() {
// b"user".into_iter().copied().collect()
// } else {
// namespace.iter().copied().collect()
// },
// attr: attr.iter().copied().collect(),
// },
// )
// })
// }
pub fn parse_xattr(i: &[u8]) -> IResult<&[u8], Xattr> {
alt((parse_xattr_complete, parse_xattr_bare))(i)
}
#[test]
fn test_parse_xattr() {
assert!(parse_xattr(b"").is_err());
assert_eq!(
parse_xattr(b"abc"),
Ok((
b"".as_slice(),
Xattr::new(b"user".to_vec(), b"abc".to_vec())
))
);
assert_eq!(
parse_xattr(b"system.abc"),
Ok((
b"".as_slice(),
Xattr::new(b"system".to_vec(), b"abc".to_vec())
))
);
assert_eq!(
parse_xattr(b"blah.blah.blah"),
Ok((
b"".as_slice(),
Xattr::new(b"user".to_vec(), b"blah.blah.blah".to_vec())
))
);
}
#[derive(Clone)]
pub struct XattrParser;
impl TypedValueParser for XattrParser {
type Value = Xattr;
fn parse_ref(
&self,
_cmd: &Command,
_arg: Option<&Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::error::Error<clap::error::RichFormatter>> {
parse_xattr(value.to_string_lossy().as_bytes())
.map(|(_remainder, predicate)| predicate)
.map_err(|e| {
clap::error::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!("Malformed xattr: {}\n", e),
)
})
}
}