From dd3a01fe10ecc28f67578da3e8bf0a2640267c41 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 8 Jun 2024 13:32:34 -0700 Subject: [PATCH 1/3] m: Add rustix backend This commit adds a backend based on the rustix crate. The main advantage of rustix, in addition to greater safety, is that it uses raw syscalls instead of going through libc. I've tried to modify the existing libc code as little as possible, in order to make this change as auditable as possible. I haven't touched the pthreads code yet, as that's a little tricky. This exists for discussion purposes. cc #401 Signed-off-by: John Nunley --- .github/workflows/tests.yml | 2 +- Cargo.toml | 6 +++ src/lib.rs | 4 ++ src/linux_android.rs | 7 +++- src/linux_android_with_fallback.rs | 21 +++++++++- src/use_file.rs | 57 +++++++++++++++++++++++---- src/util_rustix.rs | 63 ++++++++++++++++++++++++++++++ 7 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 src/util_rustix.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40db0124..d3b30d10 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, windows-2022] - toolchain: [nightly, beta, stable, 1.36] + toolchain: [nightly, beta, stable, 1.63] # Only Test macOS on stable to reduce macOS CI jobs include: # x86_64-apple-darwin. diff --git a/Cargo.toml b/Cargo.toml index 4de6dcfd..156ebd19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ documentation = "https://docs.rs/getrandom" repository = "https://github.com/rust-random/getrandom" categories = ["os", "no-std"] exclude = [".*"] +rust-version = "1.63" [dependencies] cfg-if = "1" @@ -23,6 +24,9 @@ libc = { version = "0.2.154", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] wasi = { version = "0.11", default-features = false } +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +rustix = { version = "0.38.0", features = ["event", "fs", "rand"], default-features = false, optional = true } + [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } js-sys = { version = "0.3", optional = true } @@ -30,6 +34,7 @@ js-sys = { version = "0.3", optional = true } wasm-bindgen-test = "0.3.18" [features] +default = ["rustix"] # Implement std-only traits for getrandom::Error std = [] # Disable `/dev/urandom` fallback for Linux and Android targets. @@ -46,6 +51,7 @@ rustc-dep-of-std = [ "compiler_builtins", "core", "libc/rustc-dep-of-std", + "rustix?/rustc-dep-of-std", "wasi/rustc-dep-of-std", ] # Unstable/test-only feature to run wasm-bindgen tests in a browser diff --git a/src/lib.rs b/src/lib.rs index bc3695b6..a1777696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,11 +294,15 @@ cfg_if! { ) ), ))] { + #[cfg(feature = "rustix")] + mod util_rustix; mod util_libc; mod use_file; mod lazy; #[path = "linux_android_with_fallback.rs"] mod imp; } else if #[cfg(any(target_os = "android", target_os = "linux"))] { + #[cfg(feature = "rustix")] + mod util_rustix; mod util_libc; #[path = "linux_android.rs"] mod imp; } else if #[cfg(target_os = "solaris")] { diff --git a/src/linux_android.rs b/src/linux_android.rs index 93a64945..88574129 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -1,7 +1,12 @@ //! Implementation for Linux / Android without `/dev/urandom` fallback -use crate::{util_libc, Error}; +use crate::Error; use core::mem::MaybeUninit; +#[cfg(not(feature = "rustix"))] +use crate::util_libc; +#[cfg(feature = "rustix")] +use crate::util_rustix as util_libc; + pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall) } diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index 0f5ea8a9..dde87295 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -1,11 +1,19 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback use crate::{ lazy::LazyBool, - util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}, {use_file, Error}, }; use core::mem::MaybeUninit; +#[cfg(not(feature = "rustix"))] +use crate::util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}; + +#[cfg(feature = "rustix")] +use { + crate::util_rustix::{getrandom_syscall, sys_fill_exact}, + rustix::{io::Errno, rand}, +}; + pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getrandom(2) was introduced in Linux 3.17 static HAS_GETRANDOM: LazyBool = LazyBool::new(); @@ -16,6 +24,7 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } } +#[cfg(not(feature = "rustix"))] fn is_getrandom_available() -> bool { if getrandom_syscall(&mut []) < 0 { match last_os_error().raw_os_error() { @@ -31,3 +40,13 @@ fn is_getrandom_available() -> bool { true } } + +#[cfg(feature = "rustix")] +fn is_getrandom_available() -> bool { + match rand::getrandom(&mut [], rand::GetRandomFlags::empty()) { + Err(Errno::NOSYS) => false, + #[cfg(target_os = "linux")] + Err(Errno::PERM) => false, + _ => true, + } +} diff --git a/src/use_file.rs b/src/use_file.rs index bd643ae5..dc33014c 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -1,28 +1,44 @@ //! Implementations that just need to read from a file -use crate::{ - util_libc::{open_readonly, sys_fill_exact}, - Error, -}; +use crate::Error; use core::{ cell::UnsafeCell, mem::MaybeUninit, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; +#[cfg(not(all(any(target_os = "linux", target_os = "android"), feature = "rustix")))] +use crate::util_libc::{open_readonly, sys_fill_exact}; +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "rustix"))] +use crate::util_rustix::{open_readonly, sys_fill_exact}; + /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. /// For more information see the linked man pages in lib.rs. /// - On Linux, "/dev/urandom is preferred and sufficient in all use cases". /// - On Redox, only /dev/urandom is provided. /// - On AIX, /dev/urandom will "provide cryptographically secure output". /// - On Haiku and QNX Neutrino they are identical. +#[cfg(not(feature = "rustix"))] const FILE_PATH: &str = "/dev/urandom\0"; +#[cfg(feature = "rustix")] +const FILE_PATH: &str = "/dev/urandom"; const FD_UNINIT: usize = usize::max_value(); pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let fd = get_rng_fd()?; - sys_fill_exact(dest, |buf| unsafe { - libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) - }) + sys_fill_exact(dest, |buf| read_from_fd(fd, buf)) +} + +#[cfg(not(feature = "rustix"))] +fn read_from_fd(fd: libc::c_int, buf: &mut [MaybeUninit]) -> libc::ssize_t { + unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) } +} + +#[cfg(feature = "rustix")] +fn read_from_fd( + fd: libc::c_int, + buf: &mut [MaybeUninit], +) -> Result<(&mut [u8], &mut [MaybeUninit]), rustix::io::Errno> { + rustix::io::read_uninit(unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) }, buf) } // Returns the file descriptor for the device file used to retrieve random @@ -56,7 +72,10 @@ fn get_rng_fd() -> Result { #[cfg(any(target_os = "android", target_os = "linux"))] wait_until_rng_ready()?; + #[allow(unused_unsafe)] let fd = unsafe { open_readonly(FILE_PATH)? }; + #[cfg(feature = "rustix")] + let fd = rustix::fd::IntoRawFd::into_raw_fd(fd); // The fd always fits in a usize without conflicting with FD_UNINIT. debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT); FD.store(fd as usize, Relaxed); @@ -65,7 +84,10 @@ fn get_rng_fd() -> Result { } // Succeeds once /dev/urandom is safe to read from -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(all( + any(target_os = "android", target_os = "linux"), + not(feature = "rustix") +))] fn wait_until_rng_ready() -> Result<(), Error> { // Poll /dev/random to make sure it is ok to read from /dev/urandom. let fd = unsafe { open_readonly("/dev/random\0")? }; @@ -93,6 +115,25 @@ fn wait_until_rng_ready() -> Result<(), Error> { } } +// Succeeds once /dev/urandom is safe to read from +#[cfg(all(any(target_os = "android", target_os = "linux"), feature = "rustix"))] +fn wait_until_rng_ready() -> Result<(), Error> { + use rustix::event; + + // Open the file. + let fd = crate::util_rustix::open_readonly("/dev/random")?; + + // Poll it until it is ready. + let mut pfd = [event::PollFd::new(&fd, event::PollFlags::IN)]; + loop { + match event::poll(&mut pfd, -1) { + Ok(_) => return Ok(()), + Err(rustix::io::Errno::INTR) => continue, + Err(err) => return Err(crate::util_rustix::cvt(err)), + } + } +} + struct Mutex(UnsafeCell); impl Mutex { diff --git a/src/util_rustix.rs b/src/util_rustix.rs new file mode 100644 index 00000000..ce297247 --- /dev/null +++ b/src/util_rustix.rs @@ -0,0 +1,63 @@ +//! Utilities for using rustix. +//! +//! At this point in time it is only used on Linux-like operating systems. + +use crate::Error; +use core::convert::TryInto; +use core::mem::MaybeUninit; +use core::num::NonZeroU32; + +use rustix::fd::OwnedFd; +use rustix::fs; +use rustix::io::Errno; +use rustix::rand; + +/// Convert a Rustix error to one of our errors. +pub(crate) fn cvt(err: Errno) -> Error { + match TryInto::::try_into(err.raw_os_error()) + .ok() + .and_then(NonZeroU32::new) + { + Some(code) => Error::from(code), + None => Error::ERRNO_NOT_POSITIVE, + } +} + +/// Fill a buffer by repeatedly invoking a `rustix` call. +pub(crate) fn sys_fill_exact( + mut buf: &mut [MaybeUninit], + fill: impl Fn(&mut [MaybeUninit]) -> Result<(&mut [u8], &mut [MaybeUninit]), Errno>, +) -> Result<(), Error> { + while !buf.is_empty() { + // Read into the buffer. + match fill(buf) { + Err(err) => return Err(cvt(err)), + Ok((_filled, unfilled)) => { + buf = unfilled; + } + } + } + + Ok(()) +} + +/// Open a file as read-only. +pub(crate) fn open_readonly(path: &str) -> Result { + loop { + match fs::open( + path, + fs::OFlags::CLOEXEC | fs::OFlags::RDONLY, + fs::Mode::empty(), + ) { + Ok(file) => return Ok(file), + Err(Errno::INTR) => continue, + Err(err) => return Err(cvt(err)), + } + } +} + +pub(crate) fn getrandom_syscall( + buf: &mut [MaybeUninit], +) -> Result<(&mut [u8], &mut [MaybeUninit]), Errno> { + rand::getrandom_uninit(buf, rand::GetRandomFlags::empty()) +} From 78308074da88135322f7fc2d03f9f8bf67bf9952 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sun, 9 Jun 2024 08:57:15 -0700 Subject: [PATCH 2/3] m: Add our own mutex implementation libc still uses pthreads, but on rustix we use a homegrown futex implementation based on the rustix-futex-sync crate. Signed-off-by: John Nunley --- Cargo.toml | 2 +- src/use_file.rs | 23 +------ src/util_libc.rs | 21 ++++++ src/util_rustix.rs | 164 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 156ebd19..3e0f418d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ libc = { version = "0.2.154", default-features = false } wasi = { version = "0.11", default-features = false } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -rustix = { version = "0.38.0", features = ["event", "fs", "rand"], default-features = false, optional = true } +rustix = { version = "0.38.0", features = ["event", "fs", "rand", "thread"], default-features = false, optional = true } [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } diff --git a/src/use_file.rs b/src/use_file.rs index dc33014c..721b9d3b 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -1,15 +1,14 @@ //! Implementations that just need to read from a file use crate::Error; use core::{ - cell::UnsafeCell, mem::MaybeUninit, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; #[cfg(not(all(any(target_os = "linux", target_os = "android"), feature = "rustix")))] -use crate::util_libc::{open_readonly, sys_fill_exact}; +use crate::util_libc::{open_readonly, sys_fill_exact, Mutex}; #[cfg(all(any(target_os = "linux", target_os = "android"), feature = "rustix"))] -use crate::util_rustix::{open_readonly, sys_fill_exact}; +use crate::util_rustix::{open_readonly, sys_fill_exact, Mutex}; /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. /// For more information see the linked man pages in lib.rs. @@ -134,24 +133,6 @@ fn wait_until_rng_ready() -> Result<(), Error> { } } -struct Mutex(UnsafeCell); - -impl Mutex { - const fn new() -> Self { - Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) - } - unsafe fn lock(&self) { - let r = libc::pthread_mutex_lock(self.0.get()); - debug_assert_eq!(r, 0); - } - unsafe fn unlock(&self) { - let r = libc::pthread_mutex_unlock(self.0.get()); - debug_assert_eq!(r, 0); - } -} - -unsafe impl Sync for Mutex {} - struct DropGuard(F); impl Drop for DropGuard { diff --git a/src/util_libc.rs b/src/util_libc.rs index 129362d5..f3946336 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use crate::Error; use core::{ + cell::UnsafeCell, mem::MaybeUninit, num::NonZeroU32, ptr::NonNull, @@ -160,3 +161,23 @@ pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> libc::ssize_t { ) as libc::ssize_t } } + +#[allow(dead_code)] +pub(crate) struct Mutex(UnsafeCell); + +#[allow(dead_code)] +impl Mutex { + pub(crate) const fn new() -> Self { + Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) + } + pub(crate) unsafe fn lock(&self) { + let r = libc::pthread_mutex_lock(self.0.get()); + debug_assert_eq!(r, 0); + } + pub(crate) unsafe fn unlock(&self) { + let r = libc::pthread_mutex_unlock(self.0.get()); + debug_assert_eq!(r, 0); + } +} + +unsafe impl Sync for Mutex {} diff --git a/src/util_rustix.rs b/src/util_rustix.rs index ce297247..9f3bfae5 100644 --- a/src/util_rustix.rs +++ b/src/util_rustix.rs @@ -61,3 +61,167 @@ pub(crate) fn getrandom_syscall( ) -> Result<(&mut [u8], &mut [MaybeUninit]), Errno> { rand::getrandom_uninit(buf, rand::GetRandomFlags::empty()) } + +// The mutex isn't used in all Linux implementations. +#[allow(dead_code)] +mod mutex_impl { + //! The following is derived from Rust's + //! library/std/src/sys/unix/locks/futex_mutex.rs at revision + //! 98815742cf2e914ee0d7142a02322cf939c47834. + //! Also partially based on the rustix_futex_sync crate. + + use core::sync::atomic::{AtomicU32, Ordering}; + use rustix::thread::{FutexFlags, FutexOperation}; + + pub(crate) struct Mutex { + futex: AtomicU32, + } + + const UNLOCKED: u32 = 0; + const LOCKED: u32 = 1; + const CONTENDED: u32 = 2; + + impl Mutex { + pub(crate) const fn new() -> Self { + Self { + futex: AtomicU32::new(UNLOCKED), + } + } + + // This function is safe and is only unsafe for consistency with util_libc.rs + pub(crate) unsafe fn lock(&self) { + if self + .futex + .compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + self.lock_contended(); + } + } + + #[cold] + fn lock_contended(&self) { + // Spin first to speed things up if the lock is released quickly. + let mut state = self.spin(); + + // If it's unlocked now, attempt to take the lock + // without marking it as contended. + if state == UNLOCKED { + match self.futex.compare_exchange( + UNLOCKED, + LOCKED, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return, // Locked! + Err(s) => state = s, + } + } + + loop { + // Put the lock in contended state. + // We avoid an unnecessary write if it as already set to 2, + // to be friendlier for the caches. + if state != CONTENDED && self.futex.swap(CONTENDED, Ordering::Acquire) == 0 { + // We changed it from 0 to 2, so we just successfully locked it. + return; + } + + // Wait for the futex to change state, assuming it is still 2. + futex_wait(&self.futex, CONTENDED); + + // Spin again after waking up. + state = self.spin(); + } + } + + /// Production-grade mutexes usually spin for a little to alleviate short-term contention. + fn spin(&self) -> u32 { + let mut spin = 100; + + loop { + // We only use `load` (and not `swap` or `compare_exchange`) + // while spinning, to be easier on the caches. + let state = self.futex.load(Ordering::Relaxed); + + // We stop spinning when the mutex is unlocked (0), + // but also when it's contended (2). + if state != LOCKED || spin == 0 { + return state; + } + + core::hint::spin_loop(); + spin -= 1; + } + } + + #[inline] + pub unsafe fn unlock(&self) { + if self.futex.swap(UNLOCKED, Ordering::Release) == CONTENDED { + // We only wake up one thread. When that thread locks the mutex, it + // will mark the mutex as contended (2) (see lock_contended above), + // which makes sure that any other waiting threads will also be + // woken up eventually. + futex_wake(&self.futex); + } + } + } + + /// Wait on a futex. + pub fn futex_wait(futex: &AtomicU32, expected: u32) -> bool { + use core::ptr::{null, null_mut}; + use core::sync::atomic::Ordering::Relaxed; + + loop { + // No need to wait if the value already changed. + if futex.load(Relaxed) != expected { + return true; + } + + let r = unsafe { + // Use FUTEX_WAIT_BITSET rather than FUTEX_WAIT to be able to give an + // absolute time rather than a relative time. + rustix::thread::futex( + futex.as_ptr(), + FutexOperation::WaitBitset, + FutexFlags::PRIVATE, + expected, + null(), + null_mut(), + !0u32, // A full bitmask, to make it behave like a regular FUTEX_WAIT. + ) + }; + + match r { + Err(rustix::io::Errno::TIMEDOUT) => return false, + Err(rustix::io::Errno::INTR) => continue, + _ => return true, + } + } + } + + /// Wake up one thread blocked on futex_wait. + /// + /// Returns true if a thread was actually woken up. + fn futex_wake(futex: &AtomicU32) -> bool { + use core::ptr::{null, null_mut}; + + unsafe { + match rustix::thread::futex( + futex.as_ptr(), + FutexOperation::Wake, + FutexFlags::PRIVATE, + 1, + null(), + null_mut(), + 0, + ) { + Err(_) | Ok(0) => false, + _ => true, + } + } + } +} + +#[allow(unused)] +pub(crate) use mutex_impl::Mutex; From 58752cc09b53fce66b8ac935dfe33a83d4d41d91 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sun, 9 Jun 2024 20:14:07 -0700 Subject: [PATCH 3/3] m: Completely remove libc from linux Signed-off-by: John Nunley --- Cargo.toml | 9 +++--- src/error.rs | 37 +++++++++++++++++++++++- src/lib.rs | 7 +++-- src/linux_android.rs | 4 +-- src/linux_android_with_fallback.rs | 8 +++--- src/use_file.rs | 45 ++++++++++++++++++------------ 6 files changed, 78 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e0f418d..7aba00f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,14 @@ cfg-if = "1" compiler_builtins = { version = "0.1", optional = true } core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } -[target.'cfg(unix)'.dependencies] +[target.'cfg(all(unix, not(target_os = "linux")))'.dependencies] libc = { version = "0.2.154", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] wasi = { version = "0.11", default-features = false } -[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -rustix = { version = "0.38.0", features = ["event", "fs", "rand", "thread"], default-features = false, optional = true } +[target.'cfg(target_os = "linux")'.dependencies] +rustix = { version = "0.38.0", features = ["event", "fs", "rand", "thread"], default-features = false } [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } @@ -34,7 +34,6 @@ js-sys = { version = "0.3", optional = true } wasm-bindgen-test = "0.3.18" [features] -default = ["rustix"] # Implement std-only traits for getrandom::Error std = [] # Disable `/dev/urandom` fallback for Linux and Android targets. @@ -51,7 +50,7 @@ rustc-dep-of-std = [ "compiler_builtins", "core", "libc/rustc-dep-of-std", - "rustix?/rustc-dep-of-std", + "rustix/rustc-dep-of-std", "wasi/rustc-dep-of-std", ] # Unstable/test-only feature to run wasm-bindgen tests in a browser diff --git a/src/error.rs b/src/error.rs index 13c81c7a..5847377e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -97,7 +97,42 @@ impl Error { } cfg_if! { - if #[cfg(unix)] { + if #[cfg(target_os = "linux")] { + fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> { + use core::mem; + use fmt::Write; + + // Convert Rustix errno to string. + struct Writer<'a> { + slot: &'a mut [u8], + start: *const u8, + len: usize, + } + + impl fmt::Write for Writer<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + let (slot, rest) = mem::take(&mut self.slot).split_at_mut(s.len()); + self.slot = rest; + slot.copy_from_slice(s.as_bytes()); + self.len += s.len(); + Ok(()) + } + } + + let mut writer = Writer { + start: buf.as_ptr(), + slot: buf, + len: 0 + }; + core::write!(&mut writer, "{:?}", rustix::io::Errno::from_raw_os_error(errno)).ok()?; + Some(unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts( + writer.start, + writer.len, + )) + }) + } + } else if #[cfg(unix)] { fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> { let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char; if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 { diff --git a/src/lib.rs b/src/lib.rs index a1777696..9f6a45af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,15 +294,18 @@ cfg_if! { ) ), ))] { - #[cfg(feature = "rustix")] + #[cfg(target_os = "linux")] mod util_rustix; + #[cfg(not(target_os = "linux"))] mod util_libc; mod use_file; mod lazy; #[path = "linux_android_with_fallback.rs"] mod imp; } else if #[cfg(any(target_os = "android", target_os = "linux"))] { - #[cfg(feature = "rustix")] + #[cfg(target_os = "linux")] mod util_rustix; + #[cfg(not(target_os = "linux"))] + mod util_libc; mod util_libc; #[path = "linux_android.rs"] mod imp; } else if #[cfg(target_os = "solaris")] { diff --git a/src/linux_android.rs b/src/linux_android.rs index 88574129..37817961 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -2,9 +2,9 @@ use crate::Error; use core::mem::MaybeUninit; -#[cfg(not(feature = "rustix"))] +#[cfg(not(target_os = "linux"))] use crate::util_libc; -#[cfg(feature = "rustix")] +#[cfg(target_os = "linux")] use crate::util_rustix as util_libc; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index dde87295..e18fd8c1 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -5,10 +5,10 @@ use crate::{ }; use core::mem::MaybeUninit; -#[cfg(not(feature = "rustix"))] +#[cfg(not(target_os = "linux"))] use crate::util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}; -#[cfg(feature = "rustix")] +#[cfg(target_os = "linux")] use { crate::util_rustix::{getrandom_syscall, sys_fill_exact}, rustix::{io::Errno, rand}, @@ -24,7 +24,7 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } } -#[cfg(not(feature = "rustix"))] +#[cfg(not(target_os = "linux"))] fn is_getrandom_available() -> bool { if getrandom_syscall(&mut []) < 0 { match last_os_error().raw_os_error() { @@ -41,7 +41,7 @@ fn is_getrandom_available() -> bool { } } -#[cfg(feature = "rustix")] +#[cfg(target_os = "linux")] fn is_getrandom_available() -> bool { match rand::getrandom(&mut [], rand::GetRandomFlags::empty()) { Err(Errno::NOSYS) => false, diff --git a/src/use_file.rs b/src/use_file.rs index 721b9d3b..70ede796 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -5,9 +5,9 @@ use core::{ sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; -#[cfg(not(all(any(target_os = "linux", target_os = "android"), feature = "rustix")))] +#[cfg(not(target_os = "linux"))] use crate::util_libc::{open_readonly, sys_fill_exact, Mutex}; -#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "rustix"))] +#[cfg(target_os = "linux")] use crate::util_rustix::{open_readonly, sys_fill_exact, Mutex}; /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. @@ -16,9 +16,9 @@ use crate::util_rustix::{open_readonly, sys_fill_exact, Mutex}; /// - On Redox, only /dev/urandom is provided. /// - On AIX, /dev/urandom will "provide cryptographically secure output". /// - On Haiku and QNX Neutrino they are identical. -#[cfg(not(feature = "rustix"))] +#[cfg(not(target_os = "linux"))] const FILE_PATH: &str = "/dev/urandom\0"; -#[cfg(feature = "rustix")] +#[cfg(target_os = "linux")] const FILE_PATH: &str = "/dev/urandom"; const FD_UNINIT: usize = usize::max_value(); @@ -27,28 +27,36 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { sys_fill_exact(dest, |buf| read_from_fd(fd, buf)) } -#[cfg(not(feature = "rustix"))] -fn read_from_fd(fd: libc::c_int, buf: &mut [MaybeUninit]) -> libc::ssize_t { +#[cfg(not(target_os = "linux"))] +type SysFd = libc::c_int; +#[cfg(target_os = "linux")] +type SysFd = rustix::fd::BorrowedFd<'static>; + +#[cfg(not(target_os = "linux"))] +fn read_from_fd(fd: SysFd, buf: &mut [MaybeUninit]) -> libc::ssize_t { unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) } } -#[cfg(feature = "rustix")] +#[cfg(target_os = "linux")] fn read_from_fd( - fd: libc::c_int, + fd: SysFd, buf: &mut [MaybeUninit], ) -> Result<(&mut [u8], &mut [MaybeUninit]), rustix::io::Errno> { - rustix::io::read_uninit(unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) }, buf) + rustix::io::read_uninit(fd, buf) } // Returns the file descriptor for the device file used to retrieve random // bytes. The file will be opened exactly once. All subsequent calls will // return the same file descriptor. This file descriptor is never closed. -fn get_rng_fd() -> Result { +fn get_rng_fd() -> Result { static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT); - fn get_fd() -> Option { + fn get_fd() -> Option { match FD.load(Relaxed) { FD_UNINIT => None, + #[cfg(not(target_os = "linux"))] val => Some(val as libc::c_int), + #[cfg(target_os = "linux")] + val => Some(unsafe { rustix::fd::BorrowedFd::borrow_raw(val as _) }), } } @@ -73,20 +81,21 @@ fn get_rng_fd() -> Result { #[allow(unused_unsafe)] let fd = unsafe { open_readonly(FILE_PATH)? }; - #[cfg(feature = "rustix")] + #[cfg(target_os = "linux")] let fd = rustix::fd::IntoRawFd::into_raw_fd(fd); // The fd always fits in a usize without conflicting with FD_UNINIT. debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT); FD.store(fd as usize, Relaxed); - Ok(fd) + #[cfg(not(target_os = "linux"))] + return Ok(fd); + + #[cfg(target_os = "linux")] + return Ok(unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) }); } // Succeeds once /dev/urandom is safe to read from -#[cfg(all( - any(target_os = "android", target_os = "linux"), - not(feature = "rustix") -))] +#[cfg(not(target_os = "linux"))] fn wait_until_rng_ready() -> Result<(), Error> { // Poll /dev/random to make sure it is ok to read from /dev/urandom. let fd = unsafe { open_readonly("/dev/random\0")? }; @@ -115,7 +124,7 @@ fn wait_until_rng_ready() -> Result<(), Error> { } // Succeeds once /dev/urandom is safe to read from -#[cfg(all(any(target_os = "android", target_os = "linux"), feature = "rustix"))] +#[cfg(target_os = "linux")] fn wait_until_rng_ready() -> Result<(), Error> { use rustix::event;