Skip to content

Commit 00c3cff

Browse files
josephlrnewpavlov
authored andcommitted
Improve Error handling (#54)
1 parent b3e609f commit 00c3cff

20 files changed

+186
-226
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ log = { version = "0.4", optional = true }
2222
cfg-if = "0.1"
2323

2424
[target.'cfg(any(unix, target_os = "redox", target_os = "wasi"))'.dependencies]
25-
libc = "0.2.54"
25+
libc = "0.2.60"
2626

2727
[target.wasm32-unknown-unknown.dependencies]
2828
wasm-bindgen = { version = "0.2.29", optional = true }

src/cloudabi.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@ extern "C" {
1717
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
1818
let errno = unsafe { cloudabi_sys_random_get(dest.as_mut_ptr(), dest.len()) };
1919
if let Some(code) = NonZeroU32::new(errno as u32) {
20-
error!("cloudabi_sys_random_get failed with code {}", code);
20+
error!("cloudabi_sys_random_get: failed with {}", errno);
2121
Err(Error::from(code))
2222
} else {
2323
Ok(()) // Zero means success for CloudABI
2424
}
2525
}
26-
27-
#[inline(always)]
28-
pub fn error_msg_inner(_: NonZeroU32) -> Option<&'static str> {
29-
None
30-
}

src/dummy.rs

+3-11
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,9 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88

9-
//! A dummy implementation for unsupported targets which always returns
10-
//! `Err(Error::UNAVAILABLE)`
11-
use crate::Error;
12-
use core::num::NonZeroU32;
9+
//! A dummy implementation for unsupported targets which always fails
10+
use crate::{error::UNSUPPORTED, Error};
1311

1412
pub fn getrandom_inner(_: &mut [u8]) -> Result<(), Error> {
15-
error!("no support for this platform");
16-
Err(Error::UNAVAILABLE)
17-
}
18-
19-
#[inline(always)]
20-
pub fn error_msg_inner(_: NonZeroU32) -> Option<&'static str> {
21-
None
13+
Err(UNSUPPORTED)
2214
}

src/error.rs

+112-39
Original file line numberDiff line numberDiff line change
@@ -5,78 +5,151 @@
55
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
8-
use core::convert::From;
98
use core::fmt;
109
use core::num::NonZeroU32;
1110

