Skip to content

Commit 6511d02

Browse files
committed
Overhaul Nix's error types
For many of Nix's consumers it be convenient to easily convert a Nix error into a std::io::Error. That's currently not possible because of the InvalidPath, InvalidUtf8, and UnsupportedOperation types that have no equivalent in std::io::Error. However, very few of Nix's public APIs actually return those unusual errors. So a more useful API would be for Nix's standard error type to implement Into<std::io::Error>. This commit makes Error a simple NewType around Errno. For most functions it's a drop-in replacement. There are only three exceptions: * clearenv now returns a bespoke error type. It was the only Nix function whose error couldn't be cleanly mapped onto an Errno. * sys::signal::signal now returns Error(Errno::ENOTSUP) instead of Error::UnsupportedOperation when the user passes an incompatible argument to `handler`. * When a NixPath exceeds PATH_MAX, it will now return Error(Errno::ENAMETOOLONG) instead of Error::InvalidPath. In the latter two cases there is now some abiguity about whether the error code was generated by Nix or by the OS. But I think the ambiguity is worth it for the sake of being able to implement Into<io::Error>. This commit also introduces Error::Sys() as a migration aid. Previously that as an enum variant. Now it's a function, but it will work in many of the same contexts as the original. Fixes #1155
1 parent f90033e commit 6511d02

38 files changed

+246
-187
lines changed

