use std::{cmp, io, mem, ptr};
use std::os::unix::io::RawFd;
use std::time::Duration;
use log::error;
use crate::event::{Event, EventedId, Events, Ready};
use crate::poll::{Interests, PollOption};
use crate::sys::EVENTS_CAP;
#[cfg(not(target_os = "netbsd"))]
#[allow(non_camel_case_types)]
type nchanges_t = libc::c_int;
#[cfg(target_os = "netbsd")]
#[allow(non_camel_case_types)]
type nchanges_t = libc::size_t;
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
#[allow(non_camel_case_types)]
type kevent_filter_t = libc::c_short;
#[cfg(target_os = "macos")]
#[allow(non_camel_case_types)]
type kevent_filter_t = libc::int16_t;
#[cfg(target_os = "netbsd")]
#[allow(non_camel_case_types)]
type kevent_filter_t = libc::uint32_t;
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
#[allow(non_camel_case_types)]
type kevent_flags_t = libc::c_ushort;
#[cfg(target_os = "macos")]
#[allow(non_camel_case_types)]
type kevent_flags_t = libc::uint16_t;
#[cfg(target_os = "netbsd")]
#[allow(non_camel_case_types)]
type kevent_flags_t = libc::uint32_t;
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[allow(non_camel_case_types)]
type kevent_data_t = libc::intptr_t;
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
#[allow(non_camel_case_types)]
type kevent_data_t = libc::int64_t;
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "openbsd"))]
#[allow(non_camel_case_types)]
type kevent_udata_t = *mut libc::c_void;
#[cfg(target_os = "netbsd")]
#[allow(non_camel_case_types)]
type kevent_udata_t = libc::intptr_t;
#[derive(Debug)]
pub struct Selector {
kq: RawFd,
}
impl Selector {
pub fn new() -> io::Result<Selector> {
let kq = unsafe { libc::kqueue() };
if kq == -1 {
Err(io::Error::last_os_error())
} else {
Ok(Selector { kq })
}
}
pub fn select(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<()> {
let mut kevents: [libc::kevent; EVENTS_CAP] = unsafe { mem::uninitialized() };
#[allow(trivial_numeric_casts)]
let events_cap = cmp::min(events.capacity(), EVENTS_CAP) as nchanges_t;
let timespec = timeout.map(timespec_from_duration);
#[allow(trivial_casts)]
let timespec_ptr = timespec
.as_ref()
.map(|t| t as *const libc::timespec)
.unwrap_or(ptr::null());
let n_events = unsafe {
libc::kevent(self.kq, ptr::null(), 0,
kevents.as_mut_ptr(), events_cap, timespec_ptr)
};
match n_events {
-1 => Err(io::Error::last_os_error()),
0 => Ok(()), n => {
for kevent in kevents.iter().take(n as usize) {
let event = kevent_to_event(kevent);
events.push(event);
}
Ok(())
},
}
}
pub fn register(&self, fd: RawFd, id: EventedId, interests: Interests, opt: PollOption) -> io::Result<()> {
let flags = opt_to_flags(opt) | libc::EV_ADD;
let mut changes: [libc::kevent; 2] = unsafe { mem::uninitialized() };
let mut n_changes = 0;
if interests.is_writable() {
let kevent = new_kevent(fd as libc::uintptr_t, libc::EVFILT_WRITE, flags, id);
unsafe { ptr::write(&mut changes[n_changes], kevent) };
n_changes += 1;
}
if interests.is_readable() {
let kevent = new_kevent(fd as libc::uintptr_t, libc::EVFILT_READ, flags, id);
unsafe { ptr::write(&mut changes[n_changes], kevent) };
n_changes += 1;
}
kevent_register(self.kq, &mut changes[0..n_changes], &[])
}
pub fn reregister(&self, fd: RawFd, id: EventedId, interests: Interests, opt: PollOption) -> io::Result<()> {
let flags = opt_to_flags(opt);
let write_flags = if interests.is_writable() {
flags | libc::EV_ADD
} else {
flags | libc::EV_DELETE
};
let read_flags = if interests.is_readable() {
flags | libc::EV_ADD
} else {
flags | libc::EV_DELETE
};
let mut changes: [libc::kevent; 2] = [
new_kevent(fd as libc::uintptr_t, libc::EVFILT_WRITE, write_flags, id),
new_kevent(fd as libc::uintptr_t, libc::EVFILT_READ, read_flags, id),
];
kevent_register(self.kq, &mut changes, &[libc::ENOENT as kevent_data_t])
}
pub fn deregister(&self, fd: RawFd) -> io::Result<()> {
let flags = libc::EV_DELETE | libc::EV_RECEIPT;
let mut changes: [libc::kevent; 2] = [
new_kevent(fd as libc::uintptr_t, libc::EVFILT_WRITE, flags, EventedId(::std::usize::MAX)),
new_kevent(fd as libc::uintptr_t, libc::EVFILT_READ, flags, EventedId(::std::usize::MAX)),
];
kevent_register(self.kq, &mut changes, &[libc::ENOENT as kevent_data_t])
}
pub fn setup_awakener(&self, id: EventedId) -> io::Result<()> {
let kevent = new_kevent(0, libc::EVFILT_USER, libc::EV_ADD | libc::EV_CLEAR | libc::EV_RECEIPT, id);
kevent_register(self.kq, &mut [kevent], &[])
}
pub fn try_clone(&self) -> io::Result<Selector> {
let new_kq = unsafe { libc::dup(self.kq) };
if new_kq == -1 {
Err(io::Error::last_os_error())
} else {
Ok(Selector { kq: new_kq })
}
}
pub fn wake(&self, id: EventedId) -> io::Result<()> {
let mut kevent = new_kevent(0, libc::EVFILT_USER, libc::EV_ADD | libc::EV_CLEAR | libc::EV_RECEIPT, id);
kevent.fflags = libc::NOTE_TRIGGER;
kevent_register(self.kq, &mut [kevent], &[])
}
}
fn timespec_from_duration(duration: Duration) -> libc::timespec {
libc::timespec {
tv_sec: cmp::min(duration.as_secs(), libc::time_t::max_value() as u64) as libc::time_t,
tv_nsec: libc::c_long::from(duration.subsec_nanos()),
}
}
fn kevent_to_event(kevent: &libc::kevent) -> Event {
let id = EventedId(kevent.udata as usize);
let mut readiness = Ready::empty();
if contains_flag(kevent.flags, libc::EV_ERROR) {
readiness.insert(Ready::ERROR);
}
if contains_flag(kevent.flags, libc::EV_EOF) {
readiness.insert(Ready::HUP);
if kevent.fflags != 0 {
readiness.insert(Ready::ERROR);
}
}
match kevent.filter {
libc::EVFILT_READ => readiness.insert(Ready::READABLE),
libc::EVFILT_WRITE => readiness.insert(Ready::WRITABLE),
_ => {},
}
if kevent.filter == libc::EVFILT_USER {
readiness.insert(Ready::READABLE);
}
Event::new(id, readiness)
}
fn opt_to_flags(opt: PollOption) -> kevent_flags_t {
libc::EV_RECEIPT | match opt {
PollOption::Edge => libc::EV_CLEAR,
PollOption::Level => 0, PollOption::Oneshot => libc::EV_ONESHOT,
}
}
fn new_kevent(ident: libc::uintptr_t, filter: kevent_filter_t, flags: kevent_flags_t, id: EventedId) -> libc::kevent {
libc::kevent {
ident, filter, flags,
fflags: 0,
data: 0,
udata: id.0 as kevent_udata_t,
}
}
fn kevent_register(kq: RawFd, changes: &mut [libc::kevent], ignored_errors: &[kevent_data_t]) -> io::Result<()> {
let ok = unsafe {
#[allow(trivial_numeric_casts)]
libc::kevent(kq, changes.as_ptr(), changes.len() as nchanges_t,
changes.as_mut_ptr(), changes.len() as nchanges_t, ptr::null())
};
if ok == -1 {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EINTR) => Ok(()),
_ => Err(err),
}
} else {
check_errors(&*changes, ignored_errors)
}
}
fn check_errors(events: &[libc::kevent], ignored_errors: &[kevent_data_t]) -> io::Result<()> {
for event in &*events {
let data = event.data;
if contains_flag(event.flags, libc::EV_ERROR) && data != 0 &&
!ignored_errors.contains(&data)
{
return Err(io::Error::from_raw_os_error(data as i32));
}
}
Ok(())
}
fn contains_flag(flags: kevent_flags_t, flag: kevent_flags_t) -> bool {
(flags & flag) != 0
}
impl Drop for Selector {
fn drop(&mut self) {
if unsafe { libc::close(self.kq) } == -1 {
let err = io::Error::last_os_error();
error!("error closing kqueue: {}", err);
}
}
}