Skip to content

Commit

Permalink
Allow setting kevent_flags on struct sigevent (#1731)
Browse files Browse the repository at this point in the history
* Allow setting kevent_flags on struct sigevent

Also, disallow using SigevNotify::SigevThreadId on musl.  I don't think
it ever worked.

Depends on rust-lang/libc#2813
Blocks tokio-rs/tokio#4728

* Inline libc::sigevent

Because the PR to libc is stalled for over one year, with no sign of
progress.
  • Loading branch information
asomers committed Aug 26, 2023
1 parent 183be1b commit cc73638
Showing 1 changed file with 211 additions and 49 deletions.
260 changes: 211 additions & 49 deletions src/sys/signal.rs
Expand Up @@ -1022,7 +1022,7 @@ feature! {
#[cfg(target_os = "freebsd")]
pub type type_of_thread_id = libc::lwpid_t;
/// Identifies a thread for [`SigevNotify::SigevThreadId`]
#[cfg(target_os = "linux")]
#[cfg(any(target_env = "gnu", target_env = "uclibc"))]
pub type type_of_thread_id = libc::pid_t;

/// Specifies the notification method used by a [`SigEvent`]
Expand All @@ -1042,8 +1042,7 @@ pub enum SigevNotify {
/// structure of the queued signal.
si_value: libc::intptr_t
},
// Note: SIGEV_THREAD is not implemented because libc::sigevent does not
// expose a way to set the union members needed by SIGEV_THREAD.
// Note: SIGEV_THREAD is not implemented, but could be if desired.
/// Notify by delivering an event to a kqueue.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
#[cfg_attr(docsrs, doc(cfg(all())))]
Expand All @@ -1053,8 +1052,24 @@ pub enum SigevNotify {
/// Will be contained in the kevent's `udata` field.
udata: libc::intptr_t
},
/// Notify by delivering an event to a kqueue, with optional event flags set
#[cfg(target_os = "freebsd")]
#[cfg_attr(docsrs, doc(cfg(all())))]
#[cfg(feature = "event")]
SigevKeventFlags {
/// File descriptor of the kqueue to notify.
kq: RawFd,
/// Will be contained in the kevent's `udata` field.
udata: libc::intptr_t,
/// Flags that will be set on the delivered event. See `kevent(2)`.
flags: crate::sys::event::EventFlag
},
/// Notify by delivering a signal to a thread.
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
#[cfg(any(
target_os = "freebsd",
target_env = "gnu",
target_env = "uclibc",
))]
#[cfg_attr(docsrs, doc(cfg(all())))]
SigevThreadId {
/// Signal to send
Expand All @@ -1079,17 +1094,139 @@ mod sigevent {
#![any(feature = "aio", feature = "signal")]

use std::mem;
use std::ptr;
use super::SigevNotify;
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
use super::type_of_thread_id;

#[cfg(target_os = "freebsd")]
pub(crate) use ffi::sigevent as libc_sigevent;
#[cfg(not(target_os = "freebsd"))]
pub(crate) use libc::sigevent as libc_sigevent;

// For FreeBSD only, we define the C structure here. Because the structure
// defined in libc isn't correct. The real sigevent contains union fields,
// but libc could not represent those when sigevent was originally added, so
// instead libc simply defined the most useful field. Now that Rust can
// represent unions, there's a PR to libc to fix it. However, it's stuck
// forever due to backwards compatibility concerns. Even though there's a
// workaround, libc refuses to merge it. I think it's just too complicated
// for them to want to think about right now, because that project is
// short-staffed. So we define it here instead, so we won't have to wait on
// libc.
// https://github.com/rust-lang/libc/pull/2813
#[cfg(target_os = "freebsd")]
mod ffi {
use std::{fmt, hash};

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct __c_anonymous_sigev_thread {
pub _function: *mut libc::c_void, // Actually a function pointer
pub _attribute: *mut libc::pthread_attr_t,
}
#[derive(Clone, Copy)]
// This will never be used on its own, and its parent has a Debug impl,
// so it doesn't need one.
#[allow(missing_debug_implementations)]
#[repr(C)]
pub union __c_anonymous_sigev_un {
pub _threadid: libc::__lwpid_t,
pub _sigev_thread: __c_anonymous_sigev_thread,
pub _kevent_flags: libc::c_ushort,
__spare__: [libc::c_long; 8],
}

#[derive(Clone, Copy)]
#[repr(C)]
pub struct sigevent {
pub sigev_notify: libc::c_int,
pub sigev_signo: libc::c_int,
pub sigev_value: libc::sigval,
pub _sigev_un: __c_anonymous_sigev_un,
}

impl fmt::Debug for sigevent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut ds = f.debug_struct("sigevent");
ds.field("sigev_notify", &self.sigev_notify)
.field("sigev_signo", &self.sigev_signo)
.field("sigev_value", &self.sigev_value);
// Safe because we check the sigev_notify discriminant
unsafe {
match self.sigev_notify {
libc::SIGEV_KEVENT => {
ds.field("sigev_notify_kevent_flags", &self._sigev_un._kevent_flags);
}
libc::SIGEV_THREAD_ID => {
ds.field("sigev_notify_thread_id", &self._sigev_un._threadid);
}
libc::SIGEV_THREAD => {
ds.field("sigev_notify_function", &self._sigev_un._sigev_thread._function);
ds.field("sigev_notify_attributes", &self._sigev_un._sigev_thread._attribute);
}
_ => ()
};
}
ds.finish()
}
}

impl PartialEq for sigevent {
fn eq(&self, other: &Self) -> bool {
let mut equals = self.sigev_notify == other.sigev_notify;
equals &= self.sigev_signo == other.sigev_signo;
equals &= self.sigev_value == other.sigev_value;
// Safe because we check the sigev_notify discriminant
unsafe {
match self.sigev_notify {
libc::SIGEV_KEVENT => {
equals &= self._sigev_un._kevent_flags == other._sigev_un._kevent_flags;
}
libc::SIGEV_THREAD_ID => {
equals &= self._sigev_un._threadid == other._sigev_un._threadid;
}
libc::SIGEV_THREAD => {
equals &= self._sigev_un._sigev_thread == other._sigev_un._sigev_thread;
}
_ => /* The union field is don't care */ ()
}
}
equals
}
}

impl Eq for sigevent {}

impl hash::Hash for sigevent {
fn hash<H: hash::Hasher>(&self, s: &mut H) {
self.sigev_notify.hash(s);
self.sigev_signo.hash(s);
self.sigev_value.hash(s);
// Safe because we check the sigev_notify discriminant
unsafe {
match self.sigev_notify {
libc::SIGEV_KEVENT => {
self._sigev_un._kevent_flags.hash(s);
}
libc::SIGEV_THREAD_ID => {
self._sigev_un._threadid.hash(s);
}
libc::SIGEV_THREAD => {
self._sigev_un._sigev_thread.hash(s);
}
_ => /* The union field is don't care */ ()
}
}
}
}
}

