Reduce number of syscalls in rand
In case that it is statically known that the OS doesn't support
`getrandom` (non-Linux) or becomes clear at runtime that `getrandom`
isn't available (`ENOSYS`), the opened fd ("/dev/urandom") isn't closed
after the function, so that future calls can reuse it. This saves
repeated `open`/`close` system calls at the cost of one permanently open
fd.
Additionally, this skips the initial zero-length `getrandom` call and
directly hands the user buffer to the operating system, saving one
`getrandom` syscall.
This commit is contained in:
@@ -30,8 +30,23 @@ mod imp {
|
|||||||
use fs::File;
|
use fs::File;
|
||||||
use io::Read;
|
use io::Read;
|
||||||
use libc;
|
use libc;
|
||||||
|
use sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||||
use sys::os::errno;
|
use sys::os::errno;
|
||||||
|
|
||||||
|
static GETRANDOM_URANDOM_FD: AtomicI32 = AtomicI32::new(-1);
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
fn is_getrandom_permanently_unavailable() -> bool {
|
||||||
|
GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
|
fn is_getrandom_permanently_unavailable() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
fn getrandom(buf: &mut [u8]) -> libc::c_long {
|
fn getrandom(buf: &mut [u8]) -> libc::c_long {
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -40,9 +55,14 @@ mod imp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
|
fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool { false }
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
|
fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
|
||||||
|
if is_getrandom_permanently_unavailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let mut read = 0;
|
let mut read = 0;
|
||||||
while read < v.len() {
|
while read < v.len() {
|
||||||
let result = getrandom(&mut v[read..]);
|
let result = getrandom(&mut v[read..]);
|
||||||
@@ -50,8 +70,10 @@ mod imp {
|
|||||||
let err = errno() as libc::c_int;
|
let err = errno() as libc::c_int;
|
||||||
if err == libc::EINTR {
|
if err == libc::EINTR {
|
||||||
continue;
|
continue;
|
||||||
|
} else if err == libc::ENOSYS {
|
||||||
|
GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed);
|
||||||
} else if err == libc::EAGAIN {
|
} else if err == libc::EAGAIN {
|
||||||
return false
|
return false;
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected getrandom error: {}", err);
|
panic!("unexpected getrandom error: {}", err);
|
||||||
}
|
}
|
||||||
@@ -59,52 +81,48 @@ mod imp {
|
|||||||
read += result as usize;
|
read += result as usize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
fn is_getrandom_available() -> bool {
|
|
||||||
use io;
|
|
||||||
use sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use sync::Once;
|
|
||||||
|
|
||||||
static CHECKER: Once = Once::new();
|
|
||||||
static AVAILABLE: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
CHECKER.call_once(|| {
|
|
||||||
let mut buf: [u8; 0] = [];
|
|
||||||
let result = getrandom(&mut buf);
|
|
||||||
let available = if result == -1 {
|
|
||||||
let err = io::Error::last_os_error().raw_os_error();
|
|
||||||
err != Some(libc::ENOSYS)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
|
||||||
AVAILABLE.store(available, Ordering::Relaxed);
|
|
||||||
});
|
|
||||||
|
|
||||||
AVAILABLE.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
||||||
fn is_getrandom_available() -> bool { false }
|
|
||||||
|
|
||||||
pub fn fill_bytes(v: &mut [u8]) {
|
pub fn fill_bytes(v: &mut [u8]) {
|
||||||
// getrandom_fill_bytes here can fail if getrandom() returns EAGAIN,
|
// getrandom_fill_bytes here can fail if getrandom() returns EAGAIN,
|
||||||
// meaning it would have blocked because the non-blocking pool (urandom)
|
// meaning it would have blocked because the non-blocking pool (urandom)
|
||||||
// has not initialized in the kernel yet due to a lack of entropy the
|
// has not initialized in the kernel yet due to a lack of entropy. The
|
||||||
// fallback we do here is to avoid blocking applications which could
|
// fallback we do here is to avoid blocking applications which could
|
||||||
// depend on this call without ever knowing they do and don't have a
|
// depend on this call without ever knowing they do and don't have a
|
||||||
// work around. The PRNG of /dev/urandom will still be used but not
|
// work around. The PRNG of /dev/urandom will still be used but over a
|
||||||
// over a completely full entropy pool
|
// possibly predictable entropy pool.
|
||||||
if is_getrandom_available() && getrandom_fill_bytes(v) {
|
if getrandom_fill_bytes(v) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = File::open("/dev/urandom")
|
// getrandom failed for some reason. If the getrandom call is
|
||||||
.expect("failed to open /dev/urandom");
|
// permanently unavailable (OS without getrandom, or OS version without
|
||||||
file.read_exact(v).expect("failed to read /dev/urandom");
|
// getrandom), we'll keep around the fd for /dev/urandom for future
|
||||||
|
// requests, to avoid re-opening the file on every call.
|
||||||
|
//
|
||||||
|
// Otherwise, open /dev/urandom, read from it, and close it again.
|
||||||
|
use super::super::ext::io::{FromRawFd, IntoRawFd};
|
||||||
|
let mut fd = GETRANDOM_URANDOM_FD.load(Ordering::Relaxed);
|
||||||
|
let mut close_fd = false;
|
||||||
|
if fd == -1 {
|
||||||
|
if !is_getrandom_permanently_unavailable() {
|
||||||
|
close_fd = true;
|
||||||
|
}
|
||||||
|
let file = File::open("/dev/urandom").expect("failed to open /dev/urandom");
|
||||||
|
fd = file.into_raw_fd();
|
||||||
|
// If some other thread also opened /dev/urandom and set the global
|
||||||
|
// fd already, we close our fd at the end of the function.
|
||||||
|
if !close_fd && GETRANDOM_URANDOM_FD.compare_and_swap(-1, fd, Ordering::Relaxed) != -1 {
|
||||||
|
close_fd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut file = unsafe { File::from_raw_fd(fd) };
|
||||||
|
let res = file.read_exact(v);
|
||||||
|
if !close_fd {
|
||||||
|
let _ = file.into_raw_fd();
|
||||||
|
}
|
||||||
|
res.expect("failed to read /dev/urandom");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user