Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul Nix's error types #1446

Merged
merged 4 commits into from Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,16 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1457](https://github.com/nix-rust/nix/pull/1457))

### Changed
- `ptsname_r` now returns a lossily-converted string in the event of bad UTF,
just like `ptsname`.
([#1446](https://github.com/nix-rust/nix/pull/1446))
- Nix's error type is now a simple wrapper around the platform's Errno. This
means it is now `Into<std::io::Error>`. It's also `Clone`, `Copy`, `Eq`, and
has a small fixed size. It also requires less typing. For example, the old
enum variant `nix::Error::Sys(nix::errno::Errno::EINVAL)` is now simply
`nix::Error::EINVAL`.
([#1446](https://github.com/nix-rust/nix/pull/1446))

### Fixed
### Removed

Expand Down
2 changes: 1 addition & 1 deletion src/dir.rs
Expand Up @@ -84,7 +84,7 @@ impl AsRawFd for Dir {
impl Drop for Dir {
fn drop(&mut self) {
let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
if !std::thread::panicking() && e == Err(Error::Sys(Errno::EBADF)) {
if !std::thread::panicking() && e == Err(Error::from(Errno::EBADF)) {
panic!("Closing an invalid file descriptor!");
};
}
Expand Down
20 changes: 16 additions & 4 deletions src/env.rs
@@ -1,12 +1,24 @@
use cfg_if::cfg_if;
use crate::{Error, Result};
use std::fmt;

/// Indicates that [`clearenv`] failed for some unknown reason
#[derive(Clone, Copy, Debug)]
pub struct ClearEnvError;

impl fmt::Display for ClearEnvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "clearenv failed")
}
}

impl std::error::Error for ClearEnvError {}

/// Clear the environment of all name-value pairs.
///
/// On platforms where libc provides `clearenv()`, it will be used. libc's
/// `clearenv()` is documented to return an error code but not set errno; if the
/// return value indicates a failure, this function will return
/// `Error::UnsupportedOperation`.
/// [`ClearEnvError`].
///
/// On platforms where libc does not provide `clearenv()`, a fallback
/// implementation will be used that iterates over all environment variables and
Expand All @@ -25,7 +37,7 @@ use crate::{Error, Result};
/// `environ` is currently held. The latter is not an issue if the only other
/// environment access in the program is via `std::env`, but the requirement on
/// thread safety must still be upheld.
pub unsafe fn clearenv() -> Result<()> {
pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> {
let ret;
cfg_if! {
if #[cfg(any(target_os = "fuchsia",
Expand All @@ -48,6 +60,6 @@ pub unsafe fn clearenv() -> Result<()> {
if ret == 0 {
Ok(())
} else {
Err(Error::UnsupportedOperation)
Err(ClearEnvError)
}
}
64 changes: 63 additions & 1 deletion src/errno.rs
@@ -1,5 +1,6 @@
use cfg_if::cfg_if;
use libc::{c_int, c_void};
use std::convert::TryFrom;
use std::{fmt, io, error};
use crate::{Error, Result};

Expand Down Expand Up @@ -48,6 +49,42 @@ pub fn errno() -> i32 {
}

impl Errno {
/// Convert this `Error` to an [`Errno`](enum.Errno.html).
///
/// # Example
///
/// ```
/// # use nix::Error;
/// # use nix::errno::Errno;
/// let e = Error::from(Errno::EPERM);
/// assert_eq!(Some(Errno::EPERM), e.as_errno());
/// ```
#[deprecated(
since = "0.22.0",
note = "It's a no-op now; just delete it."
)]
pub fn as_errno(self) -> Option<Self> {
Some(self)
}

/// Create a nix Error from a given errno
#[deprecated(
since = "0.22.0",
note = "It's a no-op now; just delete it."
)]
pub fn from_errno(errno: Errno) -> Error {
Error::from(errno)
}

/// Create a new invalid argument error (`EINVAL`)
#[deprecated(
since = "0.22.0",
note = "Use Errno::EINVAL instead"
)]
pub fn invalid_argument() -> Error {
Errno::EINVAL
}

pub fn last() -> Self {
last()
}
Expand All @@ -68,11 +105,26 @@ impl Errno {
/// should not be used when `-1` is not the errno sentinel value.
pub fn result<S: ErrnoSentinel + PartialEq<S>>(value: S) -> Result<S> {
if value == S::sentinel() {
Err(Error::Sys(Self::last()))
Err(Self::last())
} else {
Ok(value)
}
}

/// Backwards compatibility hack for Nix <= 0.21.0 users
///
/// In older versions of Nix, `Error::Sys` was an enum variant. Now it's a
/// function, which is compatible with most of the former use cases of the
/// enum variant. But you should use `Error(Errno::...)` instead.
#[deprecated(
since = "0.22.0",
note = "Use Errno::... instead"
)]
#[allow(non_snake_case)]
#[inline]
pub fn Sys(errno: Errno) -> Error {
errno
}
}

/// The sentinel value indicates that a function failed and more detailed
Expand Down Expand Up @@ -115,6 +167,16 @@ impl From<Errno> for io::Error {
}
}

