duende_mlock/
error.rs

1//! Error types for memory locking operations.
2
3use std::fmt;
4
5/// Error type for memory locking operations.
6///
7/// Each variant includes remediation guidance in its `Display` implementation.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MlockError {
10    /// Permission denied (EPERM).
11    ///
12    /// The process lacks the `CAP_IPC_LOCK` capability.
13    ///
14    /// # Remediation
15    ///
16    /// - Run as root: `sudo ./daemon`
17    /// - Add capability: `sudo setcap cap_ipc_lock=+ep ./daemon`
18    /// - Docker: `docker run --cap-add=IPC_LOCK ...`
19    PermissionDenied,
20
21    /// Resource limit exceeded (ENOMEM).
22    ///
23    /// Either:
24    /// - `RLIMIT_MEMLOCK` is too low
25    /// - Insufficient physical memory available
26    /// - Kernel cannot allocate tracking structures
27    ///
28    /// # Remediation
29    ///
30    /// - Raise limit: `ulimit -l unlimited`
31    /// - Docker: `docker run --ulimit memlock=-1:-1 ...`
32    /// - Systemd: `LimitMEMLOCK=infinity` in unit file
33    ResourceLimit,
34
35    /// Invalid argument (EINVAL).
36    ///
37    /// Shouldn't occur with this library unless using experimental flags
38    /// on unsupported kernels.
39    InvalidArgument,
40
41    /// Operation would block (EAGAIN).
42    ///
43    /// Some pages could not be locked. Typically occurs on macOS.
44    WouldBlock,
45
46    /// Unknown error with errno value.
47    ///
48    /// Contains the raw errno for debugging.
49    Unknown(i32),
50}
51
52impl MlockError {
53    /// Create an error from a raw errno value.
54    #[must_use]
55    pub const fn from_errno(errno: i32) -> Self {
56        match errno {
57            libc::EPERM => Self::PermissionDenied,
58            libc::ENOMEM => Self::ResourceLimit,
59            libc::EINVAL => Self::InvalidArgument,
60            libc::EAGAIN => Self::WouldBlock,
61            _ => Self::Unknown(errno),
62        }
63    }
64
65    /// Get the raw errno value, if available.
66    #[must_use]
67    pub const fn errno(&self) -> Option<i32> {
68        match self {
69            Self::PermissionDenied => Some(libc::EPERM),
70            Self::ResourceLimit => Some(libc::ENOMEM),
71            Self::InvalidArgument => Some(libc::EINVAL),
72            Self::WouldBlock => Some(libc::EAGAIN),
73            Self::Unknown(e) => Some(*e),
74        }
75    }
76
77    /// Check if this error indicates a permission issue.
78    #[must_use]
79    pub const fn is_permission_error(&self) -> bool {
80        matches!(self, Self::PermissionDenied)
81    }
82
83    /// Check if this error indicates a resource limit issue.
84    #[must_use]
85    pub const fn is_resource_limit(&self) -> bool {
86        matches!(self, Self::ResourceLimit)
87    }
88}
89
90impl fmt::Display for MlockError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            Self::PermissionDenied => write!(
94                f,
95                "permission denied: need CAP_IPC_LOCK capability or root \
96                 (docker: --cap-add=IPC_LOCK)"
97            ),
98            Self::ResourceLimit => write!(
99                f,
100                "resource limit: RLIMIT_MEMLOCK too low or insufficient memory \
101                 (docker: --ulimit memlock=-1:-1)"
102            ),
103            Self::InvalidArgument => write!(f, "invalid argument: unsupported flags for this kernel"),
104            Self::WouldBlock => write!(f, "would block: some pages could not be locked"),
105            Self::Unknown(errno) => write!(f, "mlock failed with errno={errno}"),
106        }
107    }
108}
109
110impl std::error::Error for MlockError {}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_from_errno_eperm() {
118        let err = MlockError::from_errno(libc::EPERM);
119        assert_eq!(err, MlockError::PermissionDenied);
120        assert!(err.is_permission_error());
121        assert!(!err.is_resource_limit());
122    }
123
124    #[test]
125    fn test_from_errno_enomem() {
126        let err = MlockError::from_errno(libc::ENOMEM);
127        assert_eq!(err, MlockError::ResourceLimit);
128        assert!(!err.is_permission_error());
129        assert!(err.is_resource_limit());
130    }
131
132    #[test]
133    fn test_from_errno_einval() {
134        let err = MlockError::from_errno(libc::EINVAL);
135        assert_eq!(err, MlockError::InvalidArgument);
136        assert!(!err.is_permission_error());
137        assert!(!err.is_resource_limit());
138    }
139
140    #[test]
141    fn test_from_errno_eagain() {
142        let err = MlockError::from_errno(libc::EAGAIN);
143        assert_eq!(err, MlockError::WouldBlock);
144        assert!(!err.is_permission_error());
145        assert!(!err.is_resource_limit());
146    }
147
148    #[test]
149    fn test_from_errno_unknown() {
150        let err = MlockError::from_errno(999);
151        assert_eq!(err, MlockError::Unknown(999));
152        assert_eq!(err.errno(), Some(999));
153    }
154
155    #[test]
156    fn test_errno_all_variants() {
157        assert_eq!(MlockError::PermissionDenied.errno(), Some(libc::EPERM));
158        assert_eq!(MlockError::ResourceLimit.errno(), Some(libc::ENOMEM));
159        assert_eq!(MlockError::InvalidArgument.errno(), Some(libc::EINVAL));
160        assert_eq!(MlockError::WouldBlock.errno(), Some(libc::EAGAIN));
161        assert_eq!(MlockError::Unknown(42).errno(), Some(42));
162    }
163
164    #[test]
165    fn test_display_permission_denied() {
166        let err = MlockError::PermissionDenied;
167        let msg = format!("{err}");
168        assert!(msg.contains("CAP_IPC_LOCK"));
169        assert!(msg.contains("--cap-add=IPC_LOCK"));
170    }
171
172    #[test]
173    fn test_display_resource_limit() {
174        let err = MlockError::ResourceLimit;
175        let msg = format!("{err}");
176        assert!(msg.contains("RLIMIT_MEMLOCK"));
177        assert!(msg.contains("--ulimit memlock=-1:-1"));
178    }
179
180    #[test]
181    fn test_display_invalid_argument() {
182        let err = MlockError::InvalidArgument;
183        let msg = format!("{err}");
184        assert!(msg.contains("invalid argument"));
185    }
186
187    #[test]
188    fn test_display_would_block() {
189        let err = MlockError::WouldBlock;
190        let msg = format!("{err}");
191        assert!(msg.contains("would block"));
192    }
193
194    #[test]
195    fn test_display_unknown() {
196        let err = MlockError::Unknown(42);
197        let msg = format!("{err}");
198        assert!(msg.contains("errno=42"));
199    }
200
201    #[test]
202    fn test_error_trait() {
203        let err: &dyn std::error::Error = &MlockError::PermissionDenied;
204        // Verify Error trait is implemented
205        assert!(!err.to_string().is_empty());
206    }
207}