src/dir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl AsRawFd for Dir {
8484
impl Drop for Dir {
8585
fn drop(&mut self) {
8686
let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
87-
if !std::thread::panicking() && e == Err(Error::Sys(Errno::EBADF)) {
87+
if !std::thread::panicking() && e == Err(Error::from(Errno::EBADF)) {
8888
panic!("Closing an invalid file descriptor!");
8989
};
9090
}

src/env.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
use cfg_if::cfg_if;
2-
use crate::{Error, Result};
2+
use std::fmt;
3+
4+
/// Indicates that [`clearenv`] failed for some unknown reason
5+
#[derive(Clone, Copy, Debug)]
6+
pub struct ClearEnvError;
7+
8+
impl fmt::Display for ClearEnvError {
9+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10+
write!(f, "clearenv failed")
11+
}
12+
}
13+
14+
impl std::error::Error for ClearEnvError {}
315

416
/// Clear the environment of all name-value pairs.
517
///
618
/// On platforms where libc provides `clearenv()`, it will be used. libc's
719
/// `clearenv()` is documented to return an error code but not set errno; if the
820
/// return value indicates a failure, this function will return
9-
/// `Error::UnsupportedOperation`.
21+
/// [`ClearEnvError`].
1022
///
1123
/// On platforms where libc does not provide `clearenv()`, a fallback
1224
/// implementation will be used that iterates over all environment variables and
@@ -25,7 +37,7 @@ use crate::{Error, Result};
2537
/// `environ` is currently held. The latter is not an issue if the only other
2638
/// environment access in the program is via `std::env`, but the requirement on
2739
/// thread safety must still be upheld.
28-
pub unsafe fn clearenv() -> Result<()> {
40+
pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> {
2941
let ret;
3042
cfg_if! {
3143
if #[cfg(any(target_os = "fuchsia",
@@ -48,6 +60,6 @@ pub unsafe fn clearenv() -> Result<()> {
4860
if ret == 0 {
4961
Ok(())
5062
} else {
51-
Err(Error::UnsupportedOperation)
63+
Err(ClearEnvError)
5264
}
5365
}

src/errno.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,21 @@ impl Errno {
6464
clear()
6565
}
6666

67+
pub(crate) fn result2<S: ErrnoSentinel + PartialEq<S>>(value: S)
68+
-> std::result::Result<S, Self>
69+
{
70+
if value == S::sentinel() {
71+
Err(Self::last())
72+
} else {
73+
Ok(value)
74+
}
75+
}
76+
6777
/// Returns `Ok(value)` if it does not contain the sentinel value. This
6878
/// should not be used when `-1` is not the errno sentinel value.
6979
pub fn result<S: ErrnoSentinel + PartialEq<S>>(value: S) -> Result<S> {
7080
if value == S::sentinel() {
71-
Err(Error::Sys(Self::last()))
81+
Err(Error::from(Self::last()))
7282
} else {
7383
Ok(value)
7484
}

src/fcntl.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result
288288
Some(next_size) => try_size = next_size,
289289
// It's absurd that this would happen, but handle it sanely
290290
// anyway.
291-
None => break Err(super::Error::Sys(Errno::ENAMETOOLONG)),
291+
None => break Err(super::Error::from(Errno::ENAMETOOLONG)),
292292
}
293293
}
294294
}
@@ -646,6 +646,6 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu
646646
match Errno::result(res) {
647647
Err(err) => Err(err),
648648
Ok(0) => Ok(()),
649-
Ok(errno) => Err(crate::Error::Sys(Errno::from_i32(errno))),
649+
Ok(errno) => Err(crate::Error::from(Errno::from_i32(errno))),
650650
}
651651
}

src/lib.rs

Lines changed: 82 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -79,34 +79,29 @@ pub mod unistd;
7979

8080
use libc::{c_char, PATH_MAX};
8181

82-
use std::{error, fmt, ptr, result};
82+
use std::convert::TryFrom;
83+
use std::{error, fmt, io, ptr, result};
8384
use std::ffi::{CStr, OsStr};
8485
use std::os::unix::ffi::OsStrExt;
8586
use std::path::{Path, PathBuf};
8687

87-
use errno::Errno;
88+
use errno::{Errno, ErrnoSentinel};
8889

8990
/// Nix Result Type
9091
pub type Result<T> = result::Result<T, Error>;
9192

92-
/// Nix Error Type
93+
/// Nix's main error type.
9394
///
94-
/// The nix error type provides a common way of dealing with
95-
/// various system system/libc calls that might fail. Each
96-
/// error has a corresponding errno (usually the one from the
97-
/// underlying OS) to which it can be mapped in addition to
98-
/// implementing other common traits.
95+
/// It's a wrapper around Errno. As such, it's very interoperable with
96+
/// [`std::io::Error`], but it has the advantages of:
97+
/// * `Clone`
98+
/// * `Copy`
99+
/// * `Eq`
100+
/// * Small size
101+
/// * Represents all of the system's errnos, instead of just the most common
102+
/// ones.
99103
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100-
pub enum Error {
101-
Sys(Errno),
102-
InvalidPath,
103-
/// The operation involved a conversion to Rust's native String type, which failed because the
104-
/// string did not contain all valid UTF-8.
105-
InvalidUtf8,
106-
/// The operation is not supported by Nix, in this instance either use the libc bindings or
107-
/// consult the module documentation to see if there is a more appropriate interface available.
108-
UnsupportedOperation,
109-
}
104+
pub struct Error(pub Errno);
110105

111106
impl Error {
112107
/// Convert this `Error` to an [`Errno`](enum.Errno.html).
@@ -119,49 +114,95 @@ impl Error {
119114
/// let e = Error::from(Errno::EPERM);
120115
/// assert_eq!(Some(Errno::EPERM), e.as_errno());
121116
/// ```
117+
#[deprecated(
118+
since = "0.22.0",
119+
note = "Use Error::into<Errno> instead"
120+
)]
122121
pub fn as_errno(self) -> Option<Errno> {
123-
if let Error::Sys(e) = self {
124-
Some(e)
125-
} else {
126-
None
127-
}
122+
Some(self.0)
128123
}
129124

130125
/// Create a nix Error from a given errno
126+
#[deprecated(
127+
since = "0.22.0",
128+
note = "Use Error::from instead"
129+
)]
131130
pub fn from_errno(errno: Errno) -> Error {
132-
Error::Sys(errno)
131+
Error::from(errno)
133132
}
134133

135134
/// Get the current errno and convert it to a nix Error
136135
pub fn last() -> Error {
137-
Error::Sys(Errno::last())
136+
Error::from(Errno::last())
138137
}
139138

