Skip to content

Commit f2ede81

Browse files
authored
Tweak Error representation (#614)
On UEFI targets `Error` is now represented as `NonZeroUsize` and on other targets as `NonZeroI32`. In the former case OS errors have the highest bit set to 1, while in the latter case OS errors are represented as negative integers. Additionally, this PR removes `Error::INTERNAL_START` and `Error::CUSTOM_START` associated constants since the constants are no longer relevant and the inner representation is an internal detail. This is technically a breaking change, but I highly doubt that downstream users rely on them since they can not be used for anything useful, so we can consider it as a "fix" of unintentionally exported constants.
1 parent 564dca5 commit f2ede81

File tree

8 files changed

+94
-78
lines changed

8 files changed

+94
-78
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
- Do not use `dlsym` on MUSL targets in the `linux_android_with_fallback` backend [#602]
2222
- Remove `linux_android.rs` and use `getrandom.rs` instead [#603]
2323
- Always use `RtlGenRandom` on Windows targets when compiling with pre-1.78 Rust [#610]
24+
- Internal representation of the `Error` type [#614]
25+
26+
### Removed
27+
- `Error::INTERNAL_START` and `Error::CUSTOM_START` associated constants [#614]
2428

2529
[#570]: https://github.com/rust-random/getrandom/pull/570
2630
[#572]: https://github.com/rust-random/getrandom/pull/572
@@ -33,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3337
[#603]: https://github.com/rust-random/getrandom/pull/603
3438
[#605]: https://github.com/rust-random/getrandom/pull/605
3539
[#610]: https://github.com/rust-random/getrandom/pull/610
40+
[#614]: https://github.com/rust-random/getrandom/pull/614
3641

3742
## [0.3.1] - 2025-01-28
3843

@@ -556,7 +561,7 @@ Publish initial implementation.
556561
## [0.0.0] - 2019-01-19
557562
Publish an empty template library.
558563

559-
[0.3.2]: https://github.com/rust-random/getrandom/compare/v0.3.0...v0.3.2
564+
[0.3.2]: https://github.com/rust-random/getrandom/compare/v0.3.1...v0.3.2
560565
[0.3.1]: https://github.com/rust-random/getrandom/compare/v0.3.0...v0.3.1
561566
[0.3.0]: https://github.com/rust-random/getrandom/compare/v0.2.15...v0.3.0
562567
[0.2.15]: https://github.com/rust-random/getrandom/compare/v0.2.14...v0.2.15

src/backends/efi_rng.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ fn init() -> Result<NonNull<rng::Protocol>, Error> {
4343
};
4444

4545
if ret.is_error() {
46-
return Err(Error::TEMP_EFI_ERROR);
46+
return Err(Error::from_uefi_code(ret.as_usize()));
4747
}
4848

4949
let handles_len = buf_size / HANDLE_SIZE;
@@ -112,7 +112,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
112112
};
113113

114114
if ret.is_error() {
115-
Err(Error::TEMP_EFI_ERROR)
115+
Err(Error::from_uefi_code(ret.as_usize()))
116116
} else {
117117
Ok(())
118118
}
@@ -121,5 +121,4 @@ pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
121121
impl Error {
122122
pub(crate) const BOOT_SERVICES_UNAVAILABLE: Error = Self::new_internal(10);
123123
pub(crate) const NO_RNG_HANDLE: Error = Self::new_internal(11);
124-
pub(crate) const TEMP_EFI_ERROR: Error = Self::new_internal(12);
125124
}

src/backends/hermit.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,8 @@ pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
4444
dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?;
4545
}
4646
code => {
47-
let err = u32::try_from(code.unsigned_abs())
48-
.ok()
49-
.map_or(Error::UNEXPECTED, Error::from_os_error);
50-
return Err(err);
47+
let code = i32::try_from(code).map_err(|_| Error::UNEXPECTED)?;
48+
return Err(Error::from_neg_error_code(code));
5149
}
5250
}
5351
}

src/backends/linux_raw.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,8 @@ pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
128128
}
129129
Err(_) if ret == EINTR => continue,
130130
Err(_) => {
131-
let code: u32 = ret
132-
.wrapping_neg()
133-
.try_into()
134-
.map_err(|_| Error::UNEXPECTED)?;
135-
return Err(Error::from_os_error(code));
131+
let code = i32::try_from(ret).map_err(|_| Error::UNEXPECTED)?;
132+
return Err(Error::from_neg_error_code(code));
136133
}
137134
}
138135
}

src/backends/solid.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
1414
if ret >= 0 {
1515
Ok(())
1616
} else {
17-
// ITRON error numbers are always negative, so we negate it so that it
18-
// falls in the dedicated OS error range (1..INTERNAL_START).
19-
Err(Error::from_os_error(ret.unsigned_abs()))
17+
Err(Error::from_neg_error_code(ret))
2018
}
2119
}

src/backends/wasi_p1.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
2121
let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) };
2222
match ret {
2323
0 => Ok(()),
24-
code => {
25-
let err = u32::try_from(code)
26-
.map(Error::from_os_error)
27-
.unwrap_or(Error::UNEXPECTED);
28-
Err(err)
29-
}
24+
code => Err(Error::from_neg_error_code(code)),
3025
}
3126
}

src/error.rs

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
#[cfg(feature = "std")]
22
extern crate std;
33

4-
use core::{fmt, num::NonZeroU32};
4+
use core::fmt;
55

66
// This private alias mirrors `std::io::RawOsError`:
77
// https://doc.rust-lang.org/std/io/type.RawOsError.html)
88
cfg_if::cfg_if!(
99
if #[cfg(target_os = "uefi")] {
10+
// See the UEFI spec for more information:
11+
// https://uefi.org/specs/UEFI/2.10/Apx_D_Status_Codes.html
1012
type RawOsError = usize;
13+
type NonZeroRawOsError = core::num::NonZeroUsize;
14+
const UEFI_ERROR_FLAG: RawOsError = 1 << (RawOsError::BITS - 1);
1115
} else {
1216
type RawOsError = i32;
17+
type NonZeroRawOsError = core::num::NonZeroI32;
1318
}
1419
);
1520

