Skip to content

Commit 09a615c

Browse files
committed
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.
1 parent caed80b commit 09a615c

File tree

1 file changed

+57
-39
lines changed

1 file changed

+57
-39
lines changed

src/libstd/sys/unix/rand.rs

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,23 @@ mod imp {
3030
use fs::File;
3131
use io::Read;
3232
use libc;
33+
use sync::atomic::{AtomicBool, AtomicI32, Ordering};
3334
use sys::os::errno;
3435

36+
static GETRANDOM_URANDOM_FD: AtomicI32 = AtomicI32::new(-1);
37+
#[cfg(any(target_os = "linux", target_os = "android"))]
38+
static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
39+
40+
#[cfg(any(target_os = "linux", target_os = "android"))]
41+
fn is_getrandom_permanently_unavailable() -> bool {
42+
GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed)
43+
}
44+
45+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
46+
fn is_getrandom_permanently_unavailable() -> bool {
47+
true
48+
}
49+
3550
#[cfg(any(target_os = "linux", target_os = "android"))]
3651
fn getrandom(buf: &mut [u8]) -> libc::c_long {
3752
unsafe {
@@ -40,71 +55,74 @@ mod imp {
4055
}
4156

4257
#[cfg(not(any(target_os = "linux", target_os = "android")))]
43-
fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
58+
fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool { false }
4459

60+
#[cfg(any(target_os = "linux", target_os = "android"))]
4561
fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
62+
if is_getrandom_permanently_unavailable() {
63+
return false;
64+
}
65+
4666
let mut read = 0;
4767
while read < v.len() {
4868
let result = getrandom(&mut v[read..]);
4969
if result == -1 {
5070
let err = errno() as libc::c_int;
5171
if err == libc::EINTR {
5272
continue;
73+
} else if err == libc::ENOSYS {
74+
GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed);
5375
} else if err == libc::EAGAIN {
54-
return false
76+
return false;
5577
} else {
5678
panic!("unexpected getrandom error: {}", err);
5779
}
5880
} else {
5981
read += result as usize;
6082
}
6183
}
62-
63-
return true
84+
true
6485
}
6586

66-
#[cfg(any(target_os = "linux", target_os = "android"))]
67-
fn is_getrandom_available() -> bool {
68-
use io;
69-
use sync::atomic::{AtomicBool, Ordering};
70-
use sync::Once;
71-
72-
static CHECKER: Once = Once::new();
73-
static AVAILABLE: AtomicBool = AtomicBool::new(false);
74-
75-
CHECKER.call_once(|| {
76-
let mut buf: [u8; 0] = [];
77-
let result = getrandom(&mut buf);
78-
let available = if result == -1 {
79-
let err = io::Error::last_os_error().raw_os_error();
80-
err != Some(libc::ENOSYS)
81-
} else {
82-
true
83-
};
84-
AVAILABLE.store(available, Ordering::Relaxed);
85-
});
86-
87-
AVAILABLE.load(Ordering::Relaxed)
88-
}
89-
90-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
91-
fn is_getrandom_available() -> bool { false }
92-
9387
pub fn fill_bytes(v: &mut [u8]) {
9488
// getrandom_fill_bytes here can fail if getrandom() returns EAGAIN,
9589
// meaning it would have blocked because the non-blocking pool (urandom)
96-
// has not initialized in the kernel yet due to a lack of entropy the
90+
// has not initialized in the kernel yet due to a lack of entropy. The
9791
// fallback we do here is to avoid blocking applications which could
9892
// depend on this call without ever knowing they do and don't have a
99-
// work around. The PRNG of /dev/urandom will still be used but not
100-
// over a completely full entropy pool
101-
if is_getrandom_available() && getrandom_fill_bytes(v) {
102-
return
93+
// work around. The PRNG of /dev/urandom will still be used but over a
94+
// possibly predictable entropy pool.
95+
if getrandom_fill_bytes(v) {
96+
return;
10397
}
10498

105-
let mut file = File::open("/dev/urandom")
106-
.expect("failed to open /dev/urandom");
107-
file.read_exact(v).expect("failed to read /dev/urandom");
99+
// getrandom failed for some reason. If the getrandom call is
100+
// permanently unavailable (OS without getrandom, or OS version without
101+
// getrandom), we'll keep around the fd for /dev/urandom for future
102+
// requests, to avoid re-opening the file on every call.
103+
//
104+
// Otherwise, open /dev/urandom, read from it, and close it again.
105+
use super::super::ext::io::{FromRawFd, IntoRawFd};
106+
let mut fd = GETRANDOM_URANDOM_FD.load(Ordering::Relaxed);
107+
let mut close_fd = false;
108+
if fd == -1 {
109+
if !is_getrandom_permanently_unavailable() {
110+
close_fd = true;
111+
}
112+
let file = File::open("/dev/urandom").expect("failed to open /dev/urandom");
113+
fd = file.into_raw_fd();
114+
// If some other thread also opened /dev/urandom and set the global
115+
// fd already, we close our fd at the end of the function.
116+
if !close_fd && GETRANDOM_URANDOM_FD.compare_and_swap(-1, fd, Ordering::Relaxed) != -1 {
117+
close_fd = true;
118+
}
119+
}
120+
let mut file = unsafe { File::from_raw_fd(fd) };
121+
let res = file.read_exact(v);
122+
if !close_fd {
123+
let _ = file.into_raw_fd();
124+
}
125+
res.expect("failed to read /dev/urandom");
108126
}
109127
}
110128

0 commit comments

Comments
 (0)