Skip to content

Commit 6e6f903

Browse files
committed
use_file: Use once_cell::sync::OnceCell instead of libpthreads.
pthreads mutexes are not safe to move. While it is very unlikely that the mutex we create will ever be moved, we don't actively do anything to actively prevent it from being moved. (libstd, when it used/uses pthreads mutexes, would box them to prevent them from being moved.) Also, now on Linux and Android (and many other targets for which we don't use use_std), libstd uses futexes instead of pthreads mutexes. Thus using libstd's Mutex will be more efficient and avoid adding an often-otherwise-unnecessary libpthreads dependency on these targets. * Linux, Android: Futex [1]. * Haiku, Redox, NTO, AIX: pthreads [2]. * others: not using `use_file`. This will not affect our plans for *-*-linux-none, since we don't plan to use `use_file` for it. This breaks 32-bit x86 QNX Neutrino, which doesn't have libstd because the target itself is abandoned [3]. the other QNX Neutrino targets didn't get libstd support until Rust 1.69, so this effectively raises the MSRV for them to 1.69. I tried to use `std::sync::Once` to avoid adding an external dependency but it doesn't support fallible initialization. `OnceLock` wasn't added until 1.70, and even then `OnceLock::get_or_try_init` is still unstable.
1 parent 5d8bbce commit 6e6f903

File tree

2 files changed

+37
-105
lines changed

2 files changed

+37
-105
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ wasi = { version = "0.11", default-features = false }
2727
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
2828
windows-targets = "0.52"
2929

30+
[target.'cfg(any(target_os = "aix", target_os = "android", target_os = "haiku", target_os = "linux", target_os = "nto", target_os = "redox"))'.dependencies]
31+
once_cell = { version = "1.19.0", default-features = false, features = ["std"]}
32+
3033
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
3134
wasm-bindgen = { version = "0.2.62", default-features = false, optional = true }
3235
js-sys = { version = "0.3", optional = true }

src/use_file.rs

+34-105
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,10 @@
33
extern crate std;
44

55
use crate::{util_libc::sys_fill_exact, Error};
6-
use core::{
7-
cell::UnsafeCell,
8-
ffi::c_void,
9-
mem::MaybeUninit,
10-
sync::atomic::{AtomicI32, Ordering},
11-
};
12-
use std::{
13-
fs, io,
14-
os::fd::{IntoRawFd as _, RawFd},
15-
};
6+
use core::{ffi::c_void, mem::MaybeUninit};
7+
use std::{fs::File, io, os::unix::io::AsRawFd as _};
8+
// TODO(MSRV feature(once_cell_try)): Use std::sync::OnceLock instead.
9+
use once_cell::sync::OnceCell as OnceLock;
1610

