| //! Utils that need libc. | |
| #![allow(dead_code)] | |
| use std::{fmt, io, time}; | |
| pub enum Retry { | |
| NoRetry, | |
| RetryAfter(time::Duration), | |
| } | |
| pub use Retry::*; | |
| /// Return the last OS error. | |
| pub fn errno() -> i32 { | |
| // libc has no portable way to do this so we use std. | |
| io::Error::last_os_error().raw_os_error().unwrap() | |
| } | |
| /// 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"); | |
| } | |
| /// Invoke the `read` function until `buf` is full. `retry` contols the behavior on EAGAIN. | |
| /// Panics if we get EOF before the buffer is filled. | |
| #[track_caller] | |
| pub unsafe fn read_exact_generic( | |
| buf: *mut libc::c_void, | |
| count: libc::size_t, | |
| retry: Retry, | |
| read: impl Fn(*mut libc::c_void, libc::size_t) -> libc::ssize_t, | |
| ) -> io::Result<()> { | |
| assert!(count > 0); | |
| let mut read_so_far = 0; | |
| while read_so_far < count { | |
| let res = read(buf.add(read_so_far), count - read_so_far); | |
| if res < 0 { | |
| if let RetryAfter(duration) = retry { | |
| if errno() == libc::EAGAIN { | |
| // Emulate blocking behavior by sleeping a bit and then trying again. | |
| std::thread::sleep(duration); | |
| continue; | |
| } | |
| } | |
| return Err(io::Error::last_os_error()); | |
| } | |
| if res == 0 { | |
| // We expected more data but got EOF. | |
| panic!( | |
| "could not fill buffer with {count} bytes: EOF received after {read_so_far} bytes" | |
| ); | |
| } | |
| read_so_far += res as libc::size_t; | |
| } | |
| Ok(()) | |
| } | |
| /// Try to fill the given slice by reading from `fd`. Panic if that many bytes could not be read. | |
| #[track_caller] | |
| pub fn read_exact(fd: libc::c_int, buf: &mut [u8]) -> io::Result<()> { | |
| unsafe { | |
| read_exact_generic(buf.as_mut_ptr().cast(), buf.len(), NoRetry, |buf, count| { | |
| libc::read(fd, buf, count) | |
| }) | |
| } | |
| } | |
| /// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read. | |
| #[track_caller] | |
| pub fn read_exact_array<const N: usize>(fd: libc::c_int) -> io::Result<[u8; N]> { | |
| let mut buf = [0; N]; | |
| read_exact(fd, &mut buf)?; | |
| Ok(buf) | |
| } | |
| /// Do a single read from `fd` and return the part of the buffer that was written into, | |
| /// and the rest. | |
| #[track_caller] | |
| pub fn read_partial(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { | |
| let res = errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) })?; | |
| Ok(buf.split_at_mut(res as usize)) | |
| } | |
| /// Invoke the `write` function until `buf` is full. `retry` controls the behavior on EAGAIN. | |
| pub unsafe fn write_all_generic( | |
| buf: *const libc::c_void, | |
| count: libc::size_t, | |
| retry: Retry, | |
| write: impl Fn(*const libc::c_void, libc::size_t) -> libc::ssize_t, | |
| ) -> io::Result<()> { | |
| assert!(count > 0); | |
| let mut written_so_far = 0; | |
| while written_so_far < count { | |
| let res = write(buf.add(written_so_far), count - written_so_far); | |
| if res < 0 { | |
| if let RetryAfter(duration) = retry { | |
| if errno() == libc::EAGAIN { | |
| // Emulate blocking behavior by sleeping a bit and then trying again. | |
| std::thread::sleep(duration); | |
| continue; | |
| } | |
| } | |
| return Err(io::Error::last_os_error()); | |
| } | |
| // Apparently a return value of 0 is just a short write, nothing special (unlike reads). | |
| written_so_far += res as libc::size_t; | |
| } | |
| return Ok(()); | |
| } | |
| /// Write the entire `buf` to `fd`. Panic if not all bytes could be written. | |
| #[track_caller] | |
| pub fn write_all(fd: libc::c_int, buf: &[u8]) -> io::Result<()> { | |
| unsafe { | |
| write_all_generic(buf.as_ptr().cast(), buf.len(), NoRetry, |buf, count| { | |
| libc::write(fd, buf, count) | |
| }) | |
| } | |
| } | |
| #[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::{EPOLLERR, 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, Clone, Copy)] | |
| 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 }) | |
| } | |
| /// Call `epoll_wait` on `epfd` with the provided `timeout` (in milliseconds). | |
| /// It fetches at most `max_events` events from `epfd` and | |
| /// ensures that the returned events match the `expected` events. | |
| #[track_caller] | |
| pub fn check_epoll_wait_partial(epfd: i32, expected: &[Ev], max_events: usize, timeout: i32) { | |
| let mut events = vec![libc::epoll_event { events: 0, u64: 0 }; max_events]; | |
| let num = errno_result(unsafe { | |
| libc::epoll_wait(epfd, events.as_mut_ptr(), i32::try_from(max_events).unwrap(), timeout) | |
| }) | |
| .expect("epoll_wait returned an error"); | |
| let got = events | |
| .iter() | |
| .take(num as usize) | |
| .map(|e| Ev { events: e.events.cast_signed(), data: e.u64.try_into().unwrap() }) | |
| .collect::<Vec<_>>(); | |
| assert_eq!(got, expected, "got wrong ready events"); | |
| } | |
| /// Call `epoll_wait` on `epfd` with the provided `timeout` (in milliseconds) and ensure | |
| /// that the set of *all* ready events matches `expected`. | |
| #[track_caller] | |
| pub fn check_epoll_wait(epfd: i32, expected: &[Ev], timeout: i32) { | |
| // We set `max_events` to `expected.len() + 1` to ensure that there are no additional ready | |
| // events besides those which are contained in `expected`. | |
| check_epoll_wait_partial(epfd, &expected, expected.len() + 1, timeout); | |
| } | |
| /// This does the same as [`check_epoll_wait`] just without blocking (zero `timeout`). | |
| #[track_caller] | |
| pub fn check_epoll_wait_noblock(epfd: i32, expected: &[Ev]) { | |
| check_epoll_wait(epfd, expected, 0); | |
| } | |
| /// Query the current epoll readiness of a file descriptor. | |
| /// This is done by creating a new epoll instance, adding the | |
| /// fd to the epoll interests and then performing a zero-timeout | |
| /// wait. | |
| pub fn current_epoll_readiness(fd: i32, interests: i32) -> c_int { | |
| let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); | |
| // Add fd with all possible interests to epoll instance. | |
| epoll_ctl_add(epfd, fd, interests).unwrap(); | |
| let mut out: libc::epoll_event = libc::epoll_event { events: 0, u64: 0 }; | |
| let num = errno_result(unsafe { | |
| // Use zero-timeout to just query without waiting. | |
| libc::epoll_wait(epfd, &raw mut out, /* size of out */ 1, /* don't wait */ 0) | |
| }) | |
| .expect("epoll_wait returned an error"); | |
| if num == 0 { 0 } else { out.events.cast_signed() } | |
| } | |
| } | |
| #[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))] | |
| pub mod eventfd { | |
| use super::*; | |
| // We want to do individual read/write calls here so we avoid read_exact/write_all. | |
| #[track_caller] | |
| pub fn read_val(fd: i32) -> io::Result<u64> { | |
| let mut buf = [0u8; 8]; | |
| let len = errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) })?; | |
| assert_eq!(len, 8); | |
| Ok(u64::from_ne_bytes(buf)) | |
| } | |
| #[track_caller] | |
| pub fn write_val(fd: i32, val: u64) -> io::Result<()> { | |
| let buf = val.to_ne_bytes(); | |
| let len = errno_result(unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) })?; | |
| assert_eq!(len, 8); | |
| Ok(()) | |
| } | |
| } | |
| pub mod net { | |
| use super::*; | |
| /// IPv4 localhost address bytes | |
| pub const IPV4_LOCALHOST: [u8; 4] = [127, 0, 0, 1]; | |
| /// IPv6 localhost address bytes | |
| pub const IPV6_LOCALHOST: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; | |
| /// Create a libc representation of an IPv4 address given the address bytes and a port. | |
| pub fn sock_addr_ipv4(addr_bytes: [u8; 4], port: u16) -> libc::sockaddr_in { | |
| libc::sockaddr_in { | |
| sin_family: libc::AF_INET as libc::sa_family_t, | |
| sin_port: port.to_be(), | |
| // `addr_bytes` is already in big-endian and that's the format `sin_addr` expects. | |
| #[expect(unnecessary_transmutes)] | |
| sin_addr: libc::in_addr { | |
| s_addr: unsafe { std::mem::transmute::<[u8; 4], u32>(addr_bytes) }, | |
| }, | |
| ..unsafe { core::mem::zeroed() } | |
| } | |
| } | |
| /// Create a libc representation of an IPv6 address given the address bytes and a port. | |
| /// | |
| /// This method sets `flowinfo` and `scope_id` to 0. | |
| pub fn sock_addr_ipv6(addr_bytes: [u8; 16], port: u16) -> libc::sockaddr_in6 { | |
| sock_addr_full_ipv6(addr_bytes, port, 0, 0) | |
| } | |
| /// Create a libc representation of a full IPv6 address given the address bytes, a port | |
| /// as well as a flowinfo and scope id. | |
| pub fn sock_addr_full_ipv6( | |
| addr_bytes: [u8; 16], | |
| port: u16, | |
| flowinfo: u32, | |
| scope_id: u32, | |
| ) -> libc::sockaddr_in6 { | |
| #[allow(clippy::needless_update)] | |
| libc::sockaddr_in6 { | |
| sin6_family: libc::AF_INET6 as libc::sa_family_t, | |
| sin6_port: port.to_be(), | |
| sin6_addr: libc::in6_addr { s6_addr: addr_bytes }, | |
| sin6_flowinfo: flowinfo, | |
| sin6_scope_id: scope_id, | |
| // This is only needed on some targets where an additional `sin6_len` field exists. | |
| ..unsafe { core::mem::zeroed() } | |
| } | |
| } | |
| /// Create an IPv4 TCP socket which listens on a random port at the localhost address. | |
| /// Returns the socket file descriptor and the actual socket address the socket is listening on. | |
| pub fn make_listener_ipv4() -> io::Result<(libc::c_int, libc::sockaddr_in)> { | |
| let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0))? }; | |
| // Turn address into socket address with a random free port. | |
| let addr = sock_addr_ipv4(IPV4_LOCALHOST, 0); | |
| unsafe { | |
| errno_result(libc::bind( | |
| sockfd, | |
| (&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(), | |
| size_of::<libc::sockaddr_in>() as libc::socklen_t, | |
| ))?; | |
| } | |
| unsafe { | |
| errno_result(libc::listen(sockfd, 16))?; | |
| } | |
| // Retrieve actual listener address because we used a randomized port. | |
| let (_, addr_with_port) = | |
| sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) })?; | |
| Ok((sockfd, addr_with_port)) | |
| } | |
| /// Create an IPv6 TCP socket which listens on a random port at the localhost address. | |
| /// Returns the socket file descriptor and the actual socket address the socket is listening on. | |
| pub fn make_listener_ipv6() -> io::Result<(libc::c_int, libc::sockaddr_in6)> { | |
| let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0))? }; | |
| // Turn address into socket address with a random free port. | |
| let addr = sock_addr_ipv6(IPV6_LOCALHOST, 0); | |
| unsafe { | |
| errno_result(libc::bind( | |
| sockfd, | |
| (&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(), | |
| size_of::<libc::sockaddr_in6>() as libc::socklen_t, | |
| ))?; | |
| } | |
| unsafe { | |
| errno_result(libc::listen(sockfd, 16))?; | |
| } | |
| // Retrieve actual listener address because we used a randomized port. | |
| let (_, addr_with_port) = | |
| sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) })?; | |
| Ok((sockfd, addr_with_port)) | |
| } | |
| /// Accept an incoming IPv4 connection. | |
| pub fn accept_ipv4(sockfd: libc::c_int) -> io::Result<(libc::c_int, libc::sockaddr_in)> { | |
| sockname_ipv4(|storage, len| unsafe { libc::accept(sockfd, storage, len) }) | |
| } | |
| /// Accept an incoming IPv6 connection. | |
| pub fn accept_ipv6(sockfd: libc::c_int) -> io::Result<(libc::c_int, libc::sockaddr_in6)> { | |
| sockname_ipv6(|storage, len| unsafe { libc::accept(sockfd, storage, len) }) | |
| } | |
| /// Connect the socket to the specified IPv4 address. | |
| pub fn connect_ipv4(sockfd: libc::c_int, addr: libc::sockaddr_in) -> io::Result<()> { | |
| unsafe { | |
| errno_result(libc::connect( | |
| sockfd, | |
| (&addr as *const libc::sockaddr_in).cast(), | |
| size_of::<libc::sockaddr_in>() as libc::socklen_t, | |
| ))?; | |
| } | |
| Ok(()) | |
| } | |
| /// Connect the socket to the specified IPv6 address. | |
| pub fn connect_ipv6(sockfd: libc::c_int, addr: libc::sockaddr_in6) -> io::Result<()> { | |
| unsafe { | |
| errno_result(libc::connect( | |
| sockfd, | |
| (&addr as *const libc::sockaddr_in6).cast(), | |
| size_of::<libc::sockaddr_in6>() as libc::socklen_t, | |
| ))?; | |
| } | |
| Ok(()) | |
| } | |
| /// Set a socket option. It's the caller's responsibility to ensure that `T` is | |
| /// associated with the given socket option. | |
| /// | |
| /// This function is directly copied from the standard library implementation | |
| /// for sockets on UNIX targets. | |
| pub fn setsockopt<T>( | |
| sockfd: i32, | |
| level: libc::c_int, | |
| option_name: libc::c_int, | |
| option_value: T, | |
| ) -> io::Result<()> { | |
| let option_len = size_of::<T>() as libc::socklen_t; | |
| errno_result(unsafe { | |
| libc::setsockopt( | |
| sockfd, | |
| level, | |
| option_name, | |
| (&raw const option_value) as *const _, | |
| option_len, | |
| ) | |
| })?; | |
| Ok(()) | |
| } | |
| /// Get a socket option. It's the caller's responsibility that `T` is | |
| /// associated with the given socket option. | |
| /// | |
| /// This function is directly copied from the standard library implementation | |
| /// for sockets on UNIX targets. | |
| pub fn getsockopt<T: Copy>( | |
| sockfd: libc::c_int, | |
| level: libc::c_int, | |
| option_name: libc::c_int, | |
| ) -> io::Result<T> { | |
| let mut option_value = std::mem::MaybeUninit::<T>::zeroed(); | |
| let mut option_len = size_of::<T>() as libc::socklen_t; | |
| let provided_len = option_len; | |
| errno_result(unsafe { | |
| libc::getsockopt( | |
| sockfd, | |
| level, | |
| option_name, | |
| option_value.as_mut_ptr().cast(), | |
| &mut option_len, | |
| ) | |
| })?; | |
| // Ensure that there was no truncation. | |
| assert!(option_len == provided_len); | |
| Ok(unsafe { option_value.assume_init() }) | |
| } | |
| /// Wraps a call to a platform function that returns an IPv4 socket address. | |
| /// Returns a tuple containing the actual return value of the performed | |
| /// syscall and the written address of it. | |
| pub fn sockname_ipv4<F>(f: F) -> io::Result<(libc::c_int, libc::sockaddr_in)> | |
| where | |
| F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, | |
| { | |
| let (result, addr) = sockname(f)?; | |
| let LibcSocketAddr::V4(addr) = addr else { panic!("expected IPv4 address") }; | |
| Ok((result, addr)) | |
| } | |
| /// Wraps a call to a platform function that returns an IPv6 socket address. | |
| /// Returns a tuple containing the actual return value of the performed | |
| /// syscall and the written address of it. | |
| pub fn sockname_ipv6<F>(f: F) -> io::Result<(libc::c_int, libc::sockaddr_in6)> | |
| where | |
| F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, | |
| { | |
| let (result, addr) = sockname(f)?; | |
| let LibcSocketAddr::V6(addr) = addr else { panic!("expected IPv6 address") }; | |
| Ok((result, addr)) | |
| } | |
| enum LibcSocketAddr { | |
| V4(libc::sockaddr_in), | |
| V6(libc::sockaddr_in6), | |
| } | |
| /// Wraps a call to a platform function that returns a socket address. | |
| /// This is very much the same as the function with the same name in the | |
| /// standard library implementation. | |
| /// Returns a tuple containing the actual return value of the performed | |
| /// syscall and the written address of it. | |
| fn sockname<F>(f: F) -> io::Result<(libc::c_int, LibcSocketAddr)> | |
| where | |
| F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, | |
| { | |
| let mut storage = std::mem::MaybeUninit::<libc::sockaddr_storage>::zeroed(); | |
| let mut len = size_of::<libc::sockaddr_storage>() as libc::socklen_t; | |
| let value = errno_result(f(storage.as_mut_ptr().cast(), &mut len))?; | |
| // SAFETY: | |
| // The caller guarantees that the storage has been successfully initialized | |
| // and its size written to `len` if `f` returns a success. | |
| let address = unsafe { | |
| match (*storage.as_ptr()).ss_family as libc::c_int { | |
| libc::AF_INET => { | |
| assert!(len as usize >= size_of::<libc::sockaddr_in>()); | |
| LibcSocketAddr::V4(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in)) | |
| } | |
| libc::AF_INET6 => { | |
| assert!(len as usize >= size_of::<libc::sockaddr_in6>()); | |
| LibcSocketAddr::V6(*(storage.as_ptr() as *const _ as *const libc::sockaddr_in6)) | |
| } | |
| _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid argument")), | |
| } | |
| }; | |
| Ok((value, address)) | |
| } | |
| } |