diff --git a/.travis.yml b/.travis.yml index b3759426..7161f697 100644 --- a/.travis.yml +++ b/.travis.yml @@ -102,13 +102,14 @@ matrix: - rust: nightly env: DESCRIPTION="cross-platform build only" + # Redox: wait for https://github.com/rust-lang/rust/issues/60139 install: - rustup target add x86_64-sun-solaris - rustup target add x86_64-unknown-cloudabi - rustup target add x86_64-unknown-freebsd - rustup target add x86_64-fuchsia - rustup target add x86_64-unknown-netbsd - - rustup target add x86_64-unknown-redox + # - rustup target add x86_64-unknown-redox - rustup target add x86_64-fortanix-unknown-sgx # For no_std targets - rustup component add rust-src @@ -119,7 +120,7 @@ matrix: - cargo build --target=x86_64-unknown-freebsd --all-features - cargo build --target=x86_64-fuchsia --all-features - cargo build --target=x86_64-unknown-netbsd --all-features - - cargo build --target=x86_64-unknown-redox --all-features + # - cargo build --target=x86_64-unknown-redox --all-features - cargo build --target=x86_64-fortanix-unknown-sgx --all-features - cargo xbuild --target=x86_64-unknown-uefi # also test minimum dependency versions are usable @@ -129,7 +130,7 @@ matrix: - cargo build --target=x86_64-unknown-freebsd --all-features - cargo build --target=x86_64-fuchsia --all-features - cargo build --target=x86_64-unknown-netbsd --all-features - - cargo build --target=x86_64-unknown-redox --all-features + # - cargo build --target=x86_64-unknown-redox --all-features - cargo build --target=x86_64-fortanix-unknown-sgx --all-features - cargo xbuild --target=x86_64-unknown-uefi diff --git a/Cargo.toml b/Cargo.toml index 9a4be7d0..2e6a93a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,17 +20,12 @@ members = ["tests/wasm_bindgen"] [dependencies] log = { version = "0.4", optional = true } -[target.'cfg(any(unix, target_os = "wasi"))'.dependencies] +[target.'cfg(any(unix, target_os = "redox", target_os = "wasi"))'.dependencies] libc = "0.2.54" -# For holding file descriptors -[target.'cfg(any(unix, target_os = "redox"))'.dependencies] -lazy_static = "1.3.0" - [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = { version = "0.2.29", optional = true } stdweb = { version = "0.4.9", optional = true } -lazy_static = "1.3.0" [features] std = [] diff --git a/src/lib.rs b/src/lib.rs index e2278696..e279baa5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,11 +142,11 @@ extern crate std; mod error; pub use crate::error::Error; + #[allow(dead_code)] mod util; - -// These targets need weak linkage to libc randomness functions. -#[cfg(any(target_os = "macos", target_os = "solaris", target_os = "illumos"))] +#[cfg(any(unix, target_os = "redox"))] +#[allow(dead_code)] mod util_libc; // System-specific implementations. diff --git a/src/use_file.rs b/src/use_file.rs index 5437a17a..adc1b3cd 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -9,10 +9,15 @@ //! Implementations that just need to read from a file extern crate std; +use crate::util_libc::LazyFd; use crate::Error; +use core::mem::ManuallyDrop; use core::num::NonZeroU32; -use lazy_static::lazy_static; -use std::{fs::File, io::Read}; +use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; +use std::{ + fs::File, + io::{self, Read}, +}; #[cfg(target_os = "redox")] const FILE_PATH: &str = "rand:"; @@ -29,28 +34,31 @@ const FILE_PATH: &str = "/dev/urandom"; const FILE_PATH: &str = "/dev/random"; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - lazy_static! { - static ref FILE: Result = init_file(); - } - let mut f = FILE.as_ref()?; + static FD: LazyFd = LazyFd::new(); + let fd = FD.init(init_file).ok_or(io::Error::last_os_error())?; + let file = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }); + let mut file_ref: &File = &file; if cfg!(target_os = "emscripten") { // `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes. for chunk in dest.chunks_mut(65536) { - f.read_exact(chunk)?; + file_ref.read_exact(chunk)?; } } else { - f.read_exact(dest)?; + file_ref.read_exact(dest)?; } Ok(()) } -fn init_file() -> Result { +fn init_file() -> Option { if FILE_PATH == "/dev/urandom" { // read one byte from "/dev/random" to ensure that OS RNG has initialized - File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; + File::open("/dev/random") + .ok()? + .read_exact(&mut [0u8; 1]) + .ok()?; } - Ok(File::open(FILE_PATH)?) + Some(File::open(FILE_PATH).ok()?.into_raw_fd()) } #[inline(always)] diff --git a/src/util.rs b/src/util.rs index 70adfa53..a0675be2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,10 +6,26 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; // This structure represents a laziliy initialized static usize value. Useful // when it is perferable to just rerun initialization instead of locking. +// Both unsync_init and sync_init will invoke an init() function until it +// succeeds, then return the cached value for future calls. +// +// Both methods support init() "failing". If the init() method returns UNINIT, +// that value will be returned as normal, but will not be cached. +// +// Users should only depend on the _value_ returned by init() functions. +// Specifically, for the following init() function: +// fn init() -> usize { +// a(); +// let v = b(); +// c(); +// v +// } +// the effects of c() or writes to shared memory will not necessarily be +// observed and additional syncronization methods with be needed. pub struct LazyUsize(AtomicUsize); impl LazyUsize { @@ -19,20 +35,44 @@ impl LazyUsize { // The initialization is not completed. pub const UNINIT: usize = usize::max_value(); + // The initialization is currently running. + pub const ACTIVE: usize = usize::max_value() - 1; // Runs the init() function at least once, returning the value of some run - // of init(). Unlike std::sync::Once, the init() function may be run - // multiple times. If init() returns UNINIT, future calls to unsync_init() - // will always retry. This makes UNINIT ideal for representing failure. + // of init(). Multiple callers can run their init() functions in parallel. + // init() should always return the same value, if it succeeds. pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { // Relaxed ordering is fine, as we only have a single atomic variable. - let mut val = self.0.load(Ordering::Relaxed); + let mut val = self.0.load(Relaxed); if val == Self::UNINIT { val = init(); - self.0.store(val, Ordering::Relaxed); + self.0.store(val, Relaxed); } val } + + // Synchronously runs the init() function. Only one caller will have their + // init() function running at a time, and exactly one successful call will + // be run. The init() function should never return LazyUsize::ACTIVE. + pub fn sync_init(&self, init: impl FnOnce() -> usize, mut wait: impl FnMut()) -> usize { + // Common and fast path with no contention. Don't wast time on CAS. + match self.0.load(Relaxed) { + Self::UNINIT | Self::ACTIVE => {} + val => return val, + } + // Relaxed ordering is fine, as we only have a single atomic variable. + loop { + match self.0.compare_and_swap(Self::UNINIT, Self::ACTIVE, Relaxed) { + Self::UNINIT => { + let val = init(); + self.0.store(val, Relaxed); + return val; + } + Self::ACTIVE => wait(), + val => return val, + } + } + } } // Identical to LazyUsize except with bool instead of usize. diff --git a/src/util_libc.rs b/src/util_libc.rs index 049fe88b..5554079b 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -36,3 +36,29 @@ impl Weak { NonNull::new(addr as *mut _) } } + +pub struct LazyFd(LazyUsize); + +impl LazyFd { + pub const fn new() -> Self { + Self(LazyUsize::new()) + } + + // If init() returns Some(x), x should be nonnegative. + pub fn init(&self, init: impl FnOnce() -> Option) -> Option { + let fd = self.0.sync_init( + || match init() { + // OK as val >= 0 and val <= c_int::MAX < usize::MAX + Some(val) => val as usize, + None => LazyUsize::UNINIT, + }, + || unsafe { + libc::usleep(1000); + }, + ); + match fd { + LazyUsize::UNINIT => None, + val => Some(val as libc::c_int), + } + } +} diff --git a/src/wasm32_stdweb.rs b/src/wasm32_stdweb.rs index aae12eb4..32a5686c 100644 --- a/src/wasm32_stdweb.rs +++ b/src/wasm32_stdweb.rs @@ -15,7 +15,7 @@ use stdweb::web::error::Error as WebError; use stdweb::{_js_impl, js}; use crate::Error; -use lazy_static::lazy_static; +use std::sync::Once; #[derive(Clone, Copy, Debug)] enum RngSource { @@ -25,11 +25,14 @@ enum RngSource { pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { assert_eq!(mem::size_of::(), 4); - - lazy_static! { - static ref RNG_SOURCE: Result = getrandom_init(); - } - getrandom_fill((*RNG_SOURCE)?, dest) + static ONCE: Once = Once::new(); + static mut RNG_SOURCE: Result = Err(Error::UNAVAILABLE); + + // SAFETY: RNG_SOURCE is only written once, before being read. + ONCE.call_once(|| unsafe { + RNG_SOURCE = getrandom_init(); + }); + getrandom_fill(unsafe { RNG_SOURCE }?, dest) } fn getrandom_init() -> Result {