ghee_lang/
xattr.rs

1use std::{
2    ffi::{OsStr, OsString},
3    fmt::{self, Display, Formatter},
4    string::ToString,
5};
6
7use anyhow::Result;
8use clap::{builder::TypedValueParser, Arg, Command};
9use nom::{
10    branch::alt,
11    bytes::complete::{tag, take_while1},
12    character::{complete::char, is_alphanumeric},
13    sequence::separated_pair,
14    IResult,
15};
16use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
17use thiserror::Error;
18
19#[derive(Error, Debug)]
20pub enum XattrErr {
21    #[error("Attempted to initialize Xattr with unapproved namespace {0}")]
22    IllegalNamespace(String),
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
26pub enum Namespace {
27    Security,
28    System,
29    Trusted,
30    User,
31}
32
33impl Namespace {
34    pub fn len(&self) -> usize {
35        match self {
36            Self::Security => 8,
37            Self::System => 6,
38            Self::Trusted => 7,
39            Self::User => 4,
40        }
41    }
42}
43
44impl Into<String> for Namespace {
45    fn into(self) -> String {
46        let s: &str = self.into();
47        String::from(s)
48    }
49}
50impl Into<&'static str> for Namespace {
51    fn into(self) -> &'static str {
52        match self {
53            Self::Security => "security",
54            Self::System => "system",
55            Self::Trusted => "trusted",
56            Self::User => "user",
57        }
58    }
59}
60
61#[derive(Error, Debug)]
62pub enum NamespaceErr {
63    #[error("Illegal xattr namespace {0}")]
64    IllegalNamespace(String),
65}
66
67impl TryFrom<String> for Namespace {
68    type Error = anyhow::Error;
69    fn try_from(s: String) -> Result<Self, Self::Error> {
70        Self::try_from(s.as_str())
71    }
72}
73
74impl TryFrom<&str> for Namespace {
75    type Error = anyhow::Error;
76    fn try_from(s: &str) -> Result<Self, Self::Error> {
77        match s {
78            "security" => Ok(Self::Security),
79            "system" => Ok(Self::System),
80            "trusted" => Ok(Self::Trusted),
81            "user" => Ok(Self::User),
82            x => Err(NamespaceErr::IllegalNamespace(x.to_string()).into()),
83        }
84    }
85}
86
87impl Display for Namespace {
88    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
89        let s: String = self.clone().into();
90        write!(f, "{}", s)
91    }
92}
93
94#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
95pub struct Xattr {
96    pub namespace: Namespace,
97    pub attr: String,
98}
99
100impl Xattr {
101    pub fn new<S: ToString>(namespace: Namespace, attr: S) -> Self {
102        Self {
103            namespace,
104            attr: attr.to_string(),
105        }
106    }
107
108    pub fn to_osstring(&self) -> OsString {
109        OsString::from(self.to_string())
110    }
111
112    pub fn to_string(&self) -> String {
113        format!("{}.{}", self.namespace, self.attr)
114    }
115
116    pub fn len(&self) -> usize {
117        self.namespace.len() + self.attr.len() + 1
118    }
119
120    pub fn to_minimal_string(&self) -> String {
121        if self.namespace == Namespace::User {
122            self.attr.clone()
123        } else {
124            self.to_string()
125        }
126    }
127
128    pub fn minimal_len(&self) -> usize {
129        if self.namespace == Namespace::User {
130            self.attr.len()
131        } else {
132            self.len()
133        }
134    }
135
136    /** Produce a new Xattr with a new attr component.
137     *
138     * For example, Xattr::from("user.pizza").sub("dough") would yield `user.pizza.dough`.
139     */
140    pub fn sub<S: ToString>(&self, sub_xattr: S) -> Xattr {
141        let sub = sub_xattr.to_string();
142
143        let attr = format!("{}.{}", self.attr, sub);
144
145        Self::new(self.namespace, attr)
146    }
147}
148
149impl From<&str> for Xattr {
150    fn from(s: &str) -> Self {
151        parse_xattr(s.as_bytes()).unwrap().1
152    }
153}
154
155impl From<&[u8]> for Xattr {
156    fn from(b: &[u8]) -> Self {
157        parse_xattr(b).unwrap().1
158    }
159}
160
161impl Display for Xattr {
162    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
163        write!(f, "{}.{}", self.namespace, self.attr)
164    }
165}
166
167pub struct StringVisitor;
168impl<'de> Visitor<'de> for StringVisitor {
169    type Value = String;
170    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
171        formatter.write_str("a string")
172    }
173
174    fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
175        Ok(v)
176    }
177
178    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
179        Ok(v.to_string())
180    }
181}
182
183impl Serialize for Xattr {
184    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
185    where
186        S: Serializer,
187    {
188        serializer.serialize_str(self.to_minimal_string().as_str())
189    }
190}
191
192impl<'a> Deserialize<'a> for Xattr {
193    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194    where
195        D: Deserializer<'a>,
196    {
197        let s = deserializer.deserialize_string(StringVisitor).unwrap();
198        let parts: Vec<&str> = s.split(".").collect();
199        if parts.len() == 1 {
200            Ok(Self::new(Namespace::User, parts[0]))
201        } else {
202            match Namespace::try_from(parts[0]) {
203                Ok(ns) => Ok(Self::new(ns, parts[1])),
204                Err(_) => Ok(Self::new(Namespace::User, s)),
205            }
206        }
207    }
208}
209
210fn is_alphanumeric_or_dot_or_dash(c: u8) -> bool {
211    is_alphanumeric(c) || c == '.' as u8 || c == '-' as u8
212}
213
214fn parse_xattr_complete(i: &[u8]) -> IResult<&[u8], Xattr> {
215    separated_pair(
216        alt((tag("trusted"), tag("security"), tag("system"), tag("user"))),
217        char('.'),
218        take_while1(is_alphanumeric_or_dot_or_dash),
219    )(i)
220    .map(|(i, (namespace, attr))| {
221        (
222            i,
223            Xattr::new(
224                Namespace::try_from(
225                    String::from_utf8(namespace.iter().copied().collect())
226                        .unwrap_or_else(|e| panic!("Namespace was not UTF-8: {}", e)),
227                )
228                .unwrap(),
229                String::from_utf8(attr.iter().copied().collect())
230                    .unwrap_or_else(|e| panic!("Attribute was not UTF-8: {}", e)),
231            ),
232        )
233    })
234}
235
236fn parse_xattr_bare(i: &[u8]) -> IResult<&[u8], Xattr> {
237    take_while1(is_alphanumeric_or_dot_or_dash)(i).map(|(i, attr)| {
238        (
239            i,
240            Xattr::new(Namespace::User, String::from_utf8(attr.to_vec()).unwrap()),
241        )
242    })
243}
244
245pub fn parse_xattr(i: &[u8]) -> IResult<&[u8], Xattr> {
246    alt((parse_xattr_complete, parse_xattr_bare))(i)
247}
248
249#[test]
250fn test_parse_xattr() {
251    assert!(parse_xattr(b"").is_err());
252    assert_eq!(
253        parse_xattr(b"abc"),
254        Ok((b"".as_slice(), Xattr::new(Namespace::User, "abc")))
255    );
256
257    assert_eq!(
258        parse_xattr(b"system.abc"),
259        Ok((b"".as_slice(), Xattr::new(Namespace::System, "abc")))
260    );
261    assert_eq!(
262        parse_xattr(b"blah.blah.blah"),
263        Ok((
264            b"".as_slice(),
265            Xattr::new(Namespace::User, "blah.blah.blah")
266        ))
267    );
268    assert_eq!(
269        parse_xattr(b"user.ghee.test.init"),
270        Ok((
271            b"".as_slice(),
272            Xattr::new(Namespace::User, "ghee.test.init")
273        ))
274    );
275    assert_eq!(
276        parse_xattr(b"test2"),
277        Ok((b"".as_slice(), Xattr::new(Namespace::User, "test2")))
278    );
279    assert_eq!(
280        parse_xattr(b"most-recent-snapshot"),
281        Ok((
282            b"".as_slice(),
283            Xattr::new(Namespace::User, "most-recent-snapshot")
284        ))
285    );
286}
287#[derive(Clone)]
288pub struct XattrParser;
289impl TypedValueParser for XattrParser {
290    type Value = Xattr;
291
292    fn parse_ref(
293        &self,
294        _cmd: &Command,
295        _arg: Option<&Arg>,
296        value: &OsStr,
297    ) -> Result<Self::Value, clap::error::Error<clap::error::RichFormatter>> {
298        parse_xattr(value.to_string_lossy().as_bytes())
299            .map(|(_remainder, predicate)| predicate)
300            .map_err(|e| {
301                clap::error::Error::raw(
302                    clap::error::ErrorKind::InvalidValue,
303                    format!("Malformed xattr: {}\n", e),
304                )
305            })
306    }
307}