blob: e42f39c64eb6acf0aa78cbd9a94fa4c3d1d9c277 [file] [log] [blame]
//! Utils that need libc.
#![allow(dead_code)]
use std::{fmt, io};
/// Handles the usual libc function that returns `-1` to indicate an error.
#[track_caller]
pub fn errno_result<T: From<i8> + Ord>(ret: T) -> io::Result<T> {
use std::cmp::Ordering;
match ret.cmp(&(-1i8).into()) {
Ordering::Equal => Err(io::Error::last_os_error()),
Ordering::Greater => Ok(ret),
Ordering::Less => panic!("unexpected return value: less than -1"),
}
}
/// Check that a function with errno error handling succeeded (i.e., returned 0).
#[track_caller]
pub fn errno_check<T: From<i8> + Ord + fmt::Debug>(ret: T) {
assert_eq!(errno_result(ret).unwrap(), 0i8.into(), "wrong successful result");
}
pub unsafe fn read_all(
fd: libc::c_int,
buf: *mut libc::c_void,
count: libc::size_t,
) -> libc::ssize_t {
assert!(count > 0);
let mut read_so_far = 0;
while read_so_far < count {
let res = libc::read(fd, buf.add(read_so_far), count - read_so_far);
if res < 0 {
return res;
}
if res == 0 {
// EOF
break;
}
read_so_far += res as libc::size_t;
}
return read_so_far as libc::ssize_t;
}
/// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read.
#[track_caller]
pub fn read_all_into_array<const N: usize>(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> {
let mut buf = [0; N];
let res = unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) };
if res >= 0 {
assert_eq!(res as usize, buf.len());
Ok(buf)
} else {
Err(res)
}
}
pub unsafe fn write_all(
fd: libc::c_int,
buf: *const libc::c_void,
count: libc::size_t,
) -> libc::ssize_t {
assert!(count > 0);
let mut written_so_far = 0;
while written_so_far < count {
let res = libc::write(fd, buf.add(written_so_far), count - written_so_far);
if res < 0 {
return res;
}
// Apparently a return value of 0 is just a short write, nothing special (unlike reads).
written_so_far += res as libc::size_t;
}
return written_so_far as libc::ssize_t;
}
/// Write the entire `buf` to `fd`. Error if not all bytes could be written.
#[track_caller]
pub fn write_all_from_slice(fd: libc::c_int, buf: &[u8]) -> Result<(), libc::ssize_t> {
let res = unsafe { write_all(fd, buf.as_ptr().cast(), buf.len()) };
if res >= 0 {
assert_eq!(res as usize, buf.len());
Ok(())
} else {
Err(res)
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))]
#[allow(unused_imports)]
pub mod epoll {
use libc::c_int;
pub use libc::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD};
// Re-export some constants we need a lot for this.
pub use libc::{EPOLLET, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLRDHUP};
use super::*;
/// The libc epoll_event type doesn't fit to the EPOLLIN etc constants, so we have our
/// own type. We also make the data field an int since we typically want to store FDs there.
#[derive(PartialEq, Debug)]
pub struct Ev {
pub events: c_int,
pub data: c_int,
}
#[track_caller]
pub fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: Ev) -> io::Result<()> {
let mut event = libc::epoll_event {
events: event.events.cast_unsigned(),
u64: event.data.try_into().unwrap(),
};
let ret = errno_result(unsafe { libc::epoll_ctl(epfd, op, fd, &raw mut event) })?;
assert_eq!(ret, 0);
Ok(())
}
/// Helper for the common case of adding an FD to an epoll with the FD itself being
/// the `data`.
#[track_caller]
pub fn epoll_ctl_add(epfd: c_int, fd: c_int, events: c_int) -> io::Result<()> {
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, Ev { events, data: fd })
}
#[track_caller]
pub fn check_epoll_wait_noblock<const N: usize>(epfd: i32, expected: &[Ev]) {
let mut array: [libc::epoll_event; N] = [libc::epoll_event { events: 0, u64: 0 }; N];
let num = errno_result(unsafe {
libc::epoll_wait(epfd, array.as_mut_ptr(), N.try_into().unwrap(), 0)
})
.expect("epoll_wait returned an error");
let got = &mut array[..num.try_into().unwrap()];
let got = got
.iter()
.map(|e| Ev { events: e.events.cast_signed(), data: e.u64.try_into().unwrap() })
.collect::<Vec<_>>();
assert_eq!(got, expected, "got wrong notifications");
}
}