140139
/// Create a new invalid argument error (`EINVAL`)
140+
#[deprecated(
141+
since = "0.22.0",
142+
note = "Use Error::from(Errno::EINVAL) instead"
143+
)]
141144
pub fn invalid_argument() -> Error {
142-
Error::Sys(Errno::EINVAL)
145+
Error::from(Errno::EINVAL)
146+
}
147+
148+
/// Returns `Ok(value)` if it does not contain the sentinel value. This
149+
/// should not be used when `-1` is not the errno sentinel value.
150+
pub(crate) fn result<S: ErrnoSentinel + PartialEq<S>>(value: S)
151+
-> std::result::Result<S, Error>
152+
{
153+
Errno::result2(value).map_err(Self::from)
143154
}
144155

156+
/// Backwards compatibility hack for Nix <= 0.21.0 users
157+
///
158+
/// In older versions of Nix, `Error::Sys` was an enum variant. Now it's a
159+
/// function, which is compatible with most of the former use cases of the
160+
/// enum variant. But you should use `Error(Errno::...)` instead.
161+
#[deprecated(
162+
since = "0.22.0",
163+
note = "Use Error(Errno::...) instead"
164+
)]
165+
#[allow(non_snake_case)]
166+
#[inline]
167+
pub fn Sys(errno: Errno) -> Error {
168+
Error::from(errno)
169+
}
170+
}
171+
172+
impl fmt::Display for Error {
173+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174+
write!(f, "{:?}: {}", self.0, self.0.desc())
175+
}
145176
}
146177

178+
impl error::Error for Error {}
179+
147180
impl From<Errno> for Error {
148-
fn from(errno: Errno) -> Error { Error::from_errno(errno) }
181+
fn from(errno: Errno) -> Self {
182+
Self(errno)
183+
}
149184
}
150185

