Skip to main content

use_oci_namespace/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Errors returned when namespace metadata is invalid.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum NamespaceError {
10    Empty,
11    UnknownKind,
12    InvalidPath,
13}
14
15impl fmt::Display for NamespaceError {
16    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::Empty => formatter.write_str("OCI namespace value cannot be empty"),
19            Self::UnknownKind => formatter.write_str("unknown OCI namespace kind"),
20            Self::InvalidPath => formatter.write_str("invalid OCI namespace path"),
21        }
22    }
23}
24
25impl Error for NamespaceError {}
26
27/// OCI/Linux namespace kind labels.
28#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum NamespaceKind {
30    Pid,
31    Network,
32    Mount,
33    Ipc,
34    Uts,
35    User,
36    Cgroup,
37}
38
39impl NamespaceKind {
40    /// Returns the stable namespace label.
41    #[must_use]
42    pub const fn as_str(self) -> &'static str {
43        match self {
44            Self::Pid => "pid",
45            Self::Network => "network",
46            Self::Mount => "mount",
47            Self::Ipc => "ipc",
48            Self::Uts => "uts",
49            Self::User => "user",
50            Self::Cgroup => "cgroup",
51        }
52    }
53}
54
55impl fmt::Display for NamespaceKind {
56    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57        formatter.write_str(self.as_str())
58    }
59}
60
61impl FromStr for NamespaceKind {
62    type Err = NamespaceError;
63
64    fn from_str(value: &str) -> Result<Self, Self::Err> {
65        match value.trim().to_ascii_lowercase().as_str() {
66            "pid" => Ok(Self::Pid),
67            "network" | "net" => Ok(Self::Network),
68            "mount" | "mnt" => Ok(Self::Mount),
69            "ipc" => Ok(Self::Ipc),
70            "uts" => Ok(Self::Uts),
71            "user" => Ok(Self::User),
72            "cgroup" => Ok(Self::Cgroup),
73            "" => Err(NamespaceError::Empty),
74            _ => Err(NamespaceError::UnknownKind),
75        }
76    }
77}
78
79/// A lexical namespace path marker.
80#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
81pub struct NamespacePath(String);
82
83impl NamespacePath {
84    /// Creates a namespace path marker without touching the filesystem.
85    pub fn new(value: impl AsRef<str>) -> Result<Self, NamespaceError> {
86        let trimmed = value.as_ref().trim();
87        if trimmed.is_empty() {
88            return Err(NamespaceError::Empty);
89        }
90        if trimmed
91            .bytes()
92            .any(|byte| byte.is_ascii_control() || byte == b'\0')
93        {
94            return Err(NamespaceError::InvalidPath);
95        }
96        Ok(Self(trimmed.to_string()))
97    }
98
99    /// Returns the path text.
100    #[must_use]
101    pub fn as_str(&self) -> &str {
102        &self.0
103    }
104}
105
106impl AsRef<str> for NamespacePath {
107    fn as_ref(&self) -> &str {
108        self.as_str()
109    }
110}
111
112impl fmt::Display for NamespacePath {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        formatter.write_str(self.as_str())
115    }
116}
117
118/// Namespace metadata without platform syscalls.
119#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
120pub struct Namespace {
121    kind: NamespaceKind,
122    path: Option<NamespacePath>,
123}
124
125impl Namespace {
126    /// Creates namespace metadata from a kind.
127    #[must_use]
128    pub const fn new(kind: NamespaceKind) -> Self {
129        Self { kind, path: None }
130    }
131
132    /// Adds a lexical namespace path.
133    #[must_use]
134    pub fn with_path(mut self, path: NamespacePath) -> Self {
135        self.path = Some(path);
136        self
137    }
138
139    /// Returns the namespace kind.
140    #[must_use]
141    pub const fn kind(&self) -> NamespaceKind {
142        self.kind
143    }
144
145    /// Returns the optional namespace path.
146    #[must_use]
147    pub const fn path(&self) -> Option<&NamespacePath> {
148        self.path.as_ref()
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::{Namespace, NamespaceError, NamespaceKind, NamespacePath};
155
156    #[test]
157    fn parses_namespace_kinds() -> Result<(), NamespaceError> {
158        assert_eq!("pid".parse::<NamespaceKind>()?, NamespaceKind::Pid);
159        assert_eq!("net".parse::<NamespaceKind>()?, NamespaceKind::Network);
160        assert_eq!("mnt".parse::<NamespaceKind>()?, NamespaceKind::Mount);
161        assert_eq!(
162            "bad".parse::<NamespaceKind>(),
163            Err(NamespaceError::UnknownKind)
164        );
165        Ok(())
166    }
167
168    #[test]
169    fn models_namespace_paths_lexically() -> Result<(), NamespaceError> {
170        let namespace =
171            Namespace::new(NamespaceKind::Pid).with_path(NamespacePath::new("/proc/self/ns/pid")?);
172
173        assert_eq!(namespace.kind(), NamespaceKind::Pid);
174        assert_eq!(
175            namespace.path().map(NamespacePath::as_str),
176            Some("/proc/self/ns/pid")
177        );
178        Ok(())
179    }
180}