Skip to content

Commit

Permalink
Remove TLS by using lazy_static
Browse files Browse the repository at this point in the history
  • Loading branch information
josephlr committed Jun 11, 2019
1 parent ce4a089 commit 70f7fad
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 198 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ log = { version = "0.4", optional = true }

[target.'cfg(unix)'.dependencies]
libc = "0.2.29"
lazy_static = "1.3.0"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.6", features = ["minwindef", "ntsecapi", "winnt"] }
Expand All @@ -32,9 +33,13 @@ cloudabi = "0.0.3"
[target.'cfg(fuchsia)'.dependencies]
fuchsia-cprng = "0.1"

[target.'cfg(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"

[target.wasm32-wasi.dependencies]
libc = "0.2.54"
Expand Down
16 changes: 2 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,8 @@ macro_rules! error { ($($x:tt)*) => () }
#[cfg(target_arch = "wasm32")]
extern crate std;

#[cfg(any(
target_os = "android",
target_os = "netbsd",
target_os = "solaris",
target_os = "illumos",
target_os = "redox",
target_os = "dragonfly",
target_os = "haiku",
target_os = "linux",
all(
target_arch = "wasm32",
not(target_os = "wasi")
),
))]
#[cfg(any(unix, target_os = "redox"))]
#[allow(unused)]
mod utils;
mod error;
pub use crate::error::Error;
Expand Down
69 changes: 19 additions & 50 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,30 @@
//! Implementation for Linux / Android
extern crate std;

use crate::Error;
use crate::utils::use_init;
use std::{thread_local, io::{self, Read}, fs::File};
use core::cell::RefCell;
use crate::{utils::use_file, Error};
use core::num::NonZeroU32;
use core::sync::atomic::{AtomicBool, Ordering};
use std::io;
use lazy_static::lazy_static;

// This flag tells getrandom() to return EAGAIN instead of blocking.
const GRND_NONBLOCK: libc::c_uint = 0x0001;
static RNG_INIT: AtomicBool = AtomicBool::new(false);

enum RngSource {
GetRandom,
Device(File),
File,
}

thread_local!(
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
);
lazy_static! {
static ref RNG_SOURCE: RngSource = {
let mut buf: [u8; 0] = [];
if let Err(err) = syscall_getrandom(&mut buf, false) {
if err.raw_os_error() == Some(libc::ENOSYS) {
return RngSource::File;
}
}
RngSource::GetRandom
};
}

fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<(), io::Error> {
let flags = if block { 0 } else { GRND_NONBLOCK };
Expand All @@ -42,46 +47,10 @@ fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<(), io::Error> {
}

pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_SOURCE.with(|f| {
use_init(f,
|| {
let s = if is_getrandom_available() {
RngSource::GetRandom
} else {
// read one byte from "/dev/random" to ensure that
// OS RNG has initialized
if !RNG_INIT.load(Ordering::Relaxed) {
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?;
RNG_INIT.store(true, Ordering::Relaxed)
}
RngSource::Device(File::open("/dev/urandom")?)
};
Ok(s)
}, |f| {
match f {
RngSource::GetRandom => syscall_getrandom(dest, true),
RngSource::Device(f) => f.read_exact(dest),
}.map_err(From::from)
})
})
}

fn is_getrandom_available() -> bool {
use std::sync::{Once, ONCE_INIT};

static CHECKER: Once = ONCE_INIT;
static AVAILABLE: AtomicBool = AtomicBool::new(false);

CHECKER.call_once(|| {
let mut buf: [u8; 0] = [];
let available = match syscall_getrandom(&mut buf, false) {
Ok(()) => true,
Err(err) => err.raw_os_error() != Some(libc::ENOSYS),
};
AVAILABLE.store(available, Ordering::Relaxed);
});

AVAILABLE.load(Ordering::Relaxed)
match *RNG_SOURCE {
RngSource::GetRandom => syscall_getrandom(dest, true).map_err(From::from),
RngSource::File => use_file(None, dest),
}
}

#[inline(always)]
Expand Down
75 changes: 25 additions & 50 deletions src/solaris_illumos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
//! libc::dlsym.
extern crate std;

use crate::Error;
use crate::utils::use_init;
use std::{thread_local, io::{self, Read}, fs::File};
use core::cell::RefCell;
use crate::{utils::use_file, Error};
use core::mem;
use core::num::NonZeroU32;
use std::io;
use lazy_static::lazy_static;

#[cfg(target_os = "illumos")]
type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t;
Expand All @@ -32,12 +32,21 @@ type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) ->

enum RngSource {
GetRandom(GetRandomFn),
Device(File),
File,
}

thread_local!(
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
);
lazy_static! {
static ref RNG_SOURCE: RngSource = {
let name = "getrandom\0";
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) };

if addr.is_null() {
RngSource::File
} else {
RngSource::GetRandom(unsafe { mem::transmute(addr) })
}
};
}