@@ -19,16 +24,16 @@ cfg_if::cfg_if!(
1924
/// if so, which error code the OS gave the application. If such an error is
2025
/// encountered, please consult with your system documentation.
2126
///
22-
/// Internally this type is a NonZeroU32, with certain values reserved for
23-
/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`].
24-
///
2527
/// *If this crate's `"std"` Cargo feature is enabled*, then:
2628
/// - [`getrandom::Error`][Error] implements
2729
/// [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html)
2830
/// - [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) implements
2931
/// [`From<getrandom::Error>`](https://doc.rust-lang.org/std/convert/trait.From.html).
32+
33+
// note: on non-UEFI targets OS errors are represented as negative integers,
34+
// while on UEFI targets OS errors have the highest bit set to 1.
3035
#[derive(Copy, Clone, Eq, PartialEq)]
31-
pub struct Error(NonZeroU32);
36+
pub struct Error(NonZeroRawOsError);
3237

3338
impl Error {
3439
/// This target/platform is not supported by `getrandom`.
@@ -38,29 +43,32 @@ impl Error {
3843
/// Encountered an unexpected situation which should not happen in practice.
3944
pub const UNEXPECTED: Error = Self::new_internal(2);
4045

41-
/// Codes below this point represent OS Errors (i.e. positive i32 values).
42-
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
43-
/// reserved for use by the `rand` and `getrandom` crates.
44-
pub const INTERNAL_START: u32 = 1 << 31;
46+
/// Internal errors can be in the range of 2^16..2^17
47+
const INTERNAL_START: RawOsError = 1 << 16;
48+
/// Custom errors can be in the range of 2^17..(2^17 + 2^16)
49+
const CUSTOM_START: RawOsError = 1 << 17;
4550

46-
/// Codes at or above this point can be used by users to define their own
47-
/// custom errors.
48-
pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30);
51+
/// Creates a new instance of an `Error` from a negative error code.
52+
#[cfg(not(target_os = "uefi"))]
53+
#[allow(dead_code)]
54+
pub(super) fn from_neg_error_code(code: RawOsError) -> Self {
55+
if code < 0 {
56+
let code = NonZeroRawOsError::new(code).expect("`code` is negative");
57+
Self(code)
58+
} else {
59+
Error::UNEXPECTED
60+
}
61+
}
4962

50-
/// Creates a new instance of an `Error` from a particular OS error code.
51-
///
52-
/// This method is analogous to [`std::io::Error::from_raw_os_error()`][1],
53-
/// except that it works in `no_std` contexts and `code` will be
54-
/// replaced with `Error::UNEXPECTED` if it isn't in the range
55-
/// `1..Error::INTERNAL_START`. Thus, for the result `r`,
56-
/// `r == Self::UNEXPECTED || r.raw_os_error().unsigned_abs() == code`.
57-
///
58-
/// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.from_raw_os_error
63+
/// Creates a new instance of an `Error` from an UEFI error code.
64+
#[cfg(target_os = "uefi")]
5965
#[allow(dead_code)]
60-
pub(super) fn from_os_error(code: u32) -> Self {
61-
match NonZeroU32::new(code) {
62-
Some(code) if code.get() < Self::INTERNAL_START => Self(code),
63-
_ => Self::UNEXPECTED,
66+
pub(super) fn from_uefi_code(code: RawOsError) -> Self {
67+
if code & UEFI_ERROR_FLAG != 0 {
68+
let code = NonZeroRawOsError::new(code).expect("The highest bit of `code` is set to 1");
69+
Self(code)
70+
} else {
71+
Self::UNEXPECTED
6472
}
6573
}
6674

@@ -79,27 +87,53 @@ impl Error {
7987
#[inline]
8088
pub fn raw_os_error(self) -> Option<RawOsError> {
8189
let code = self.0.get();
82-
if code >= Self::INTERNAL_START {
83-
return None;
90+
91+
// note: in this method we need to cover only backends which rely on
92+
// `Error::{from_error_code, from_errno, from_uefi_code}` methods,
93+
// on all other backends this method always returns `None`.
94+
95+
#[cfg(target_os = "uefi")]
96+
{
97+
if code & UEFI_ERROR_FLAG != 0 {
98+
Some(code)
99+
} else {
100+
None
101+
}
102+
}
103+
104+
#[cfg(not(target_os = "uefi"))]
105+
{
106+
// On most targets `std` expects positive error codes while retrieving error strings:
107+
// - `libc`-based targets use `strerror_r` which expects positive error codes.
108+
// - Hermit relies on the `hermit-abi` crate, which expects positive error codes:
109+
// https://docs.rs/hermit-abi/0.4.0/src/hermit_abi/errno.rs.html#400-532
110+
// - WASIp1 uses the same conventions as `libc`:
111+
// https://github.com/rust-lang/rust/blob/1.85.0/library/std/src/sys/pal/wasi/os.rs#L57-L67
112+
//
113+
// The only exception is Solid, `std` expects negative system error codes, see:
114+
// https://github.com/rust-lang/rust/blob/1.85.0/library/std/src/sys/pal/solid/error.rs#L5-L31
115+
if code >= 0 {
116+
None
117+
} else if cfg!(not(target_os = "solid_asp3")) {
118+
code.checked_neg()
119+
} else {
120+
Some(code)
121+
}
84122
}
85-
let errno = RawOsError::try_from(code).ok()?;
86-
#[cfg(target_os = "solid_asp3")]
87-
let errno = -errno;
88-
Some(errno)
89123
}
90124

91125
/// Creates a new instance of an `Error` from a particular custom error code.
92126
pub const fn new_custom(n: u16) -> Error {
93-
// SAFETY: code > 0 as CUSTOM_START > 0 and adding n won't overflow a u32.
94-
let code = Error::CUSTOM_START + (n as u32);
95-
Error(unsafe { NonZeroU32::new_unchecked(code) })
127+
// SAFETY: code > 0 as CUSTOM_START > 0 and adding `n` won't overflow `RawOsError`.
128+
let code = Error::CUSTOM_START + (n as RawOsError);
129+
Error(unsafe { NonZeroRawOsError::new_unchecked(code) })
96130
}
97131

98132
/// Creates a new instance of an `Error` from a particular internal error code.
99133
pub(crate) const fn new_internal(n: u16) -> Error {
100-
// SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32.
101-
let code = Error::INTERNAL_START + (n as u32);
102-
Error(unsafe { NonZeroU32::new_unchecked(code) })
134+
// SAFETY: code > 0 as INTERNAL_START > 0 and adding `n` won't overflow `RawOsError`.
135+
let code = Error::INTERNAL_START + (n as RawOsError);
136+
Error(unsafe { NonZeroRawOsError::new_unchecked(code) })
103137
}
104138

105139
fn internal_desc(&self) -> Option<&'static str> {
@@ -176,15 +210,3 @@ impl fmt::Display for Error {
176210
}
177211
}
178212
}
179-
180-
#[cfg(test)]
181-
mod tests {
182-
use super::Error;
183-
use core::mem::size_of;
184-
185-
#[test]
186-
fn test_size() {
187-
assert_eq!(size_of::<Error>(), 4);
188-
assert_eq!(size_of::<Result<(), Error>>(), 4);
189-
}
190-
}

src/util_libc.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ cfg_if! {
3434
}
3535

3636
pub(crate) fn last_os_error() -> Error {
37-
let errno: libc::c_int = unsafe { get_errno() };
37+
// We assume that on all targets which use the `util_libc` module `c_int` is equal to `i32`
38+
let errno: i32 = unsafe { get_errno() };
3839

39-
// c_int-to-u32 conversion is lossless for nonnegative values if they are the same size.
40-
const _: () = assert!(core::mem::size_of::<libc::c_int>() == core::mem::size_of::<u32>());
41-
42-
match u32::try_from(errno) {
43-
Ok(code) if code != 0 => Error::from_os_error(code),
44-
_ => Error::ERRNO_NOT_POSITIVE,
40+
if errno > 0 {
41+
let code = errno
42+
.checked_neg()
43+
.expect("Positive number can be always negated");
44+
Error::from_neg_error_code(code)
45+
} else {
46+
Error::ERRNO_NOT_POSITIVE
4547
}
4648
}
4749

0 commit comments

Comments
 (0)