diff options
| author | Tobias Bucher <tobiasbucher5991@gmail.com> | 2018-08-26 21:13:44 +0200 |
|---|---|---|
| committer | Tobias Bucher <tobiasbucher5991@gmail.com> | 2018-08-26 21:22:54 +0200 |
| commit | 09a615c09761d5066b19d4b58a6a1d0e515ede45 (patch) | |
| tree | f8153ed4abd6ee15c1f314d38d0460572ca69619 /src/libstd/sys/unix/rand.rs | |
| parent | caed80ba4ba8d9f4d3fa8aa9af6c4092d779cd9d (diff) | |
| download | rust-09a615c09761d5066b19d4b58a6a1d0e515ede45.tar.gz rust-09a615c09761d5066b19d4b58a6a1d0e515ede45.zip | |
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.
Diffstat (limited to 'src/libstd/sys/unix/rand.rs')
| -rw-r--r-- | src/libstd/sys/unix/rand.rs | 96 |
1 files changed, 57 insertions, 39 deletions
diff --git a/src/libstd/sys/unix/rand.rs b/src/libstd/sys/unix/rand.rs index 01c0ada4ffb..857d0370c29 100644 --- a/src/libstd/sys/unix/rand.rs +++ b/src/libstd/sys/unix/rand.rs @@ -30,8 +30,23 @@ mod imp { use fs::File; use io::Read; use libc; + use sync::atomic::{AtomicBool, AtomicI32, Ordering}; 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"))] fn getrandom(buf: &mut [u8]) -> libc::c_long { unsafe { @@ -40,9 +55,14 @@ mod imp { } #[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 { + if is_getrandom_permanently_unavailable() { + return false; + } + let mut read = 0; while read < v.len() { let result = getrandom(&mut v[read..]); @@ -50,8 +70,10 @@ mod imp { let err = errno() as libc::c_int; if err == libc::EINTR { continue; + } else if err == libc::ENOSYS { + GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed); } else if err == libc::EAGAIN { - return false + return false; } else { panic!("unexpected getrandom error: {}", err); } @@ -59,52 +81,48 @@ mod imp { read += result as usize; } } - - return true + 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]) { // getrandom_fill_bytes here can fail if getrandom() returns EAGAIN, // 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 // 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 - // over a completely full entropy pool - if is_getrandom_available() && getrandom_fill_bytes(v) { - return + // work around. The PRNG of /dev/urandom will still be used but over a + // possibly predictable entropy pool. + if getrandom_fill_bytes(v) { + return; } - let mut file = File::open("/dev/urandom") - .expect("failed to open /dev/urandom"); - file.read_exact(v).expect("failed to read /dev/urandom"); + // getrandom failed for some reason. If the getrandom call is + // permanently unavailable (OS without getrandom, or OS version without + // 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"); } } |