impl TryFrom<io::Error> for Errno {
type Error = io::Error;

fn try_from(ioerror: io::Error) -> std::result::Result<Self, io::Error> {
ioerror.raw_os_error()
.map(Errno::from_i32)
.ok_or(ioerror)
}
}

fn last() -> Errno {
Errno::from_i32(errno())
}
Expand Down
4 changes: 2 additions & 2 deletions src/fcntl.rs
Expand Up @@ -288,7 +288,7 @@ fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result
Some(next_size) => try_size = next_size,
// It's absurd that this would happen, but handle it sanely
// anyway.
None => break Err(super::Error::Sys(Errno::ENAMETOOLONG)),
None => break Err(super::Error::from(Errno::ENAMETOOLONG)),
}
}
}
Expand Down Expand Up @@ -646,6 +646,6 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu
match Errno::result(res) {
Err(err) => Err(err),
Ok(0) => Ok(()),
Ok(errno) => Err(crate::Error::Sys(Errno::from_i32(errno))),
Ok(errno) => Err(crate::Error::from(Errno::from_i32(errno))),
}
}
94 changes: 15 additions & 79 deletions src/lib.rs
Expand Up @@ -79,91 +79,27 @@ pub mod unistd;

use libc::{c_char, PATH_MAX};

use std::{error, fmt, ptr, result};
use std::{ptr, result};
use std::ffi::{CStr, OsStr};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};

use errno::Errno;

/// Nix Result Type
pub type Result<T> = result::Result<T, Error>;
pub type Result<T> = result::Result<T, Errno>;

/// Nix Error Type
/// Nix's main error type.
///
/// The nix error type provides a common way of dealing with
/// various system system/libc calls that might fail. Each
/// error has a corresponding errno (usually the one from the
/// underlying OS) to which it can be mapped in addition to
/// implementing other common traits.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Error {
Sys(Errno),
InvalidPath,
/// The operation involved a conversion to Rust's native String type, which failed because the
/// string did not contain all valid UTF-8.
InvalidUtf8,
/// The operation is not supported by Nix, in this instance either use the libc bindings or
/// consult the module documentation to see if there is a more appropriate interface available.
UnsupportedOperation,
}