151-
impl From<std::string::FromUtf8Error> for Error {
152-
fn from(_: std::string::FromUtf8Error) -> Error { Error::InvalidUtf8 }
186+
impl From<Error> for Errno {
187+
fn from(error: Error) -> Self {
188+
error.0
189+
}
153190
}
154191

155-
impl error::Error for Error {}
192+
impl TryFrom<io::Error> for Error {
193+
type Error = io::Error;
156194

157-
impl fmt::Display for Error {
158-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159-
match *self {
160-
Error::InvalidPath => write!(f, "Invalid path"),
161-
Error::InvalidUtf8 => write!(f, "Invalid UTF-8 string"),
162-
Error::UnsupportedOperation => write!(f, "Unsupported Operation"),
163-
Error::Sys(errno) => write!(f, "{:?}: {}", errno, errno.desc()),
164-
}
195+
fn try_from(ioerror: io::Error) -> std::result::Result<Self, io::Error> {
196+
ioerror.raw_os_error()
197+
.map(Errno::from_i32)
198+
.map(Error::from)
199+
.ok_or(ioerror)
200+
}
201+
}
202+
203+
impl From<Error> for io::Error {
204+
fn from(error: Error) -> Self {
205+
Self::from_raw_os_error(error.0 as i32)
165206
}
166207
}
167208

@@ -217,7 +258,7 @@ impl NixPath for CStr {
217258
where F: FnOnce(&CStr) -> T {
218259
// Equivalence with the [u8] impl.
219260
if self.len() >= PATH_MAX as usize {
220-
return Err(Error::InvalidPath);
261+
return Err(Error::from(Errno::ENAMETOOLONG))
221262
}
222263

223264
Ok(f(self))
@@ -238,11 +279,11 @@ impl NixPath for [u8] {
238279
let mut buf = [0u8; PATH_MAX as usize];
239280

240281
if self.len() >= PATH_MAX as usize {
241-
return Err(Error::InvalidPath);
282+
return Err(Error::from(Errno::ENAMETOOLONG))
242283
}
243284

244285
match self.iter().position(|b| *b == 0) {
245-
Some(_) => Err(Error::InvalidPath),
286+
Some(_) => Err(Error::from(Errno::EINVAL)),
246287
None => {
247288
unsafe {
248289
// TODO: Replace with bytes::copy_memory. rust-lang/rust#24028

src/mount/bsd.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ impl fmt::Display for NmountError {
136136

137137
impl From<NmountError> for io::Error {
138138
fn from(err: NmountError) -> Self {
139-
io::Error::from_raw_os_error(err.errno as i32)
139+
err.errno.into()
140140
}
141141
}
142142

@@ -381,7 +381,7 @@ impl<'a> Nmount<'a> {
381381
Some(CStr::from_bytes_with_nul(sl).unwrap())
382382
}
383383
};
384-
Err(NmountError::new(error.as_errno().unwrap(), errmsg))
384+
Err(NmountError::new(error.into(), errmsg))
385385
}
386386
}
387387
}

src/pty.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,21 @@ impl Drop for PtyMaster {
7070
// condition, which can cause confusing errors for future I/O
7171
// operations.
7272
let e = unistd::close(self.0);
73-
if e == Err(Error::Sys(Errno::EBADF)) {
73+
if e == Err(Error(Errno::EBADF)) {
7474
panic!("Closing an invalid file descriptor!");
7575
};
7676
}
7777
}
7878

7979
impl io::Read for PtyMaster {
8080
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
81-
unistd::read(self.0, buf).map_err(|e| e.as_errno().unwrap().into())
81+
unistd::read(self.0, buf).map_err(io::Error::from)
8282
}
8383
}
8484

8585
impl io::Write for PtyMaster {
8686
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
87-
unistd::write(self.0, buf).map_err(|e| e.as_errno().unwrap().into())
87+
unistd::write(self.0, buf).map_err(io::Error::from)
8888
}
8989
fn flush(&mut self) -> io::Result<()> {
9090
Ok(())
@@ -99,7 +99,7 @@ impl io::Write for PtyMaster {
9999
#[inline]
100100
pub fn grantpt(fd: &PtyMaster) -> Result<()> {
101101
if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 {
102-
return Err(Error::last());
102+
return Err(Error::from(Errno::last()));
103103
}
104104

105105
Ok(())
@@ -145,7 +145,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
145145
};
146146

147147
if fd < 0 {
148-
return Err(Error::last());
148+
return Err(Error::from(Errno::last()));
149149
}
150150

151151
Ok(PtyMaster(fd))
@@ -171,7 +171,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
171171
pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> {
172172
let name_ptr = libc::ptsname(fd.as_raw_fd());
173173
if name_ptr.is_null() {
174-
return Err(Error::last());
174+
return Err(Error::from(Errno::last()));
175175
}
176176

177177
let name = CStr::from_ptr(name_ptr);
@@ -213,7 +213,7 @@ pub fn ptsname_r(fd: &PtyMaster) -> Result<String> {
213213
#[inline]
214214
pub fn unlockpt(fd: &PtyMaster) -> Result<()> {
215215
if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 {
216-
return Err(Error::last());
216+
return Err(Error::from(Errno::last()));
217217
}
218218

219219
Ok(())

src/sched.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ mod sched_linux_like {
6868
/// `field` is the CPU id to test
6969
pub fn is_set(&self, field: usize) -> Result<bool> {
7070
if field >= CpuSet::count() {
71-
Err(Error::Sys(Errno::EINVAL))
71+
Err(Error::from(Errno::EINVAL))
7272
} else {
7373
Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) })
7474
}
@@ -78,7 +78,7 @@ mod sched_linux_like {
7878
/// `field` is the CPU id to add
7979
pub fn set(&mut self, field: usize) -> Result<()> {
8080
if field >= CpuSet::count() {
81-
Err(Error::Sys(Errno::EINVAL))
81+
Err(Error::from(Errno::EINVAL))
8282
} else {
8383
unsafe { libc::CPU_SET(field, &mut self.cpu_set); }
8484
Ok(())
@@ -89,7 +89,7 @@ mod sched_linux_like {
8989
/// `field` is the CPU id to remove
9090
pub fn unset(&mut self, field: usize) -> Result<()> {
9191
if field >= CpuSet::count() {
92-
Err(Error::Sys(Errno::EINVAL))
92+
Err(Error::from(Errno::EINVAL))
9393
} else {
9494
unsafe { libc::CPU_CLR(field, &mut self.cpu_set);}
9595
Ok(())

0 commit comments

Comments
 (0)