Skip to content

Commit

Permalink
Move Weak into a separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
newpavlov committed May 21, 2024
1 parent cf65e83 commit 590341d
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 69 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ cfg_if! {
#[path = "solaris.rs"] mod imp;
} else if #[cfg(target_os = "netbsd")] {
mod util_libc;
mod weak;
#[path = "netbsd.rs"] mod imp;
} else if #[cfg(target_os = "fuchsia")] {
#[path = "fuchsia.rs"] mod imp;
Expand Down
15 changes: 9 additions & 6 deletions src/netbsd.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
//! Implementation for NetBSD
use crate::{
util_libc::{sys_fill_exact, Weak},
Error,
};
use core::{mem::MaybeUninit, ptr};
use crate::{util_libc::sys_fill_exact, weak::Weak, Error};
use core::{ffi::c_void, mem::MaybeUninit, ptr};

fn kern_arnd(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND];
Expand All @@ -28,8 +25,14 @@ 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;

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
fn link_getrandom() -> *mut c_void {
static NAME: &[u8] = b"getrandom\0";
unsafe { libc::dlsym(libc::RTLD_DEFAULT, NAME.as_ptr() as *const _) }
}

// getrandom(2) was introduced in NetBSD 10.0
static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") };
static GETRANDOM: Weak = Weak::new(link_getrandom);

if let Some(fptr) = GETRANDOM.ptr() {
let func: GetRandomFn = unsafe { core::mem::transmute(fptr) };
return sys_fill_exact(dest, |buf| unsafe {
Expand Down
64 changes: 1 addition & 63 deletions src/util_libc.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
#![allow(dead_code)]
use crate::Error;
use core::{
mem::MaybeUninit,
num::NonZeroU32,
ptr::NonNull,
sync::atomic::{fence, AtomicPtr, Ordering},
};
use libc::c_void;
use core::{mem::MaybeUninit, num::NonZeroU32};

cfg_if! {
if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] {
Expand Down Expand Up @@ -76,62 +70,6 @@ pub fn sys_fill_exact(
Ok(())
}

// A "weak" binding to a C function that may or may not be present at runtime.
// Used for supporting newer OS features while still building on older systems.
// 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 Weak {
name: &'static str,
addr: AtomicPtr<c_void>,
}

impl Weak {
// A non-null pointer value which indicates we are uninitialized. This
// constant should ideally not be a valid address of a function pointer.
// However, if by chance libc::dlsym does return UNINIT, there will not
// be undefined behavior. libc::dlsym will just be called each time ptr()
// is called. This would be inefficient, but correct.
// TODO: Replace with core::ptr::invalid_mut(1) when that is stable.
const UNINIT: *mut c_void = 1 as *mut c_void;

// Construct a binding to a C function with a given name. This function is
// unsafe because `name` _must_ be null terminated.
pub const unsafe fn new(name: &'static str) -> Self {
Self {
name,
addr: AtomicPtr::new(Self::UNINIT),
}
}

// Return the address of a function if present at runtime. Otherwise,
// return None. Multiple callers can call ptr() concurrently. It will
// always return _some_ value returned by libc::dlsym. However, the
// dlsym function may be called multiple times.
pub fn ptr(&self) -> Option<NonNull<c_void>> {
// 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 dlsym() 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.
match self.addr.load(Ordering::Relaxed) {
Self::UNINIT => {
let symbol = self.name.as_ptr() as *const _;
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, symbol) };
// Synchronizes with the Acquire fence below
self.addr.store(addr, Ordering::Release);
NonNull::new(addr)
}
addr => {
let func = NonNull::new(addr)?;
fence(Ordering::Acquire);
Some(func)
}
}
}
}

// SAFETY: path must be null terminated, FD must be manually closed.
pub unsafe fn open_readonly(path: &str) -> Result<libc::c_int, Error> {
debug_assert_eq!(path.as_bytes().last(), Some(&0));
Expand Down
66 changes: 66 additions & 0 deletions src/weak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use core::{
ffi::c_void,
ptr::NonNull,
sync::atomic::{fence, AtomicPtr, Ordering},
};

// A "weak" binding to a C function that may or may not be present at runtime.
// Used for supporting newer OS features while still building on older systems.
// 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 Weak {
addr: AtomicPtr<c_void>,
link_fn: fn() -> *mut c_void,
}

impl Weak {
// A non-null pointer value which indicates we are uninitialized. This
// constant should ideally not be a valid address of a function pointer.
// However, if by chance libc::dlsym does return UNINIT, there will not
// be undefined behavior. libc::dlsym will just be called each time ptr()
// is called. This would be inefficient, but correct.
// TODO: Replace with core::ptr::invalid_mut(1) when that is stable.
const UNINIT: *mut c_void = 1 as *mut c_void;

// Construct a weak binding a C function.
pub const fn new(link_fn: fn() -> *mut c_void) -> Self {
Self {
addr: AtomicPtr::new(Self::UNINIT),
link_fn,
}
}

// Return the address of a function if present at runtime. Otherwise,
// return None. Multiple callers can call ptr() concurrently. It will
// always return _some_ value returned by libc::dlsym. However, the
// dlsym function may be called multiple times.
pub fn ptr(&self) -> Option<NonNull<c_void>> {
// 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 dlsym() 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.
match self.addr.load(Ordering::Relaxed) {
Self::UNINIT => {
let addr = (self.link_fn)();
// Synchronizes with the Acquire fence below
self.addr.store(addr, Ordering::Release);
NonNull::new(addr)
}
addr => {
let func = NonNull::new(addr)?;
fence(Ordering::Acquire);
Some(func)
}
}
}
}

static GETRANDOM: Weak = Weak::new(link_getrandom);

fn link_getrandom() -> *mut c_void {
static NAME: &[u8] = b"getrandom\0";
unsafe { libc::dlsym(libc::RTLD_DEFAULT, NAME.as_ptr() as *const _) }
}

0 comments on commit 590341d

Please sign in to comment.