Skip to main content

mountos_native/
error.rs

1use std::{fmt, io};
2
3/// Errors returned by the client.
4#[derive(Debug)]
5pub enum Error {
6  /// The server answered with an error frame. `code` is a POSIX name such as ENOENT.
7  Server { code: String, message: String },
8  /// The peer broke the frame or message contract.
9  Protocol(String),
10  /// The socket failed.
11  Io(io::Error),
12  /// A call did not finish within `Options::call_timeout`. On a pipelining connection only this
13  /// call fails and the late reply is dropped. Otherwise the connection was dropped, so a late
14  /// reply cannot reach another call, and its open files and watches are gone. The code is
15  /// `DEADLINE_EXCEEDED` and the `io::ErrorKind` is `TimedOut`.
16  Timeout(String),
17  /// A request is larger than the frame limits. Nothing was sent and the connection is intact.
18  TooLarge(String),
19  /// A directory helper hit its bound: `read_dir_all` found more entries than its limit, or
20  /// `walk` queued more pending directories than its limit. The message names the limit.
21  TooManyEntries(String),
22  /// A write that covers more than one frame failed after `written` bytes were stored.
23  /// `source` is the failure. `code` and the `io::ErrorKind` come from `source`.
24  Partial { written: usize, source: Box<Error> },
25}
26
27pub type Result<T> = std::result::Result<T, Error>;
28
29impl Error {
30  /// Server error code, if this is a server error. A timeout has the code `DEADLINE_EXCEEDED`.
31  /// A partial write has the code of its cause.
32  pub fn code(&self) -> Option<&str> {
33    match self {
34      Error::Server { code, .. } => Some(code),
35      Error::Timeout(_) => Some("DEADLINE_EXCEEDED"),
36      Error::Partial { source, .. } => source.code(),
37      _ => None,
38    }
39  }
40
41  /// Copies the error. An I/O error keeps its kind and text.
42  pub(crate) fn dup(&self) -> Error {
43    match self {
44      Error::Server { code, message } => Error::Server {
45        code: code.clone(),
46        message: message.clone(),
47      },
48      Error::Protocol(m) => Error::Protocol(m.clone()),
49      Error::Io(e) => Error::Io(io::Error::new(e.kind(), e.to_string())),
50      Error::Timeout(m) => Error::Timeout(m.clone()),
51      Error::TooLarge(m) => Error::TooLarge(m.clone()),
52      Error::TooManyEntries(m) => Error::TooManyEntries(m.clone()),
53      Error::Partial { written, source } => Error::Partial {
54        written: *written,
55        source: Box::new(source.dup()),
56      },
57    }
58  }
59
60  pub(crate) fn server(code: &str, message: &str) -> Error {
61    Error::Server {
62      code: code.into(),
63      message: message.into(),
64    }
65  }
66}
67
68impl fmt::Display for Error {
69  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70    match self {
71      Error::Server { code, message } if message.is_empty() => write!(f, "{code}"),
72      Error::Server { code, message } => write!(f, "{code}: {message}"),
73      Error::Protocol(m) => write!(f, "protocol error: {m}"),
74      Error::Io(e) => write!(f, "io error: {e}"),
75      Error::Timeout(m) => write!(f, "DEADLINE_EXCEEDED: {m}"),
76      Error::TooLarge(m) => write!(f, "request too large: {m}"),
77      Error::TooManyEntries(m) => write!(f, "too many entries: {m}"),
78      Error::Partial { written, source } => write!(f, "partial write of {written} bytes: {source}"),
79    }
80  }
81}
82
83impl std::error::Error for Error {
84  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85    match self {
86      Error::Io(e) => Some(e),
87      Error::Partial { source, .. } => Some(source.as_ref()),
88      _ => None,
89    }
90  }
91}
92
93impl From<io::Error> for Error {
94  fn from(e: io::Error) -> Error {
95    Error::Io(e)
96  }
97}
98
99impl From<Error> for io::Error {
100  fn from(e: Error) -> io::Error {
101    let kind = match &e {
102      Error::TooLarge(_) | Error::TooManyEntries(_) => io::ErrorKind::InvalidInput,
103      e => e.code().map_or(io::ErrorKind::Other, kind_for_code),
104    };
105    match e {
106      Error::Io(e) => e,
107      e => io::Error::new(kind, e),
108    }
109  }
110}
111
112/// Maps a server error code to the closest `io::ErrorKind`. A code without a close kind maps to
113/// `Other`. The code string stays available through `Error::code`.
114pub(crate) fn kind_for_code(code: &str) -> io::ErrorKind {
115  use io::ErrorKind as K;
116  match code {
117    "ENOENT" => K::NotFound,
118    "EACCES" | "EPERM" => K::PermissionDenied,
119    "EEXIST" => K::AlreadyExists,
120    "EINVAL" => K::InvalidInput,
121    "ENOTDIR" => K::NotADirectory,
122    "EISDIR" => K::IsADirectory,
123    "ENOTEMPTY" => K::DirectoryNotEmpty,
124    "EROFS" => K::ReadOnlyFilesystem,
125    "ENOSPC" => K::StorageFull,
126    "EFBIG" => K::FileTooLarge,
127    "EXDEV" => K::CrossesDevices,
128    "EMLINK" => K::TooManyLinks,
129    "EBUSY" => K::ResourceBusy,
130    "ENAMETOOLONG" => K::InvalidFilename,
131    "EDQUOT" => K::QuotaExceeded,
132    "ESTALE" => K::StaleNetworkFileHandle,
133    "EAGAIN" => K::WouldBlock,
134    "UNSUPPORTED_OPERATION" | "UNSUPPORTED_VERSION" => K::Unsupported,
135    "DEADLINE_EXCEEDED" => K::TimedOut,
136    _ => K::Other,
137  }
138}
139
140#[cfg(test)]
141mod tests {
142  use super::*;
143  use io::ErrorKind as K;
144
145  #[test]
146  fn every_spec_code_maps() {
147    let want = [
148      ("ENOENT", K::NotFound),
149      ("EACCES", K::PermissionDenied),
150      ("EPERM", K::PermissionDenied),
151      ("EEXIST", K::AlreadyExists),
152      ("EINVAL", K::InvalidInput),
153      ("ENOTDIR", K::NotADirectory),
154      ("EISDIR", K::IsADirectory),
155      ("ENOTEMPTY", K::DirectoryNotEmpty),
156      ("EROFS", K::ReadOnlyFilesystem),
157      ("ENOSPC", K::StorageFull),
158      ("EFBIG", K::FileTooLarge),
159      ("EXDEV", K::CrossesDevices),
160      ("EMLINK", K::TooManyLinks),
161      ("EBUSY", K::ResourceBusy),
162      ("ENAMETOOLONG", K::InvalidFilename),
163      ("EDQUOT", K::QuotaExceeded),
164      ("ESTALE", K::StaleNetworkFileHandle),
165      ("EAGAIN", K::WouldBlock),
166      ("UNSUPPORTED_OPERATION", K::Unsupported),
167      ("UNSUPPORTED_VERSION", K::Unsupported),
168      ("DEADLINE_EXCEEDED", K::TimedOut),
169      ("EIO", K::Other),
170      ("EBADF", K::Other),
171      ("ELOOP", K::Other),
172      ("EUNKNOWN", K::Other),
173    ];
174    for (code, kind) in want {
175      let e: io::Error = Error::server(code, "m").into();
176      assert_eq!(e.kind(), kind, "{code}");
177    }
178  }
179
180  #[test]
181  fn code_survives_conversion() {
182    let e: io::Error = Error::server("ENOSPC", "disk full").into();
183    let inner = e.get_ref().unwrap().downcast_ref::<Error>().unwrap();
184    assert_eq!(inner.code(), Some("ENOSPC"));
185  }
186
187  #[test]
188  fn timeout_too_large_and_partial_convert() {
189    let e: io::Error = Error::Timeout("t".into()).into();
190    assert_eq!(e.kind(), K::TimedOut);
191    assert_eq!(Error::Timeout("t".into()).code(), Some("DEADLINE_EXCEEDED"));
192    let e: io::Error = Error::TooLarge("x".into()).into();
193    assert_eq!(e.kind(), K::InvalidInput);
194    let p = Error::Partial {
195      written: 5,
196      source: Box::new(Error::server("ENOSPC", "full")),
197    };
198    assert_eq!(p.code(), Some("ENOSPC"));
199    let e: io::Error = p.dup().into();
200    assert_eq!(e.kind(), K::StorageFull);
201  }
202
203  #[test]
204  fn non_server_errors_keep_other_kind() {
205    let e: io::Error = Error::Protocol("x".into()).into();
206    assert_eq!(e.kind(), K::Other);
207  }
208}