]> git.lizzy.rs Git - rust.git/commitdiff
Reduce number of syscalls in `rand`
authorTobias Bucher <tobiasbucher5991@gmail.com>
Sun, 26 Aug 2018 19:13:44 +0000 (21:13 +0200)
committerTobias Bucher <tobiasbucher5991@gmail.com>
Sun, 26 Aug 2018 19:22:54 +0000 (21:22 +0200)
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.

src/libstd/sys/unix/rand.rs

index 01c0ada4ffbe531f227fbaec9858831bb56b6367..857d0370c299be514c1527e3275cff06b27620e8 100644 (file)
@@ -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 @@ fn getrandom(buf: &mut [u8]) -> libc::c_long {
     }
 
     #[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 @@ fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
                 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 @@ fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
                 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");
     }
 }