1711
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
1812
/// For more information see the linked man pages in lib.rs.
@@ -26,78 +20,41 @@ const FILE_PATH: &str = "/dev/urandom";
2620
// `#[cold]` because it is hot when it is actually used.
2721
#[cfg_attr(any(target_os = "android", target_os = "linux"), inline(never))]
2822
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
29-
let fd = get_rng_fd()?;
30-
sys_fill_exact(dest, |buf| unsafe {
31-
libc::read(fd, buf.as_mut_ptr().cast::<c_void>(), buf.len())
32-
})
33-
}
34-
35-
// Returns the file descriptor for the device file used to retrieve random
36-
// bytes. The file will be opened exactly once. All subsequent calls will
37-
// return the same file descriptor. This file descriptor is never closed.
38-
fn get_rng_fd() -> Result<RawFd, Error> {
39-
// std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor.
40-
const FD_UNINIT: RawFd = -1;
41-
42-
// In theory `RawFd` could be something other than `i32`, but for the
43-
// targets we currently support that use `use_file`, it is always `i32`.
44-
// If/when we add support for a target where that isn't the case, we may
45-
// need to use a different atomic type or make other accomodations. The
46-
// compiler will let us know if/when that is the case, because the
47-
// `FD.store(fd)` would fail to compile.
48-
//
4923
// The opening of the file, by libc/libstd/etc. may write some unknown
5024
// state into in-process memory. (Such state may include some sanitizer
5125
// bookkeeping, or we might be operating in a unikernal-like environment
5226
// where all the "kernel" file descriptor bookkeeping is done in our
53-
// process.) `get_fd_locked` stores into FD using `Ordering::Release` to
54-
// ensure any such state is synchronized. `get_fd` loads from `FD` with
55-
// `Ordering::Acquire` to synchronize with it.
56-
static FD: AtomicI32 = AtomicI32::new(FD_UNINIT);
57-
58-
fn get_fd() -> Option<RawFd> {
59-
match FD.load(Ordering::Acquire) {
60-
FD_UNINIT => None,
61-
val => Some(val),
62-
}
63-
}
64-
65-
#[cold]
66-
fn get_fd_locked() -> Result<RawFd, Error> {
67-
// This mutex is used to prevent multiple threads from opening file
68-
// descriptors concurrently, which could run into the limit on the
69-
// number of open file descriptors. Our goal is to have no more than one
70-
// file descriptor open, ever.
71-
//
72-
// SAFETY: We use the mutex only in this method, and we always unlock it
73-
// before returning, making sure we don't violate the pthread_mutex_t API.
74-
static MUTEX: Mutex = Mutex::new();
75-
unsafe { MUTEX.lock() };
76-
let _guard = DropGuard(|| unsafe { MUTEX.unlock() });
77-
78-
if let Some(fd) = get_fd() {
79-
return Ok(fd);
80-
}
81-
82-
// On Linux, /dev/urandom might return insecure values.
83-
#[cfg(any(target_os = "android", target_os = "linux"))]
84-
wait_until_rng_ready()?;
85-
86-
let file = fs::File::open(FILE_PATH).map_err(map_io_error)?;
87-
88-
let fd = file.into_raw_fd();
89-
debug_assert!(fd != FD_UNINIT);
90-
FD.store(fd, Ordering::Release);
27+
// process.) Thus we avoid using (relaxed) atomics like we use in other
28+
// parts of the library.
29+
//
30+
// We prevent multiple threads from opening file descriptors concurrently,
31+
// which could run into the limit on the number of open file descriptors.
32+
// Our goal is to have no more than one file descriptor open, ever.
33+
//
34+
// We assume any call to `OnceLock::get_or_try_init` synchronizes-with
35+
// (Ordering::Acquire) the preceding call to `OnceLock::get_or_try_init`
36+
// after `init()` returns an `Ok` result (Ordering::Release). See
37+
// https://github.com/rust-lang/rust/issues/126239.
38+
static FILE: OnceLock<File> = OnceLock::new();
39+
let file = FILE.get_or_try_init(init)?;
40+
41+
// TODO(MSRV feature(read_buf)): Use `std::io::Read::read_buf`
42+
sys_fill_exact(dest, |buf| unsafe {
43+
libc::read(
44+
file.as_raw_fd(),
45+
buf.as_mut_ptr().cast::<c_void>(),
46+
buf.len(),
47+
)
48+
})
49+
}
9150

92-
Ok(fd)
93-
}
51+
#[cold]
52+
fn init() -> Result<File, Error> {
53+
// On Linux, /dev/urandom might return insecure values.
54+
#[cfg(any(target_os = "android", target_os = "linux"))]
55+
wait_until_rng_ready()?;
9456

95-
// Use double-checked locking to avoid acquiring the lock if possible.
96-
if let Some(fd) = get_fd() {
97-
Ok(fd)
98-
} else {
99-
get_fd_locked()
100-
}
57+
File::open(FILE_PATH).map_err(map_io_error)
10158
}
10259

10360
// Polls /dev/random to make sure it is ok to read from /dev/urandom.
@@ -130,9 +87,7 @@ fn get_rng_fd() -> Result<RawFd, Error> {
13087
// libsodium uses `libc::poll` similarly to this.
13188
#[cfg(any(target_os = "android", target_os = "linux"))]
13289
fn wait_until_rng_ready() -> Result<(), Error> {
133-
use std::os::unix::io::AsRawFd as _;
134-
135-
let file = fs::File::open("/dev/random").map_err(map_io_error)?;
90+
let file = File::open("/dev/random").map_err(map_io_error)?;
13691
let mut pfd = libc::pollfd {
13792
fd: file.as_raw_fd(),
13893
events: libc::POLLIN,
@@ -171,29 +126,3 @@ fn map_io_error(err: io::Error) -> Error {
171126
}
172127
})
173128
}
174-
175-
struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
176-
177-
impl Mutex {
178-
const fn new() -> Self {
179-
Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))
180-
}
181-
unsafe fn lock(&self) {
182-
let r = libc::pthread_mutex_lock(self.0.get());
183-
debug_assert_eq!(r, 0);
184-
}
185-
unsafe fn unlock(&self) {
186-
let r = libc::pthread_mutex_unlock(self.0.get());
187-
debug_assert_eq!(r, 0);
188-
}
189-
}
190-
191-
unsafe impl Sync for Mutex {}
192-
193-
struct DropGuard<F: FnMut()>(F);
194-
195-
impl<F: FnMut()> Drop for DropGuard<F> {
196-
fn drop(&mut self) {
197-
self.0()
198-
}
199-
}

0 commit comments

Comments
 (0)