1use std::{num::TryFromIntError, path::PathBuf};
2
3const MLOCK_HINT: &str = "\
4\n\nPossible fixes:\
5\n ulimit -l unlimited\
6\n setcap cap_ipc_lock+ep <binary>\
7\n # Kubernetes:\
8\n securityContext:\
9\n capabilities:\
10\n add: [\"IPC_LOCK\"]";
11
12#[derive(Debug, thiserror::Error)]
13pub enum MlockError {
14 #[error("{call} failed: permission denied (EPERM){}", MLOCK_HINT)]
15 PermissionDenied { call: &'static str },
16
17 #[error(
18 "{call} failed: cannot lock {len} bytes — RLIMIT_MEMLOCK too low (ENOMEM){}",
19 MLOCK_HINT
20 )]
21 OutOfMemory { call: &'static str, len: usize },
22
23 #[error("{call}: {source}")]
24 Other {
25 call: &'static str,
26 source: std::io::Error,
27 },
28}
29
30#[derive(Debug, thiserror::Error)]
31pub enum Error {
32 #[error("{context}: {source}")]
33 Io {
34 context: String,
35 source: std::io::Error,
36 },
37
38 #[error(transparent)]
39 Mlock(#[from] MlockError),
40
41 #[error("{0}")]
42 Syscall(#[from] nix::errno::Errno),
43
44 #[error("{0}")]
45 TryFromInt(#[from] TryFromIntError),
46
47 #[cfg(feature = "rayon")]
48 #[error("{0}")]
49 ThreadPool(#[from] rayon::ThreadPoolBuildError),
50
51 #[error("{path}: offset {offset} beyond file size {file_len}")]
52 OffsetBeyondFile {
53 path: PathBuf,
54 offset: u64,
55 file_len: u64,
56 },
57}
58
59impl Error {
60 pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
61 Self::Io {
62 context: context.into(),
63 source,
64 }
65 }
66}
67
68impl From<std::io::Error> for Error {
69 fn from(source: std::io::Error) -> Self {
70 Self::Io {
71 context: String::new(),
72 source,
73 }
74 }
75}
76
77pub type Result<T> = std::result::Result<T, Error>;
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_io_error_display_with_context() {
85 let err = Error::io(
86 "/tmp/test.dat",
87 std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"),
88 );
89 let msg = err.to_string();
90 assert!(msg.contains("/tmp/test.dat"), "msg: {msg}");
91 assert!(msg.contains("file not found"), "msg: {msg}");
92 }
93
94 #[test]
95 fn test_io_error_from_std() {
96 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
97 let err: Error = io_err.into();
98 assert!(matches!(err, Error::Io { .. }));
99 }
100
101 #[test]
102 fn test_syscall_error_from_errno() {
103 let err: Error = nix::errno::Errno::EBADF.into();
104 assert!(matches!(err, Error::Syscall(_)));
105 assert!(err.to_string().contains("EBADF"));
106 }
107
108 #[test]
109 fn test_offset_beyond_file_display() {
110 let err = Error::OffsetBeyondFile {
111 path: PathBuf::from("/data/big.bin"),
112 offset: 1000,
113 file_len: 500,
114 };
115 let msg = err.to_string();
116 assert!(msg.contains("/data/big.bin"), "msg: {msg}");
117 assert!(msg.contains("1000"), "msg: {msg}");
118 assert!(msg.contains("500"), "msg: {msg}");
119 }
120}