12-
// A randomly-chosen 24-bit prefix for our codes
13-
pub(crate) const CODE_PREFIX: u32 = 0x57f4c500;
14-
const CODE_UNKNOWN: u32 = CODE_PREFIX | 0x00;
15-
const CODE_UNAVAILABLE: u32 = CODE_PREFIX | 0x01;
16-
17-
/// The error type.
11+
/// A small and `no_std` compatible error type.
12+
///
13+
/// The [`Error::raw_os_error()`] will indicate if the error is from the OS, and
14+
/// if so, which error code the OS gave the application. If such an error is
15+
/// encountered, please consult with your system documentation.
1816
///
19-
/// This type is small and no-std compatible.
17+
/// Internally this type is a NonZeroU32, with certain values reserved for
18+
/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`].
2019
#[derive(Copy, Clone, Eq, PartialEq)]
21-
pub struct Error(pub(crate) NonZeroU32);
20+
pub struct Error(NonZeroU32);
2221

2322
impl Error {
24-
/// An unknown error.
25-
pub const UNKNOWN: Error = Error(unsafe { NonZeroU32::new_unchecked(CODE_UNKNOWN) });
23+
#[deprecated(since = "0.1.7")]
24+
pub const UNKNOWN: Error = UNSUPPORTED;
25+
#[deprecated(since = "0.1.7")]
26+
pub const UNAVAILABLE: Error = UNSUPPORTED;
27+
28+
/// Codes below this point represent OS Errors (i.e. positive i32 values).
29+
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
30+
/// reserved for use by the `rand` and `getrandom` crates.
31+
pub const INTERNAL_START: u32 = 1 << 31;
2632

27-
/// No generator is available.
28-
pub const UNAVAILABLE: Error = Error(unsafe { NonZeroU32::new_unchecked(CODE_UNAVAILABLE) });
33+
/// Codes at or above this point can be used by users to define their own
34+
/// custom errors.
35+
pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30);
2936

30-
/// Extract the error code.
37+
/// Extract the raw OS error code (if this error came from the OS)
3138
///
32-
/// This may equal one of the codes defined in this library or may be a
33-
/// system error code.
39+
/// This method is identical to `std::io::Error::raw_os_error()`, except
40+
/// that it works in `no_std` contexts. If this method returns `None`, the
41+
/// error value can still be formatted via the `Diplay` implementation.
42+
#[inline]
43+
pub fn raw_os_error(&self) -> Option<i32> {
44+
if self.0.get() < Self::INTERNAL_START {
45+
Some(self.0.get() as i32)
46+
} else {
47+
None
48+
}
49+
}
50+
51+
/// Extract the bare error code.
3452
///
35-
/// One may attempt to format this error via the `Display` implementation.
53+
/// This code can either come from the underlying OS, or be a custom error.
54+
/// Use [`Error::raw_os_error()`] to disambiguate.
55+
#[inline]
3656
pub fn code(&self) -> NonZeroU32 {
3757
self.0
3858
}
59+
}
3960

40-
pub(crate) fn msg(&self) -> Option<&'static str> {
41-
if let Some(msg) = crate::imp::error_msg_inner(self.0) {
42-
Some(msg)
43-
} else {
44-
match *self {
45-
Error::UNKNOWN => Some("getrandom: unknown error"),
46-
Error::UNAVAILABLE => Some("getrandom: unavailable"),
47-
_ => None,
48-
}
49-
}
61+
#[cfg(any(unix, target_os = "redox"))]
62+
fn os_err_desc(errno: i32, buf: &mut [u8]) -> Option<&str> {
63+
let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char;
64+
if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 {
65+
return None;
5066
}
67+
68+
// Take up to trailing null byte
69+
let idx = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
70+
core::str::from_utf8(&buf[..idx]).ok()
71+
}
72+
73+
#[cfg(not(any(unix, target_os = "redox")))]
74+
fn os_err_desc(_errno: i32, _buf: &mut [u8]) -> Option<&str> {
75+
None
5176
}
5277

5378
impl fmt::Debug for Error {
54-
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
55-
match self.msg() {
56-
Some(msg) => write!(f, "Error(\"{}\")", msg),
57-
None => write!(f, "Error(0x{:08X})", self.0),
79+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80+
let mut dbg = f.debug_struct("Error");
81+
if let Some(errno) = self.raw_os_error() {
82+
dbg.field("os_error", &errno);
83+
let mut buf = [0u8; 128];
84+
if let Some(desc) = os_err_desc(errno, &mut buf) {
85+
dbg.field("description", &desc);
86+
}
87+
} else if let Some(desc) = internal_desc(*self) {
88+
dbg.field("internal_code", &self.0.get());
89+
dbg.field("description", &desc);
90+
} else {
91+
dbg.field("unknown_code", &self.0.get());
5892
}
93+
dbg.finish()
5994
}
6095
}
6196

6297
impl fmt::Display for Error {
63-
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
64-
match self.msg() {
65-
Some(msg) => write!(f, "{}", msg),
66-
None => write!(f, "getrandom: unknown code 0x{:08X}", self.0),
98+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99+
if let Some(errno) = self.raw_os_error() {
100+
let mut buf = [0u8; 128];
101+
match os_err_desc(errno, &mut buf) {
102+
Some(desc) => f.write_str(desc),
103+
None => write!(f, "OS Error: {}", errno),
104+
}
105+
} else if let Some(desc) = internal_desc(*self) {
106+
f.write_str(desc)
107+
} else {
108+
write!(f, "Unknown Error: {}", self.0.get())
67109
}
68110
}
69111
}
70112

71113
impl From<NonZeroU32> for Error {
72114
fn from(code: NonZeroU32) -> Self {
73-
Error(code)
115+
Self(code)
74116
}
75117
}
76118

77-
impl From<&Error> for Error {
78-
fn from(error: &Error) -> Self {
79-
*error
119+
// TODO: Convert to a function when min_version >= 1.33
120+
macro_rules! internal_error {
121+
($n:expr) => {
122+
Error(unsafe { NonZeroU32::new_unchecked(Error::INTERNAL_START + $n as u16 as u32) })
123+
};
124+
}
125+
126+
/// Internal Error constants
127+
pub(crate) const UNSUPPORTED: Error = internal_error!(0);
128+
pub(crate) const ERRNO_NOT_POSITIVE: Error = internal_error!(1);
129+
pub(crate) const UNKNOWN_IO_ERROR: Error = internal_error!(2);
130+
pub(crate) const SEC_RANDOM_FAILED: Error = internal_error!(3);
131+
pub(crate) const RTL_GEN_RANDOM_FAILED: Error = internal_error!(4);
132+
pub(crate) const FAILED_RDRAND: Error = internal_error!(5);
133+
pub(crate) const NO_RDRAND: Error = internal_error!(6);
134+
pub(crate) const BINDGEN_CRYPTO_UNDEF: Error = internal_error!(7);
135+
pub(crate) const BINDGEN_GRV_UNDEF: Error = internal_error!(8);
136+
pub(crate) const STDWEB_NO_RNG: Error = internal_error!(9);
137+
pub(crate) const STDWEB_RNG_FAILED: Error = internal_error!(10);
138+
139+
fn internal_desc(error: Error) -> Option<&'static str> {
140+
match error {
141+
UNSUPPORTED => Some("getrandom: this target is not supported"),
142+
ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"),
143+
UNKNOWN_IO_ERROR => Some("Unknown std::io::Error"),
144+
SEC_RANDOM_FAILED => Some("SecRandomCopyBytes: call failed"),
145+
RTL_GEN_RANDOM_FAILED => Some("RtlGenRandom: call failed"),
146+
FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"),
147+
NO_RDRAND => Some("RDRAND: instruction not supported"),
148+
BINDGEN_CRYPTO_UNDEF => Some("wasm-bindgen: self.crypto is undefined"),
149+
BINDGEN_GRV_UNDEF => Some("wasm-bindgen: crypto.getRandomValues is undefined"),
150+
STDWEB_NO_RNG => Some("stdweb: no randomness source available"),
151+
STDWEB_RNG_FAILED => Some("stdweb: failed to get randomness"),
152+
_ => None,
80153
}
81154
}
82155

src/error_impls.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,29 @@
77
// except according to those terms.
88
extern crate std;
99

10-
use crate::error::Error;
10+
use crate::{error::UNKNOWN_IO_ERROR, Error};
1111
use core::convert::From;
1212
use core::num::NonZeroU32;
13-
use std::{error, io};
13+
use std::io;
1414

1515
impl From<io::Error> for Error {
1616
fn from(err: io::Error) -> Self {
17-
err.raw_os_error()
18-
.and_then(|code| NonZeroU32::new(code as u32))
19-
.map(|code| Error(code))
20-
// in practice this should never happen
21-
.unwrap_or(Error::UNKNOWN)
17+
if let Some(errno) = err.raw_os_error() {
18+
if let Some(code) = NonZeroU32::new(errno as u32) {
19+
return Error::from(code);
20+
}
21+
}
22+
UNKNOWN_IO_ERROR
2223
}
2324
}
2425

2526
impl From<Error> for io::Error {
2627
fn from(err: Error) -> Self {
27-
match err.msg() {
28-
Some(msg) => io::Error::new(io::ErrorKind::Other, msg),
29-
None => io::Error::from_raw_os_error(err.0.get() as i32),
28+
match err.raw_os_error() {
29+
Some(errno) => io::Error::from_raw_os_error(errno),
30+
None => io::Error::new(io::ErrorKind::Other, err),
3031
}
3132
}
3233
}
3334

34-
impl error::Error for Error {}
35+
impl std::error::Error for Error {}

src/freebsd.rs

-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
//! Implementation for FreeBSD
1010
use crate::util_libc::{sys_fill_exact, Weak};
1111
use crate::Error;
12-
use core::num::NonZeroU32;
1312
use core::{mem, ptr};
1413

1514
type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t;
@@ -44,8 +43,3 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
4443
sys_fill_exact(dest, kern_arnd)
4544
}
4645
}
47-
48-
#[inline(always)]
49-
pub fn error_msg_inner(_: NonZeroU32) -> Option<&'static str> {
50-
None
51-
}

src/fuchsia.rs

-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
//! Implementation for Fuchsia Zircon
1010
use crate::Error;
11-
use core::num::NonZeroU32;
1211

1312
#[link(name = "zircon")]
1413
extern "C" {
@@ -19,8 +18,3 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
1918
unsafe { zx_cprng_draw(dest.as_mut_ptr(), dest.len()) }
2019
Ok(())
2120
}
22-
23-
#[inline(always)]
24-
pub fn error_msg_inner(_: NonZeroU32) -> Option<&'static str> {
25-
None
26-
}

src/ios.rs

+2-11
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
// except according to those terms.
88

99
//! Implementation for iOS
10-
extern crate std;
11-
12-
use crate::Error;
13-
use core::num::NonZeroU32;
10+
use crate::{error::SEC_RANDOM_FAILED, Error};
1411

1512
// TODO: Make extern once extern_types feature is stabilized. See:
1613
// https://github.com/rust-lang/rust/issues/43467
@@ -27,14 +24,8 @@ extern "C" {
2724
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
2825
let ret = unsafe { SecRandomCopyBytes(kSecRandomDefault, dest.len(), dest.as_mut_ptr()) };
2926
if ret == -1 {
30-
error!("SecRandomCopyBytes call failed");
31-
Err(Error::UNKNOWN)
27+
Err(SEC_RANDOM_FAILED)
3228
} else {
3329
Ok(())
3430
}
3531
}
36-
37-
#[inline(always)]
38-
pub fn error_msg_inner(_: NonZeroU32) -> Option<&'static str> {
39-
None
40-
}

src/lib.rs

+3-23
Original file line numberDiff line numberDiff line change
@@ -79,25 +79,8 @@
7979
//! `getrandom`, hence after the first successful call one can be reasonably
8080
//! confident that no errors will occur.
8181
//!
82-
//! On unsupported platforms, `getrandom` always fails with [`Error::UNAVAILABLE`].
83-
//!
84-
//! ## Error codes
85-
//! The crate uses the following custom error codes:
86-
//! - `0x57f4c500` (dec: 1475659008) - an unknown error. Constant:
87-
//! [`Error::UNKNOWN`]
88-
//! - `0x57f4c501` (dec: 1475659009) - no generator is available. Constant:
89-
//! [`Error::UNAVAILABLE`]
90-
//! - `0x57f4c580` (dec: 1475659136) - `self.crypto` is undefined,
91-
//! `wasm-bindgen` specific error.
92-
//! - `0x57f4c581` (dec: 1475659137) - `crypto.getRandomValues` is undefined,
93-
//! `wasm-bindgen` specific error.
94-
//!
95-
//! These codes are provided for reference only and should not be matched upon
96-
//! (but you can match on `Error` constants). The codes may change in future and
97-
//! such change will not be considered a breaking one.
98-
//!
99-
//! Other error codes will originate from an underlying system. In case if such
100-
//! error is encountered, please consult with your system documentation.
82+
//! On unsupported platforms, `getrandom` always fails. See the [`Error`] type
83+
//! for more information on what data is returned on failure.
10184
//!
10285
//! [1]: http://man7.org/linux/man-pages/man2/getrandom.2.html
10386
//! [2]: http://man7.org/linux/man-pages/man4/urandom.4.html
@@ -146,14 +129,12 @@ cfg_if! {
146129
}
147130
}
148131

149-
#[cfg(feature = "std")]
150-
extern crate std;
151-
152132
mod error;
153133
pub use crate::error::Error;
154134

155135
#[allow(dead_code)]
156136
mod util;
137+
// Unlike the other Unix, Fuchsia and iOS don't use the libc to make any calls.
157138
#[cfg(any(
158139
target_os = "android",
159140
target_os = "dragonfly",
@@ -197,7 +178,6 @@ mod error_impls;
197178
target_os = "solaris",
198179
target_os = "illumos",
199180
))]
200-
#[allow(dead_code)]
201181
mod use_file;
202182

203183
// System-specific implementations.

0 commit comments

Comments
 (0)