use std::io;
use std::mem::ManuallyDrop;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpStream, UdpSocket};
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
use std::os::unix::net as unix;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux;
mod unix_addr;
#[cfg(ring)]
pub(crate) use self::linux::end_queued;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) use self::linux::{UNIX_INQ, enable_inq, read_inq, syscall};
pub(crate) use self::unix_addr::{UnixAddr, unix_addr};
unsafe fn borrow<S: FromRawFd>(fd: RawFd) -> ManuallyDrop<S> {
ManuallyDrop::new(unsafe { S::from_raw_fd(fd) })
}
#[inline(always)]
pub(crate) fn ready<T>(result: io::Result<T>) -> Option<io::Result<T>> {
match result {
Err(e) if matches!(e.raw_os_error(), Some(code) if code == libc::EAGAIN || code == libc::EWOULDBLOCK) => {
None
}
Err(e) => Some(Err(counted(e))),
ok => Some(ok),
}
}
#[cold]
#[inline(never)]
fn counted(e: io::Error) -> io::Error {
crate::rt::counters::io_error(&e);
e
}
#[inline(always)]
pub(super) fn retry<T>(mut call: impl FnMut() -> io::Result<T>) -> io::Result<T> {
loop {
match call() {
Err(e) if interrupted(&e) => {}
other => return other,
}
}
}
#[inline(always)]
pub(super) fn interrupted(e: &io::Error) -> bool {
match e.raw_os_error() {
Some(code) => code == libc::EINTR,
None => e.kind() == io::ErrorKind::Interrupted,
}
}
pub(super) fn cvt(ret: isize) -> io::Result<usize> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}
pub(super) fn cvt_int(ret: libc::c_int) -> io::Result<libc::c_int> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
#[inline]
unsafe fn read_raw(fd: RawFd, buf: *mut u8, len: usize) -> io::Result<usize> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return unsafe { syscall(libc::SYS_read, [fd as usize, buf as usize, len, 0, 0, 0]) };
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return cvt(unsafe { libc::read(fd, buf.cast(), len) });
}
#[inline(always)]
pub(crate) fn read(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
match unsafe { read_raw(fd, buf.as_mut_ptr(), buf.len()) } {
Err(e) if interrupted(&e) => read_again(fd, buf),
other => other,
}
}
#[cold]
#[inline(never)]
fn read_again(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
retry(|| unsafe { read_raw(fd, buf.as_mut_ptr(), buf.len()) })
}
pub(crate) fn peek(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
retry(|| cvt(unsafe { libc::recv(fd, buf.as_mut_ptr().cast(), buf.len(), libc::MSG_PEEK) }))
}
pub(crate) fn write(fd: RawFd, buf: &[u8]) -> io::Result<usize> {
retry(|| cvt(unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) }))
}
pub(crate) fn write_bufs(fd: RawFd, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
if let [one] = bufs {
return write(fd, one);
}
let count = bufs.len().min(IOV_MAX) as libc::c_int;
retry(|| cvt(unsafe { libc::writev(fd, bufs.as_ptr().cast(), count) }))
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
pub(crate) const SEND_FLAGS: libc::c_int = libc::MSG_NOSIGNAL;
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
)))]
pub(crate) const SEND_FLAGS: libc::c_int = 0;
#[inline]
pub(crate) fn send(fd: RawFd, buf: &[u8]) -> io::Result<usize> {
retry(|| {
unsafe { send_raw(fd, buf.as_ptr(), buf.len()) }
})
}
#[inline]
unsafe fn send_raw(fd: RawFd, buf: *const u8, len: usize) -> io::Result<usize> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return unsafe {
syscall(
libc::SYS_sendto,
[fd as usize, buf as usize, len, SEND_FLAGS as usize, 0, 0],
)
};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return cvt(unsafe { libc::send(fd, buf.cast(), len, SEND_FLAGS) });
}
const IOV_MAX: usize = 1024;
#[inline]
pub(crate) fn send_bufs(fd: RawFd, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
if let [one] = bufs {
return send(fd, one);
}
send_msg(fd, bufs)
}
#[inline(never)]
fn send_msg(fd: RawFd, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
let mut msg: libc::msghdr = unsafe { core::mem::zeroed() };
let bufs = bufs.get(..IOV_MAX).unwrap_or(bufs);
msg.msg_iov = bufs.as_ptr().cast_mut().cast();
msg.msg_iovlen = bufs.len() as _;
retry(|| cvt(unsafe { libc::sendmsg(fd, &msg, SEND_FLAGS) }))
}
pub(crate) fn read_bufs(fd: RawFd, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
if let [one] = bufs {
return read(fd, one);
}
let count = bufs.len().min(IOV_MAX) as libc::c_int;
retry(|| cvt(unsafe { libc::readv(fd, bufs.as_ptr().cast(), count) }))
}
pub(crate) fn shutdown(fd: RawFd, how: std::net::Shutdown) -> io::Result<()> {
shutdown_raw(fd, shut_how(how))
}
pub(crate) fn shutdown_raw(fd: RawFd, how: libc::c_int) -> io::Result<()> {
cvt_int(unsafe { libc::shutdown(fd, how) }).map(drop)
}
fn shut_how(how: std::net::Shutdown) -> libc::c_int {
match how {
std::net::Shutdown::Read => libc::SHUT_RD,
std::net::Shutdown::Write => libc::SHUT_WR,
std::net::Shutdown::Both => libc::SHUT_RDWR,
}
}
pub(crate) fn take_error(fd: RawFd) -> io::Result<Option<io::Error>> {
unsafe { borrow::<TcpStream>(fd) }.take_error()
}
pub(crate) fn set_nonblocking(fd: RawFd) -> io::Result<()> {
unsafe {
let flags = cvt_int(libc::fcntl(fd, libc::F_GETFL))?;
if flags & libc::O_NONBLOCK == 0 {
cvt_int(libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK))?;
}
}
Ok(())
}
pub(crate) fn set_nosigpipe(fd: RawFd) {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
let one: libc::c_int = 1;
unsafe {
libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_NOSIGPIPE,
(&one as *const libc::c_int).cast(),
size_of::<libc::c_int>() as libc::socklen_t,
)
};
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
let _ = fd;
}
fn socket_option(fd: RawFd, level: libc::c_int, name: libc::c_int) -> io::Result<libc::c_int> {
let mut value: libc::c_int = 0;
let mut len = size_of::<libc::c_int>() as libc::socklen_t;
cvt_int(unsafe {
libc::getsockopt(
fd,
level,
name,
(&mut value as *mut libc::c_int).cast(),
&mut len,
)
})?;
Ok(value)
}
pub(crate) fn kind_of(fd: RawFd) -> io::Result<crate::io::source::Kind> {
use crate::io::source::Kind;
let mut st: libc::stat = unsafe { core::mem::zeroed() };
cvt_int(unsafe { libc::fstat(fd, &mut st) })?;
match st.st_mode & libc::S_IFMT {
libc::S_IFSOCK => {}
libc::S_IFREG | libc::S_IFBLK => return Ok(Kind::File),
_ => return Ok(Kind::Bytes),
}
let ty = socket_option(fd, libc::SOL_SOCKET, libc::SO_TYPE)?;
let listening = match socket_option(fd, libc::SOL_SOCKET, libc::SO_ACCEPTCONN) {
Ok(listening) => listening != 0,
Err(e) if e.raw_os_error() == Some(libc::ENOPROTOOPT) => has_no_peer(fd),
Err(e) => return Err(e),
};
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
let mut len = size_of::<libc::sockaddr_storage>() as libc::socklen_t;
cvt_int(unsafe {
libc::getsockname(
fd,
(&mut storage as *mut libc::sockaddr_storage).cast(),
&mut len,
)
})?;
let unix = libc::c_int::from(storage.ss_family) == libc::AF_UNIX;
Ok(match (ty, unix, listening) {
(libc::SOCK_DGRAM, true, _) => Kind::UnixDatagram,
(libc::SOCK_DGRAM, false, _) => Kind::Udp,
(_, true, true) => Kind::UnixListener,
(_, false, true) => Kind::TcpListener,
(_, true, false) => Kind::Stream,
_ => Kind::Tcp,
})
}
fn has_no_peer(fd: RawFd) -> bool {
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
let mut len = size_of::<libc::sockaddr_storage>() as libc::socklen_t;
let ret = unsafe {
libc::getpeername(
fd,
(&mut storage as *mut libc::sockaddr_storage).cast(),
&mut len,
)
};
ret < 0 && io::Error::last_os_error().raw_os_error() == Some(libc::ENOTCONN)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn adopt(fd: OwnedFd) -> io::Result<OwnedFd> {
use std::os::fd::AsRawFd;
set_nonblocking(fd.as_raw_fd())?;
cvt_int(unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) })?;
set_nosigpipe(fd.as_raw_fd());
Ok(fd)
}
pub(crate) fn socket(domain: libc::c_int, ty: libc::c_int) -> io::Result<OwnedFd> {
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
{
let fd = cvt_int(unsafe {
libc::socket(domain, ty | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC, 0)
})?;
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
let fd = cvt_int(unsafe { libc::socket(domain, ty, 0) })?;
adopt(unsafe { OwnedFd::from_raw_fd(fd) })
}
}
fn accept_raw(fd: RawFd) -> io::Result<(OwnedFd, libc::sockaddr_storage, libc::socklen_t)> {
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
let mut len = size_of::<libc::sockaddr_storage>() as libc::socklen_t;
let addr = (&mut storage as *mut libc::sockaddr_storage).cast::<libc::sockaddr>();
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
let conn = retry(|| {
cvt_int(unsafe {
libc::accept4(fd, addr, &mut len, libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC)
})
})?;
#[cfg(any(target_os = "macos", target_os = "ios"))]
let conn = retry(|| {
cvt_int(unsafe { libc::accept(fd, addr, &mut len) })
})?;
let conn = unsafe { OwnedFd::from_raw_fd(conn) };
#[cfg(any(target_os = "macos", target_os = "ios"))]
let conn = adopt(conn)?;
Ok((conn, storage, len))
}
pub(crate) fn accept_inet(fd: RawFd) -> io::Result<(OwnedFd, SocketAddr)> {
let (conn, storage, len) = accept_raw(fd)?;
Ok((conn, inet_addr(&storage, len)?))
}
pub(crate) fn accept_unix(fd: RawFd) -> io::Result<(OwnedFd, unix::SocketAddr)> {
let (conn, storage, len) = accept_raw(fd)?;
let un = unsafe { &*(&storage as *const libc::sockaddr_storage).cast::<libc::sockaddr_un>() };
Ok((conn, unix_addr(un, len)?))
}
pub(crate) fn inet_addr(
storage: &libc::sockaddr_storage,
len: libc::socklen_t,
) -> io::Result<SocketAddr> {
let len = len as usize;
match libc::c_int::from(storage.ss_family) {
libc::AF_INET if len >= size_of::<libc::sockaddr_in>() => {
let a =
unsafe { &*(storage as *const libc::sockaddr_storage).cast::<libc::sockaddr_in>() };
Ok(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::from(u32::from_be(a.sin_addr.s_addr)),
u16::from_be(a.sin_port),
)))
}
libc::AF_INET6 if len >= size_of::<libc::sockaddr_in6>() => {
let a = unsafe {
&*(storage as *const libc::sockaddr_storage).cast::<libc::sockaddr_in6>()
};
Ok(SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::from(a.sin6_addr.s6_addr),
u16::from_be(a.sin6_port),
a.sin6_flowinfo,
a.sin6_scope_id,
)))
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"the peer's address is not an internet address",
)),
}
}
pub(crate) fn raw_inet(addr: &SocketAddr) -> (libc::sockaddr_storage, libc::socklen_t) {
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
let len = match addr {
SocketAddr::V4(v4) => {
let a = unsafe {
&mut *(&mut storage as *mut libc::sockaddr_storage).cast::<libc::sockaddr_in>()
};
a.sin_family = libc::AF_INET as libc::sa_family_t;
a.sin_port = v4.port().to_be();
a.sin_addr.s_addr = u32::from(*v4.ip()).to_be();
size_of::<libc::sockaddr_in>()
}
SocketAddr::V6(v6) => {
let a = unsafe {
&mut *(&mut storage as *mut libc::sockaddr_storage).cast::<libc::sockaddr_in6>()
};
a.sin6_family = libc::AF_INET6 as libc::sa_family_t;
a.sin6_port = v6.port().to_be();
a.sin6_addr.s6_addr = v6.ip().octets();
a.sin6_flowinfo = v6.flowinfo();
a.sin6_scope_id = v6.scope_id();
size_of::<libc::sockaddr_in6>()
}
};
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
{
storage.ss_len = len as u8;
}
(storage, len as libc::socklen_t)
}
pub(crate) fn bind_inet(fd: RawFd, addr: &SocketAddr) -> io::Result<()> {
let (storage, len) = raw_inet(addr);
cvt_int(unsafe { libc::bind(fd, (&storage as *const libc::sockaddr_storage).cast(), len) })
.map(drop)
}
pub(crate) fn bind_unix(fd: RawFd, addr: &UnixAddr) -> io::Result<()> {
cvt_int(unsafe { libc::bind(fd, addr.as_ptr(), addr.len) }).map(drop)
}
pub(crate) fn listen(fd: RawFd, backlog: u32) -> io::Result<()> {
let backlog = libc::c_int::try_from(backlog).unwrap_or(libc::c_int::MAX);
cvt_int(unsafe { libc::listen(fd, backlog) }).map(drop)
}
pub(crate) fn connect_inet(fd: RawFd, addr: &SocketAddr) -> io::Result<bool> {
let (storage, len) = raw_inet(addr);
connect_raw(fd, (&storage as *const libc::sockaddr_storage).cast(), len)
}
pub(crate) fn connect_unix(fd: RawFd, addr: &UnixAddr) -> io::Result<bool> {
connect_raw(fd, addr.as_ptr(), addr.len)
}
fn connect_raw(fd: RawFd, addr: *const libc::sockaddr, len: libc::socklen_t) -> io::Result<bool> {
if unsafe { libc::connect(fd, addr, len) } == 0 {
return Ok(true);
}
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EINPROGRESS) | Some(libc::EINTR) => Ok(false),
_ => Err(err),
}
}
#[inline(always)]
fn recv_from_raw(
fd: RawFd,
buf: &mut [core::mem::MaybeUninit<u8>],
peek: bool,
storage: &mut libc::sockaddr_storage,
) -> io::Result<(usize, libc::socklen_t)> {
let mut len = size_of::<libc::sockaddr_storage>() as libc::socklen_t;
let flags = if peek { libc::MSG_PEEK } else { 0 };
let n = retry(|| {
#[cfg(any(target_os = "linux", target_os = "android"))]
return unsafe {
syscall(
libc::SYS_recvfrom,
[
fd as usize,
buf.as_mut_ptr() as usize,
buf.len(),
flags as usize,
core::ptr::from_mut(storage) as usize,
(&raw mut len) as usize,
],
)
};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return cvt(unsafe {
libc::recvfrom(
fd,
buf.as_mut_ptr().cast(),
buf.len(),
flags,
core::ptr::from_mut(storage).cast(),
&mut len,
)
});
})?;
Ok((n, len))
}
unsafe fn send_to_raw(
fd: RawFd,
buf: &[u8],
addr: *const libc::sockaddr,
len: libc::socklen_t,
) -> io::Result<usize> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return unsafe {
syscall(
libc::SYS_sendto,
[
fd as usize,
buf.as_ptr() as usize,
buf.len(),
SEND_FLAGS as usize,
addr as usize,
len as usize,
],
)
};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return cvt(unsafe { libc::sendto(fd, buf.as_ptr().cast(), buf.len(), SEND_FLAGS, addr, len) });
}
#[inline(always)]
pub(crate) fn recv_from_udp_uninit(
fd: RawFd,
buf: &mut [core::mem::MaybeUninit<u8>],
peek: bool,
) -> io::Result<(usize, SocketAddr)> {
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
let (n, len) = recv_from_raw(fd, buf, peek, &mut storage)?;
if len == 0 || libc::c_int::from(storage.ss_family) == libc::AF_UNSPEC {
return no_sender(fd, n);
}
Ok((n, inet_addr(&storage, len)?))
}
#[inline(always)]
pub(crate) fn recv_from_udp(
fd: RawFd,
buf: &mut [u8],
peek: bool,
) -> io::Result<(usize, SocketAddr)> {
let into = unsafe { &mut *(core::ptr::from_mut(buf) as *mut [core::mem::MaybeUninit<u8>]) };
recv_from_udp_uninit(fd, into, peek)
}
#[cold]
#[inline(never)]
fn no_sender(fd: RawFd, n: usize) -> io::Result<(usize, SocketAddr)> {
let socket = unsafe { borrow::<UdpSocket>(fd) };
match socket.peer_addr() {
Ok(peer) => Ok((n, peer)),
Err(_) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the system named no sender for a datagram received into an empty buffer",
)),
}
}
pub(crate) fn recv_from_unix_uninit(
fd: RawFd,
buf: &mut [core::mem::MaybeUninit<u8>],
peek: bool,
) -> io::Result<(usize, unix::SocketAddr)> {
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
let (n, len) = recv_from_raw(fd, buf, peek, &mut storage)?;
let un = unsafe { &*(&raw const storage).cast::<libc::sockaddr_un>() };
Ok((n, unix_addr(un, len)?))
}
#[inline(always)]
pub(crate) fn recv_from_unix(
fd: RawFd,
buf: &mut [u8],
peek: bool,
) -> io::Result<(usize, unix::SocketAddr)> {
if peek {
let into = unsafe { &mut *(core::ptr::from_mut(buf) as *mut [core::mem::MaybeUninit<u8>]) };
return recv_from_unix_uninit(fd, into, true);
}
let socket = unsafe { borrow::<unix::UnixDatagram>(fd) };
retry(|| socket.recv_from(buf))
}
pub(crate) fn send_to_udp(fd: RawFd, buf: &[u8], addr: SocketAddr) -> io::Result<usize> {
let (raw, len) = raw_inet(&addr);
retry(|| unsafe { send_to_raw(fd, buf, (&raw const raw).cast(), len) })
}
pub(crate) fn send_to_unix(fd: RawFd, buf: &[u8], addr: &UnixAddr) -> io::Result<usize> {
retry(|| unsafe { send_to_raw(fd, buf, addr.as_ptr(), addr.len) })
}
pub(super) fn file_offset(offset: u64) -> io::Result<libc::off_t> {
libc::off_t::try_from(offset).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"the offset is past what the file can hold",
)
})
}
pub(crate) fn pread(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<usize> {
#[cfg(test)]
file_calls::note(fd, (b'r', buf.len(), offset));
let offset = file_offset(offset)?;
retry(|| cvt(unsafe { libc::pread(fd, buf.as_mut_ptr().cast(), buf.len(), offset) }))
}
pub(crate) fn pwrite_all(fd: RawFd, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
while !buf.is_empty() {
#[cfg(test)]
file_calls::note(fd, (b'w', buf.len(), offset));
let at = file_offset(offset)?;
let n = retry(|| cvt(unsafe { libc::pwrite(fd, buf.as_ptr().cast(), buf.len(), at) }))?;
if n == 0 {
return Err(io::ErrorKind::WriteZero.into());
}
buf = &buf[n..];
offset += n as u64;
}
Ok(())
}
pub(crate) fn append_all(fd: RawFd, mut buf: &[u8]) -> io::Result<u64> {
while !buf.is_empty() {
let n = retry(|| cvt(unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) }))?;
if n == 0 {
return Err(io::ErrorKind::WriteZero.into());
}
buf = &buf[n..];
}
seek(fd, None)
}
pub(crate) struct FileMode {
pub(crate) append: bool,
pub(crate) direct: bool,
}
pub(crate) fn file_mode(fd: RawFd) -> io::Result<FileMode> {
let flags = cvt_int(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
#[cfg(any(target_os = "linux", target_os = "android"))]
let direct = flags & libc::O_DIRECT != 0;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
let direct = false;
Ok(FileMode {
append: flags & libc::O_APPEND != 0,
direct,
})
}
pub(crate) fn seek(fd: RawFd, offset: Option<u64>) -> io::Result<u64> {
let (to, whence) = match offset {
Some(offset) => (file_offset(offset)?, libc::SEEK_SET),
None => (0, libc::SEEK_CUR),
};
let at = unsafe { libc::lseek(fd, to, whence) };
if at < 0 {
Err(io::Error::last_os_error())
} else {
Ok(at as u64)
}
}
pub(crate) fn file_size(fd: RawFd) -> io::Result<u64> {
let mut st: libc::stat = unsafe { core::mem::zeroed() };
cvt_int(unsafe { libc::fstat(fd, &mut st) })?;
Ok(st.st_size as u64)
}
pub(crate) fn duplicate(fd: RawFd) -> io::Result<OwnedFd> {
let copy = cvt_int(unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) })?;
Ok(unsafe { OwnedFd::from_raw_fd(copy) })
}
pub(crate) const SENDS_FILES: bool = cfg!(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "freebsd"
));
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn send_file(sock: RawFd, file: RawFd, offset: u64, len: usize) -> io::Result<usize> {
let mut at = file_offset(offset)?;
retry(|| cvt(unsafe { libc::sendfile(sock, file, &mut at, len) }))
}
#[cfg(target_os = "macos")]
pub(crate) fn send_file(sock: RawFd, file: RawFd, offset: u64, len: usize) -> io::Result<usize> {
let at = file_offset(offset)?;
retry(|| {
let mut sent = libc::off_t::try_from(len).unwrap_or(libc::off_t::MAX);
let r = unsafe { libc::sendfile(file, sock, at, &mut sent, core::ptr::null_mut(), 0) };
match cvt_int(r) {
Ok(_) => Ok(sent as usize),
Err(_) if sent > 0 => Ok(sent as usize),
Err(err) => Err(err),
}
})
}
#[cfg(target_os = "freebsd")]
pub(crate) fn send_file(sock: RawFd, file: RawFd, offset: u64, len: usize) -> io::Result<usize> {
let at = file_offset(offset)?;
retry(|| {
let mut sent: libc::off_t = 0;
let r = unsafe { libc::sendfile(file, sock, at, len, core::ptr::null_mut(), &mut sent, 0) };
match cvt_int(r) {
Ok(_) => Ok(sent as usize),
Err(_) if sent > 0 => Ok(sent as usize),
Err(err) => Err(err),
}
})
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "freebsd"
)))]
pub(crate) fn send_file(_: RawFd, _: RawFd, _: u64, _: usize) -> io::Result<usize> {
Err(io::ErrorKind::Unsupported.into())
}
pub(crate) fn poll_writable(fd: RawFd) -> io::Result<()> {
let mut poll = libc::pollfd {
fd,
events: libc::POLLOUT,
revents: 0,
};
match retry(|| cvt_int(unsafe { libc::poll(&mut poll, 1, 0) }))? {
0 => Err(io::ErrorKind::WouldBlock.into()),
_ => Ok(()),
}
}
pub(crate) fn read_uninit(fd: RawFd, buf: &mut [core::mem::MaybeUninit<u8>]) -> io::Result<usize> {
retry(|| unsafe { read_raw(fd, buf.as_mut_ptr().cast(), buf.len()) })
}
pub(crate) fn pread_uninit(
fd: RawFd,
buf: &mut [core::mem::MaybeUninit<u8>],
offset: u64,
) -> io::Result<usize> {
#[cfg(test)]
file_calls::note(fd, (b'r', buf.len(), offset));
let offset = file_offset(offset)?;
retry(|| cvt(unsafe { libc::pread(fd, buf.as_mut_ptr().cast(), buf.len(), offset) }))
}
#[cfg(ring)]
pub(crate) fn adopt_accepted(_listener: RawFd, _conn: &OwnedFd) -> io::Result<()> {
Ok(())
}
#[cfg(ring)]
pub(crate) fn adopt_connected(_fd: RawFd) -> io::Result<()> {
Ok(())
}
#[cfg(test)]
pub(crate) mod file_calls {
use std::os::fd::RawFd;
use std::sync::Mutex;
pub(crate) type Call = (u8, usize, u64);
static CALLS: Mutex<Vec<(RawFd, Call)>> = Mutex::new(Vec::new());
pub(crate) fn note(fd: RawFd, call: Call) {
if let Ok(mut calls) = CALLS.lock() {
calls.push((fd, call));
}
}
#[cfg(target_os = "linux")]
pub(crate) fn take(fd: RawFd) -> Vec<Call> {
let Ok(mut calls) = CALLS.lock() else {
return Vec::new();
};
let (mine, rest) = core::mem::take(&mut *calls)
.into_iter()
.partition::<Vec<_>, _>(|(of, _)| *of == fd);
*calls = rest;
mine.into_iter().map(|(_, call)| call).collect()
}
}