diff --git a/proxmox-sys/Cargo.toml b/proxmox-sys/Cargo.toml index cbb4bcf4..aa59bc52 100644 --- a/proxmox-sys/Cargo.toml +++ b/proxmox-sys/Cargo.toml @@ -11,6 +11,7 @@ authors = [ failure = "0.1" lazy_static = "1.4" libc = "0.2" +nix = "0.16" proxmox-tools = { version = "0.1.1-dev.1", path = "../proxmox-tools" } # Docs should be able to reference the proxmox crate. diff --git a/proxmox-sys/src/error.rs b/proxmox-sys/src/error.rs new file mode 100644 index 00000000..3a265a32 --- /dev/null +++ b/proxmox-sys/src/error.rs @@ -0,0 +1,165 @@ +//! Helpers for `io::Error` and `nix::Error`. +//! +//! When dealing with low level functionality, the `nix` crate contains a lot of helpful +//! functionality. Unfortunately it also contains its own error type which doesn't mix all too well +//! with `std::io::Error`. Some of the common cases we want to deal with is checking for `EAGAIN`, +//! `EWOULDBLOCK` or `ENOENT` specifically. To do this easily, we add the `SysError` trait (rather +//! than a type), which is implemented for both these errors and allows checking for the most +//! common errors in a unified way. +//! +//! Another problem with the `nix` error type is that it has no equivalent to `ErrorKind::Other`. +//! Unfortunately this is more difficult to deal with, so for now, we consider `io::Error` to be +//! the more general error (and require an `into_io_error` implementation in `SysError`). +//! +//! See the `SysError` and `SysResult` traits for examples. + +use std::io; + +use nix::errno::Errno; +use nix::Error; + +/// Helper to convert non-system-errors into an `io::Error` or `io::ErrorKind::Other`. +/// +/// A more convenient way is to use the `io_format_err!` macro. +pub fn io_err_other(e: E) -> io::Error { + io::Error::new(std::io::ErrorKind::Other, e.to_string()) +} + +/// This trait should be implemented for error types which can represent system errors. Note that +/// it is discouraged to try to map non-system errors to with this trait, since users of this trait +/// assume there to be a relation between the error code and a previous system call. For instance, +/// `error.is_errno(Errno::EAGAIN)` can be used when implementing a reactor to drive async I/O. +/// +/// Usage examples: +/// +/// ``` +/// # use failure::{bail, Error}; +/// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode}; +/// +/// use proxmox_sys::error::SysError; +/// +/// # fn test() -> Result<(), Error> { +/// +/// match Dir::open(".", OFlag::O_RDONLY, Mode::empty()) { +/// Ok(_dir) => { +/// // Do something +/// } +/// Err(ref err) if err.not_found() => { +/// // Handle ENOENT specially +/// } +/// Err(err) => bail!("failed to open directory: {}", err), +/// } +/// +/// # Ok(()) +/// # } +/// ``` +pub trait SysError { + /// Check if this error is a specific error returned from a system call. + fn is_errno(&self, value: Errno) -> bool; + + /// Convert this error into a `std::io::Error`. This must use the correct `std::io::ErrorKind`, + /// so that for example `ErrorKind::WouldBlock` means that the previous system call failed with + /// `EAGAIN`. + fn into_io_error(self) -> io::Error; + + /// Convenience shortcut to check for `EAGAIN`. + #[inline] + fn would_block(&self) -> bool { + self.is_errno(Errno::EAGAIN) + } + + /// Convenience shortcut to check for `ENOENT`. + #[inline] + fn not_found(&self) -> bool { + self.is_errno(Errno::ENOENT) + } + + /// Convenience shortcut to check for `EEXIST`. + #[inline] + fn already_exists(&self) -> bool { + self.is_errno(Errno::EEXIST) + } +} + +impl SysError for io::Error { + #[inline] + fn is_errno(&self, value: Errno) -> bool { + self.raw_os_error() == Some(value as i32) + } + + #[inline] + fn into_io_error(self) -> io::Error { + self + } +} + +impl SysError for nix::Error { + #[inline] + fn is_errno(&self, value: Errno) -> bool { + *self == Error::Sys(value) + } + + #[inline] + fn into_io_error(self) -> io::Error { + match self { + Error::Sys(raw) => io::Error::from_raw_os_error(raw as _), + other => io::Error::new(io::ErrorKind::Other, other.to_string()), + } + } +} + +/// Convenience helper to map a `Result<_, nix::Error>` to a `Result<_, std::io::Error>` quickly. +/// +/// Usage example: +/// +/// ```no_run +/// # use std::os::unix::io::RawFd; +/// # use failure::{bail, Error}; +/// +/// use proxmox_sys::error::SysResult; +/// +/// struct MyReader(RawFd); +/// +/// impl std::io::Read for MyReader { +/// fn read(&mut self, buf: &mut [u8]) -> std::io::Result { +/// nix::unistd::read(self.0, buf).into_io_result() +/// } +/// +/// // ... rest +/// } +/// ``` +pub trait SysResult { + type Ok; + + fn into_io_result(self) -> io::Result; +} + +impl SysResult for Result { + type Ok = T; + + #[inline] + fn into_io_result(self) -> io::Result { + self.map_err(|e| e.into_io_error()) + } +} + +macro_rules! other_error { + ($err:ty) => { + impl SysResult for Result { + type Ok = T; + + #[inline] + fn into_io_result(self) -> io::Result { + self.map_err($crate::error::io_err_other) + } + } + }; +} + +other_error!(std::char::ParseCharError); +other_error!(std::net::AddrParseError); +other_error!(std::num::ParseFloatError); +other_error!(std::num::ParseIntError); +other_error!(std::str::ParseBoolError); +other_error!(std::str::Utf8Error); +other_error!(std::string::FromUtf8Error); diff --git a/proxmox-sys/src/lib.rs b/proxmox-sys/src/lib.rs index f690c6fe..10076345 100644 --- a/proxmox-sys/src/lib.rs +++ b/proxmox-sys/src/lib.rs @@ -2,4 +2,5 @@ pub mod macros; +pub mod error; pub mod linux;