/// Used to request asynchronous notification of the completion of certain
/// events, such as POSIX AIO and timers.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
// It can't be Copy on all platforms.
#[allow(missing_copy_implementations)]
pub struct SigEvent {
sigevent: libc::sigevent
sigevent: libc_sigevent
}

impl SigEvent {
Expand All @@ -1107,65 +1244,90 @@ mod sigevent {
/// `SIGEV_SIGNAL`. That field is part of a union that shares space with the
/// more genuinely useful `sigev_notify_thread_id`
pub fn new(sigev_notify: SigevNotify) -> SigEvent {
let mut sev = unsafe { mem::MaybeUninit::<libc::sigevent>::zeroed().assume_init() };
sev.sigev_notify = match sigev_notify {
SigevNotify::SigevNone => libc::SIGEV_NONE,
SigevNotify::SigevSignal{..} => libc::SIGEV_SIGNAL,
let mut sev: libc_sigevent = unsafe { mem::zeroed() };
match sigev_notify {
SigevNotify::SigevNone => {
sev.sigev_notify = libc::SIGEV_NONE;
},
SigevNotify::SigevSignal{signal, si_value} => {
sev.sigev_notify = libc::SIGEV_SIGNAL;
sev.sigev_signo = signal as libc::c_int;
sev.sigev_value.sival_ptr = si_value as *mut libc::c_void
},
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT,
SigevNotify::SigevKevent{kq, udata} => {
sev.sigev_notify = libc::SIGEV_KEVENT;
sev.sigev_signo = kq;
sev.sigev_value.sival_ptr = udata as *mut libc::c_void;
},
#[cfg(target_os = "freebsd")]
SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID,
#[cfg(all(target_os = "linux", target_env = "gnu", not(target_arch = "mips")))]
SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID,
#[cfg(all(target_os = "linux", target_env = "uclibc", not(target_arch = "mips")))]
SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID,
#[cfg(any(all(target_os = "linux", target_env = "musl"), target_arch = "mips"))]
SigevNotify::SigevThreadId{..} => 4 // No SIGEV_THREAD_ID defined
};
sev.sigev_signo = match sigev_notify {
SigevNotify::SigevSignal{ signal, .. } => signal as libc::c_int,
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
SigevNotify::SigevKevent{ kq, ..} => kq,
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
SigevNotify::SigevThreadId{ signal, .. } => signal as libc::c_int,
_ => 0
};
sev.sigev_value.sival_ptr = match sigev_notify {
SigevNotify::SigevNone => ptr::null_mut::<libc::c_void>(),
SigevNotify::SigevSignal{ si_value, .. } => si_value as *mut libc::c_void,
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
SigevNotify::SigevKevent{ udata, .. } => udata as *mut libc::c_void,
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
SigevNotify::SigevThreadId{ si_value, .. } => si_value as *mut libc::c_void,
};
SigEvent::set_tid(&mut sev, &sigev_notify);
#[cfg(feature = "event")]
SigevNotify::SigevKeventFlags{kq, udata, flags} => {
sev.sigev_notify = libc::SIGEV_KEVENT;
sev.sigev_signo = kq;
sev.sigev_value.sival_ptr = udata as *mut libc::c_void;
sev._sigev_un._kevent_flags = flags.bits();
},
#[cfg(target_os = "freebsd")]
SigevNotify::SigevThreadId{signal, thread_id, si_value} => {
sev.sigev_notify = libc::SIGEV_THREAD_ID;
sev.sigev_signo = signal as libc::c_int;
sev.sigev_value.sival_ptr = si_value as *mut libc::c_void;
sev._sigev_un._threadid = thread_id;
}
#[cfg(any(target_env = "gnu", target_env = "uclibc"))]
SigevNotify::SigevThreadId{signal, thread_id, si_value} => {
sev.sigev_notify = libc::SIGEV_THREAD_ID;
sev.sigev_signo = signal as libc::c_int;
sev.sigev_value.sival_ptr = si_value as *mut libc::c_void;
sev.sigev_notify_thread_id = thread_id;
}
}
SigEvent{sigevent: sev}
}

#[cfg(any(target_os = "freebsd", target_os = "linux"))]
fn set_tid(sev: &mut libc::sigevent, sigev_notify: &SigevNotify) {
sev.sigev_notify_thread_id = match *sigev_notify {
SigevNotify::SigevThreadId { thread_id, .. } => thread_id,
_ => 0 as type_of_thread_id
};
}

#[cfg(not(any(target_os = "freebsd", target_os = "linux")))]
fn set_tid(_sev: &mut libc::sigevent, _sigev_notify: &SigevNotify) {
/// Return a copy of the inner structure
#[cfg(target_os = "freebsd")]
pub fn sigevent(&self) -> libc::sigevent {
// Safe because they're really the same structure. See
// https://github.com/rust-lang/libc/pull/2813
unsafe {
mem::transmute::<libc_sigevent, libc::sigevent>(self.sigevent)
}
}

/// Return a copy of the inner structure
#[cfg(not(target_os = "freebsd"))]
pub fn sigevent(&self) -> libc::sigevent {
self.sigevent
}

/// Returns a mutable pointer to the `sigevent` wrapped by `self`
#[cfg(target_os = "freebsd")]
pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent {
// Safe because they're really the same structure. See
// https://github.com/rust-lang/libc/pull/2813
&mut self.sigevent as *mut libc_sigevent as *mut libc::sigevent
}

/// Returns a mutable pointer to the `sigevent` wrapped by `self`
#[cfg(not(target_os = "freebsd"))]
pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent {
&mut self.sigevent
}
}

impl<'a> From<&'a libc::sigevent> for SigEvent {
#[cfg(target_os = "freebsd")]
fn from(sigevent: &libc::sigevent) -> Self {
// Safe because they're really the same structure. See
// https://github.com/rust-lang/libc/pull/2813
let sigevent = unsafe {
mem::transmute::<libc::sigevent, libc_sigevent>(*sigevent)
};
SigEvent{ sigevent }
}
#[cfg(not(target_os = "freebsd"))]
fn from(sigevent: &libc::sigevent) -> Self {
SigEvent{ sigevent: *sigevent }
}
Expand Down

0 comments on commit cc73638

Please sign in to comment.