Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

netbsd: Simplify weak lookup. #484

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 1 addition & 59 deletions src/lazy.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
//! Helpers built around pointer-sized atomics.
#![cfg(target_has_atomic = "ptr")]
#![allow(dead_code)]
use core::{
ffi::c_void,
sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
};
use core::sync::atomic::{AtomicUsize, Ordering};

// This structure represents a lazily initialized static usize value. Useful
// when it is preferable to just rerun initialization instead of locking.
Expand Down Expand Up @@ -67,58 +64,3 @@ impl LazyBool {
self.0.unsync_init(|| usize::from(init())) != 0
}
}

// This structure represents a lazily initialized static pointer value.
///
/// It's intended to be used for weak linking of a C function that may
/// or may not be present at runtime.
///
/// Based off of the DlsymWeak struct in libstd:
/// https://github.com/rust-lang/rust/blob/1.61.0/library/std/src/sys/unix/weak.rs#L84
/// except that the caller must manually cast self.ptr() to a function pointer.
pub struct LazyPtr {
addr: AtomicPtr<c_void>,
}

impl LazyPtr {
/// A non-null pointer value which indicates we are uninitialized.
///
/// This constant should ideally not be a valid pointer. However,
/// if by chance initialization function passed to the `unsync_init`
/// method does return UNINIT, there will not be undefined behavior.
/// The initialization function will just be called each time `get()`
/// is called. This would be inefficient, but correct.
const UNINIT: *mut c_void = !0usize as *mut c_void;

/// Construct new `LazyPtr` in uninitialized state.
pub const fn new() -> Self {
Self {
addr: AtomicPtr::new(Self::UNINIT),
}
}

// Runs the init() function at most once, returning the value of some run 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 Fn() -> *mut c_void) -> *mut c_void {
#[cold]
fn do_init(this: &LazyPtr, init: impl Fn() -> *mut c_void) -> *mut c_void {
let addr = init();
this.addr.store(addr, Ordering::Release);
addr
}

// Despite having only a single atomic variable (self.addr), we still
// cannot always use Ordering::Relaxed, as we need to make sure a
// successful call to `init` is "ordered before" any data read through
// the returned pointer (which occurs when the function is called).
// Our implementation mirrors that of the one in libstd, meaning that
// the use of non-Relaxed operations is probably unnecessary.
let val = self.addr.load(Ordering::Acquire);
if val != Self::UNINIT {
val
} else {
do_init(self, init)
}
}
}
73 changes: 50 additions & 23 deletions src/netbsd.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
//! Implementation for NetBSD
use crate::{lazy::LazyPtr, util_libc::sys_fill_exact, Error};
use core::{ffi::c_void, mem::MaybeUninit, ptr};
//!
//! `getrandom(2)` was introduced in NetBSD 10. To support older versions we
//! implement our own weak linkage to it, and provide a fallback based on the
//! KERN_ARND sysctl.
use crate::{util_libc::sys_fill_exact, Error};
use core::{
cmp,
ffi::c_void,
mem::{self, MaybeUninit},
ptr,
sync::atomic::{AtomicPtr, Ordering},
};

unsafe extern "C" fn polyfill_using_kern_arand(
buf: *mut c_void,
buflen: libc::size_t,
flags: libc::c_uint,
) -> libc::ssize_t {
debug_assert_eq!(flags, 0);

fn kern_arnd(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND];
let mut len = buf.len();

// NetBSD will only return up to 256 bytes at a time, and
// older NetBSD kernels will fail on longer buffers.
let mut len = cmp::min(buflen, 256);

let ret = unsafe {
libc::sysctl(
MIB.as_ptr(),
MIB.len() as libc::c_uint,
buf.as_mut_ptr().cast::<c_void>(),
buf,
&mut len,
ptr::null(),
0,
Expand All @@ -22,30 +42,37 @@ fn kern_arnd(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
}
}

type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t;
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this signature as @josephlr suggested.


// getrandom(2) was introduced in NetBSD 10.0
static GETRANDOM: LazyPtr = LazyPtr::new();
static GETRANDOM: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());

fn dlsym_getrandom() -> *mut c_void {
#[cold]
fn init() -> *mut c_void {
static NAME: &[u8] = b"getrandom\0";
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }
let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
if ptr.is_null() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this from the match style that people didn't like.

// Verify `polyfill_using_kern_arand` has the right signature.
const POLYFILL: GetRandomFn = polyfill_using_kern_arand;
ptr = POLYFILL as *mut c_void;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tweaked this by splitting the conversion into two clearly-correct parts because polyfill_using_kern_arand as *mut c_void is quite a dangerous cast.

}
GETRANDOM.store(ptr, Ordering::Release);
ptr
}

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
let fptr = GETRANDOM.unsync_init(dlsym_getrandom);
if !fptr.is_null() {
let func: GetRandomFn = unsafe { core::mem::transmute(fptr) };
return sys_fill_exact(dest, |buf| unsafe {
func(buf.as_mut_ptr().cast::<u8>(), buf.len(), 0)
});
}

// NetBSD will only return up to 256 bytes at a time, and
// older NetBSD kernels will fail on longer buffers.
for chunk in dest.chunks_mut(256) {
sys_fill_exact(chunk, kern_arnd)?
// Despite being only a single atomic variable, we still cannot always use
// Ordering::Relaxed, as we need to make sure a successful call to `init`
// is "ordered before" any data read through the returned pointer (which
// occurs when the function is called). Our implementation mirrors that of
// the one in libstd, meaning that the use of non-Relaxed operations is
// probably unnecessary.
let mut fptr = GETRANDOM.load(Ordering::Acquire);
if fptr.is_null() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this from the match style that people didn't like.

fptr = init();
}
Ok(())
let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) };
sys_fill_exact(dest, |buf| unsafe {
fptr(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0)
})
}