Merge pull request #742 from dhardy/pcg

rand_pcg: prepare 0.1.2 release with i128 auto-detection
diff --git a/rand_os/src/lib.rs b/rand_os/src/lib.rs
index 907c629..1b9cb25 100644
--- a/rand_os/src/lib.rs
+++ b/rand_os/src/lib.rs
@@ -283,7 +283,8 @@
 #[cfg(any(target_os = "linux", target_os = "android",
           target_os = "netbsd", target_os = "dragonfly",
           target_os = "solaris", target_os = "redox",
-          target_os = "haiku", target_os = "emscripten"))]
+          target_os = "haiku", target_os = "emscripten",
+          target_os = "illumos"))]
 mod random_device;
 
 macro_rules! mod_use {
@@ -309,7 +310,7 @@
 mod_use!(cfg(target_os = "netbsd"), netbsd);
 mod_use!(cfg(target_os = "openbsd"), openbsd_bitrig);
 mod_use!(cfg(target_os = "redox"), redox);
-mod_use!(cfg(target_os = "solaris"), solaris);
+mod_use!(cfg(any(target_os = "solaris", target_os = "illumos")), solarish);
 mod_use!(cfg(windows), windows);
 mod_use!(cfg(target_env = "sgx"), sgx);
 
@@ -376,6 +377,7 @@
     target_os = "openbsd",
     target_os = "redox",
     target_os = "solaris",
+    target_os = "illumos",
     windows,
     target_arch = "wasm32",
     target_env = "sgx"
diff --git a/rand_os/src/solaris.rs b/rand_os/src/solarish.rs
similarity index 69%
rename from rand_os/src/solaris.rs
rename to rand_os/src/solarish.rs
index e8965fd..952643f 100644
--- a/rand_os/src/solaris.rs
+++ b/rand_os/src/solarish.rs
@@ -28,8 +28,9 @@
 use std::io::Read;
 use std::fs::{File, OpenOptions};
 use std::os::unix::fs::OpenOptionsExt;
-use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
+use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering, AtomicUsize};
 use std::cmp;
+use std::mem;
 
 #[derive(Clone, Debug)]
 pub struct OsRng {
@@ -97,9 +98,10 @@
     }
 
     fn max_chunk_size(&self) -> usize {
-        // The documentation says 1024 is the maximum for getrandom, but
-        // 1040 for /dev/random.
-        1024
+        // This is the largest size that's guaranteed to not block across
+        // all the Solarish platforms, though some may allow for larger
+        // sizes.
+        256
     }
 
     fn method_str(&self) -> &'static str {
@@ -110,18 +112,48 @@
     }
 }
 
-fn getrandom(buf: &mut [u8], blocking: bool) -> libc::c_long {
-    extern "C" {
-        fn syscall(number: libc::c_long, ...) -> libc::c_long;
+#[cfg(target_os = "illumos")]
+type GetRandomFn = unsafe extern fn(*mut u8, libc::size_t, libc::c_uint)
+    -> libc::ssize_t;
+#[cfg(target_os = "solaris")]
+type GetRandomFn = unsafe extern fn(*mut u8, libc::size_t, libc::c_uint)
+    -> libc::c_int;
+
+// Use dlsym to determine if getrandom(2) is present in libc.  On Solarish
+// systems, the syscall interface is not stable and can change between
+// updates.  Even worse, issuing unsupported syscalls will cause the system
+// to generate a SIGSYS signal (usually terminating the program).
+// Instead the stable APIs are exposed via libc.  Cache the result of the
+// lookup for future calls.  This is loosely modeled after the
+// libstd::sys::unix::weak macro which unfortunately is not exported.
+fn fetch() -> Option<GetRandomFn> {
+    static FPTR: AtomicUsize = AtomicUsize::new(1);
+
+    if FPTR.load(Ordering::SeqCst) == 1 {
+        let name = "getrandom\0";
+        let addr = unsafe {
+            libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize
+        };
+        FPTR.store(addr, Ordering::SeqCst);
     }
 
-    const SYS_GETRANDOM: libc::c_long = 143;
+    let ptr = FPTR.load(Ordering::SeqCst);
+    unsafe {
+        mem::transmute::<usize, Option<GetRandomFn>>(ptr)
+    }
+}
+
+fn getrandom(buf: &mut [u8], blocking: bool) -> libc::ssize_t {
     const GRND_NONBLOCK: libc::c_uint = 0x0001;
     const GRND_RANDOM: libc::c_uint = 0x0002;
 
-    unsafe {
-        syscall(SYS_GETRANDOM, buf.as_mut_ptr(), buf.len(),
-                if blocking { 0 } else { GRND_NONBLOCK } | GRND_RANDOM)
+    if let Some(rand) = fetch() {
+        let flag = if blocking { 0 } else { GRND_NONBLOCK } | GRND_RANDOM;
+        unsafe {
+            rand(buf.as_mut_ptr(), buf.len(), flag) as libc::ssize_t
+        }
+    } else {
+        -1
     }
 }
 
@@ -143,7 +175,7 @@
                 err,
             ));
         }
-    } else if result != dest.len() as i64 {
+    } else if result != dest.len() as libc::ssize_t {
         return Err(Error::new(ErrorKind::Unavailable,
                               "unexpected getrandom error"));
     }
@@ -151,25 +183,10 @@
 }
 
 fn is_getrandom_available() -> bool {
-    use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
-    use std::sync::{Once, ONCE_INIT};
-
-    static CHECKER: Once = ONCE_INIT;
-    static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT;
-
-    CHECKER.call_once(|| {
-        debug!("OsRng: testing getrandom");
-        let mut buf: [u8; 0] = [];
-        let result = getrandom(&mut buf, false);
-        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);
-        info!("OsRng: using {}", if available { "getrandom" } else { "/dev/random" });
-    });
-
-    AVAILABLE.load(Ordering::Relaxed)
+    let available = match fetch() {
+        Some(_) => true,
+        None => false,
+    };
+    info!("OsRng: using {}", if available { "getrandom" } else { "/dev/random" });
+    available
 }