fn libc_getrandom(rand: GetRandomFn, dest: &mut [u8]) -> Result<(), Error> {
let ret = unsafe { rand(dest.as_mut_ptr(), dest.len(), 0) as libc::ssize_t };
Expand All @@ -53,49 +62,15 @@ fn libc_getrandom(rand: GetRandomFn, dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
// 256 bytes is the lowest common denominator across all the Solaris
// derived platforms for atomically obtaining random data.
RNG_SOURCE.with(|f| {
use_init(
f,
|| {
let s = match fetch_getrandom() {
Some(fptr) => RngSource::GetRandom(fptr),
None => RngSource::Device(File::open("/dev/random")?),
};
Ok(s)
},
|f| {
match f {
RngSource::GetRandom(rp) => {
for chunk in dest.chunks_mut(256) {
libc_getrandom(*rp, chunk)?
}
}
RngSource::Device(randf) => {
for chunk in dest.chunks_mut(256) {
randf.read_exact(chunk)?
}
}
};
Ok(())
},
)
})
}

fn fetch_getrandom() -> Option<GetRandomFn> {
use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};

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);
match *RNG_SOURCE {
RngSource::GetRandom(rp) => {
for chunk in dest.chunks_mut(256) {
libc_getrandom(rp, chunk)?
}
Ok(())
}
RngSource::File => use_file(Some(256), dest),
}

let ptr = FPTR.load(Ordering::SeqCst);
unsafe { mem::transmute::<usize, Option<GetRandomFn>>(ptr) }
}

#[inline(always)]
Expand Down
41 changes: 5 additions & 36 deletions src/use_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,19 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Implementation for DragonFly / Haiku
//! Implementations that just need to read from a file
extern crate std;

use crate::Error;
use crate::utils::use_init;
use std::{thread_local, io::Read, fs::File};
use core::cell::RefCell;
use crate::{utils::use_file, Error};
use core::num::NonZeroU32;

thread_local!(static RNG_FILE: RefCell<Option<File>> = RefCell::new(None));

#[cfg(target_os = "redox")]
const FILE_PATH: &str = "rand:";
#[cfg(target_os = "netbsd")]
const FILE_PATH: &str = "/dev/urandom";
#[cfg(any(target_os = "dragonfly", target_os = "emscripten", target_os = "haiku"))]
const FILE_PATH: &str = "/dev/random";

pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_FILE.with(|f| {
use_init(f, || init_file(), |f| use_file(f, dest))
})
}

fn use_file(f: &mut File, dest: &mut [u8]) -> Result<(), Error> {
// `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes.
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)?;
}
use_file(Some(65536), dest)
} else {
f.read_exact(dest)?;
}
core::mem::forget(f);
Ok(())
}

fn init_file() -> Result<File, Error> {
if cfg!(target_os = "netbsd") {
// read one byte from "/dev/random" to ensure that OS RNG has initialized
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?;
use_file(None, dest)
}
let f = File::open(FILE_PATH)?;
Ok(f)
}

#[inline(always)]
Expand Down
54 changes: 36 additions & 18 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,44 @@
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Helper function for reading random data from a file.
extern crate std;

use crate::Error;
use core::cell::RefCell;
use core::ops::DerefMut;
use std::{
fs::File,
io::Read,
os::unix::io::{FromRawFd, IntoRawFd, RawFd},
};
use lazy_static::lazy_static;

/// If `f` contains `Some(T)` call `use_f` using contents of `f` as an argument,
/// otherwise initialize `f` value using `init_f`, store resulting value in `f`
/// and call `use_f`.
pub(crate) fn use_init<T, F, U>(f: &RefCell<Option<T>>, init_f: F, mut use_f: U)
-> Result<(), Error>
where F: FnOnce() -> Result<T, Error>, U: FnMut(&mut T) -> Result<(), Error>
{
let mut f = f.borrow_mut();
let f: &mut Option<T> = f.deref_mut();
match f {
None => *f = Some(init_f()?),
_ => (),
}
#[cfg(target_os = "redox")]
const FILE_PATH: &str = "rand:";
#[cfg(any(target_os = "netbsd", target_os = "linux", target_os = "android", target_os = "solaris", target_os = "illumos"))]
const FILE_PATH: &str = "/dev/urandom";
#[cfg(any(target_os = "dragonfly", target_os = "emscripten", target_os = "haiku"))]
const FILE_PATH: &str = "/dev/random";

lazy_static! {
static ref RNG_FILE: Result<RawFd, Error> = {
if cfg!(target_os = "netbsd") || cfg!(target_os = "linux") || cfg!(target_os = "android") {
// read one byte from "/dev/random" to ensure that OS RNG has initialized
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?;
}
Ok(File::open(FILE_PATH)?.into_raw_fd())
};
}

pub(crate) fn use_file(chunk_len: Option<usize>, dest: &mut [u8]) -> Result<(), Error> {
let mut f = unsafe { File::from_raw_fd((*RNG_FILE)?) };

match f {
Some(f) => use_f(f),
None => unreachable!(),
if let Some(chunk_len) = chunk_len {
for chunk in dest.chunks_mut(chunk_len) {
f.read_exact(chunk)?;
}
} else {
f.read_exact(dest)?;
}
Ok(())
}
Loading

0 comments on commit 70f7fad

Please sign in to comment.