impl Error {
/// Convert this `Error` to an [`Errno`](enum.Errno.html).
///
/// # Example
///
/// ```
/// # use nix::Error;
/// # use nix::errno::Errno;
/// let e = Error::from(Errno::EPERM);
/// assert_eq!(Some(Errno::EPERM), e.as_errno());
/// ```
pub fn as_errno(self) -> Option<Errno> {
if let Error::Sys(e) = self {
Some(e)
} else {
None
}
}

/// Create a nix Error from a given errno
pub fn from_errno(errno: Errno) -> Error {
Error::Sys(errno)
}

/// Get the current errno and convert it to a nix Error
pub fn last() -> Error {
Error::Sys(Errno::last())
}

/// Create a new invalid argument error (`EINVAL`)
pub fn invalid_argument() -> Error {
Error::Sys(Errno::EINVAL)
}

}

impl From<Errno> for Error {
fn from(errno: Errno) -> Error { Error::from_errno(errno) }
}

impl From<std::string::FromUtf8Error> for Error {
fn from(_: std::string::FromUtf8Error) -> Error { Error::InvalidUtf8 }
}

impl error::Error for Error {}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::InvalidPath => write!(f, "Invalid path"),
Error::InvalidUtf8 => write!(f, "Invalid UTF-8 string"),
Error::UnsupportedOperation => write!(f, "Unsupported Operation"),
Error::Sys(errno) => write!(f, "{:?}: {}", errno, errno.desc()),
}
}
}
/// It's a wrapper around Errno. As such, it's very interoperable with
/// [`std::io::Error`], but it has the advantages of:
/// * `Clone`
/// * `Copy`
/// * `Eq`
/// * Small size
/// * Represents all of the system's errnos, instead of just the most common
/// ones.
pub type Error = Errno;

pub trait NixPath {
fn is_empty(&self) -> bool;
Expand Down Expand Up @@ -217,7 +153,7 @@ impl NixPath for CStr {
where F: FnOnce(&CStr) -> T {
// Equivalence with the [u8] impl.
if self.len() >= PATH_MAX as usize {
return Err(Error::InvalidPath);
return Err(Error::from(Errno::ENAMETOOLONG))
}

Ok(f(self))
Expand All @@ -238,11 +174,11 @@ impl NixPath for [u8] {
let mut buf = [0u8; PATH_MAX as usize];

if self.len() >= PATH_MAX as usize {
return Err(Error::InvalidPath);
return Err(Error::from(Errno::ENAMETOOLONG))
}

match self.iter().position(|b| *b == 0) {
Some(_) => Err(Error::InvalidPath),
Some(_) => Err(Error::from(Errno::EINVAL)),
None => {
unsafe {
// TODO: Replace with bytes::copy_memory. rust-lang/rust#24028
Expand Down
15 changes: 8 additions & 7 deletions src/mount/bsd.rs
@@ -1,4 +1,5 @@
use crate::{
Error,
Errno,
NixPath,
Result,
Expand Down Expand Up @@ -99,7 +100,7 @@ libc_bitflags!(
/// by `nmount(2)`.
#[derive(Debug)]
pub struct NmountError {
errno: Errno,
errno: Error,
errmsg: Option<String>
}

Expand All @@ -109,14 +110,14 @@ impl NmountError {
self.errmsg.as_deref()
}

/// Returns the inner [`Errno`]
pub fn errno(&self) -> Errno {
/// Returns the inner [`Error`]
pub fn error(&self) -> Error {
self.errno
}

fn new(errno: Errno, errmsg: Option<&CStr>) -> Self {
fn new(error: Error, errmsg: Option<&CStr>) -> Self {
Self {
errno,
errno: error,
errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned)
}
}
Expand All @@ -136,7 +137,7 @@ impl fmt::Display for NmountError {

impl From<NmountError> for io::Error {
fn from(err: NmountError) -> Self {
io::Error::from_raw_os_error(err.errno as i32)
err.errno.into()
}
}

Expand Down Expand Up @@ -381,7 +382,7 @@ impl<'a> Nmount<'a> {
Some(CStr::from_bytes_with_nul(sl).unwrap())
}
};
Err(NmountError::new(error.as_errno().unwrap(), errmsg))
Err(NmountError::new(error.into(), errmsg))
}
}
}
Expand Down