pub use crcbl_wayland::ffi::*;
use core::ffi::{c_int, c_long};
use crate::linux::{CLOCK_MONOTONIC, Nfds};
unsafe extern "C" {
fn poll(fds: *mut PollFd, nfds: Nfds, timeout: c_int) -> c_int;
fn clock_gettime(clock_id: c_int, tp: *mut Timespec) -> c_int;
}
#[repr(C)]
struct PollFd {
fd: c_int,
events: i16,
revents: i16,
}
#[repr(C)]
struct Timespec {
tv_sec: c_long,
tv_nsec: c_long,
}
const POLLIN: i16 = 0x001;
const POLLOUT: i16 = 0x004;
pub fn poll_readable(fd: c_int, timeout_ms: c_int) -> bool {
let mut fds = PollFd {
fd,
events: POLLIN,
revents: 0,
};
let result = unsafe { poll(&raw mut fds, 1, timeout_ms) };
result > 0 && fds.revents & POLLIN != 0
}
#[must_use]
pub fn poll_ready(fd: c_int, for_write: bool, timeout_ms: c_int) -> bool {
let mut fds = PollFd {
fd,
events: if for_write { POLLOUT } else { POLLIN },
revents: 0,
};
let result = unsafe { poll(&raw mut fds, 1, timeout_ms) };
result > 0 && fds.revents != 0
}
#[must_use]
pub fn poll_readable_with(primary: c_int, aux: &[c_int], timeout_ms: c_int) -> bool {
if aux.is_empty() {
return poll_readable(primary, timeout_ms);
}
let mut fds: Vec<PollFd> = core::iter::once(PollFd {
fd: primary,
events: POLLIN,
revents: 0,
})
.chain(aux.iter().map(|fd| PollFd {
fd: *fd,
events: POLLIN | POLLOUT,
revents: 0,
}))
.collect();
let count = Nfds::try_from(fds.len()).unwrap_or(1);
let result = unsafe { poll(fds.as_mut_ptr(), count, timeout_ms) };
result > 0 && fds[0].revents & POLLIN != 0
}
#[must_use]
pub fn monotonic_nanos() -> u64 {
let mut ts = Timespec {
tv_sec: 0,
tv_nsec: 0,
};
if unsafe { clock_gettime(CLOCK_MONOTONIC, &raw mut ts) } != 0 {
return 0;
}
let seconds = u64::try_from(ts.tv_sec).unwrap_or(0);
let nanos = u64::try_from(ts.tv_nsec).unwrap_or(0);
seconds * 1_000_000_000 + nanos
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_monotonic_clock_advances_and_is_not_the_epoch() {
let first = monotonic_nanos();
assert!(first > 0, "CLOCK_MONOTONIC is available on Linux");
let second = monotonic_nanos();
assert!(second >= first, "CLOCK_MONOTONIC never goes backwards");
}
#[test]
fn polling_a_closed_descriptor_does_not_block() {
assert!(!poll_readable(-1, 0));
assert!(!poll_ready(-1, true, 0));
assert!(!poll_ready(-1, false, 0));
assert!(!poll_readable_with(-1, &[-1, -1], 0));
}
#[test]
fn a_fresh_pipe_is_writable_and_becomes_readable_when_written() {
use std::io::Write as _;
use std::os::fd::AsRawFd as _;
let (reader, mut writer) = std::io::pipe().expect("pipe");
assert!(
poll_ready(writer.as_raw_fd(), true, 0),
"an empty pipe takes a PIPE_BUF write without blocking"
);
assert!(
!poll_ready(reader.as_raw_fd(), false, 0),
"nothing in it yet"
);
writer.write_all(b"x").expect("write");
assert!(poll_ready(reader.as_raw_fd(), false, 0));
let (empty, closed) = std::io::pipe().expect("pipe");
drop(closed);
assert!(
!poll_readable(empty.as_raw_fd(), 0),
"no POLLIN, only POLLHUP"
);
assert!(poll_ready(empty.as_raw_fd(), false, 0));
assert!(!poll_readable_with(-1, &[reader.as_raw_fd()], 0));
assert!(poll_readable_with(reader.as_raw_fd(), &[-1], 0));
}
}