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::ffi::OsStrExt;
use std::os::unix::net::{self as unix, UnixDatagram};
use std::path::Path;
unsafe fn borrow<S: FromRawFd>(fd: RawFd) -> ManuallyDrop<S> {
ManuallyDrop::new(unsafe { S::from_raw_fd(fd) })
}
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) => {
crate::rt::counters::io_error(&e);
Some(Err(e))
}
ok => Some(ok),
}
}
fn retry<T>(mut call: impl FnMut() -> io::Result<T>) -> io::Result<T> {
loop {
match call() {
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
other => return other,
}
}
}
fn cvt(ret: isize) -> io::Result<usize> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}
fn cvt_int(ret: libc::c_int) -> io::Result<libc::c_int> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
pub(crate) fn read(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
retry(|| cvt(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) }))
}
pub(crate) fn write(fd: RawFd, buf: &[u8]) -> io::Result<usize> {
retry(|| cvt(unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) }))
}
#[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"
)))]
const SEND_FLAGS: libc::c_int = 0;
pub(crate) fn send(fd: RawFd, buf: &[u8]) -> io::Result<usize> {
retry(|| cvt(unsafe { libc::send(fd, buf.as_ptr().cast(), buf.len(), SEND_FLAGS) }))
}
pub(crate) fn shutdown_write(fd: RawFd) -> io::Result<()> {
cvt_int(unsafe { libc::shutdown(fd, libc::SHUT_WR) }).map(drop)
}
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)
}
pub(crate) fn connect_unix_waiting(addr: &UnixAddr) -> io::Result<OwnedFd> {
use std::os::fd::AsRawFd;
let socket = socket(libc::AF_UNIX, libc::SOCK_STREAM)?;
let fd = socket.as_raw_fd();
unsafe {
let flags = cvt_int(libc::fcntl(fd, libc::F_GETFL))?;
cvt_int(libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK))?;
retry(|| cvt_int(libc::connect(fd, addr.as_ptr(), addr.len)))?;
cvt_int(libc::fcntl(fd, libc::F_SETFL, flags))?;
}
Ok(socket)
}
#[derive(Clone, Copy)]
pub(crate) struct UnixAddr {
pub(crate) raw: libc::sockaddr_un,
pub(crate) len: libc::socklen_t,
}
impl UnixAddr {
pub(crate) fn path(path: &Path) -> io::Result<UnixAddr> {
let bytes = path.as_os_str().as_bytes();
if bytes.contains(&0) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the socket path holds a NUL byte",
));
}
Self::from_bytes(bytes, 1)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn abstract_name(name: &[u8]) -> io::Result<UnixAddr> {
let mut with_nul = [0u8; 108];
let len = name.len() + 1;
if len > with_nul.len() {
return Err(too_long());
}
with_nul[1..len].copy_from_slice(name);
Self::from_bytes(&with_nul[..len], 0)
}
pub(crate) fn of(addr: &unix::SocketAddr) -> io::Result<UnixAddr> {
if let Some(path) = addr.as_pathname() {
return Self::path(path);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
if let Some(name) = addr.as_abstract_name() {
return Self::abstract_name(name);
}
}
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"an unnamed Unix socket address names no socket",
))
}
fn from_bytes(bytes: &[u8], extra: usize) -> io::Result<UnixAddr> {
let mut raw: libc::sockaddr_un = unsafe { core::mem::zeroed() };
if bytes.len() + extra > raw.sun_path.len() {
return Err(too_long());
}
raw.sun_family = libc::AF_UNIX as libc::sa_family_t;
for (dst, &src) in raw.sun_path.iter_mut().zip(bytes) {
*dst = src as libc::c_char;
}
let len = core::mem::offset_of!(libc::sockaddr_un, sun_path) + bytes.len() + extra;
Ok(UnixAddr {
raw,
len: len as libc::socklen_t,
})
}
pub(crate) fn as_ptr(&self) -> *const libc::sockaddr {
(&self.raw as *const libc::sockaddr_un).cast()
}
}
fn too_long() -> io::Error {
io::Error::new(
io::ErrorKind::InvalidInput,
"the socket address is too long",
)
}
impl core::fmt::Debug for UnixAddr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match unix_addr(&self.raw, self.len) {
Ok(addr) => write!(f, "{addr:?}"),
Err(_) => f.write_str("(unnamed)"),
}
}
}
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),
}
}
pub(crate) fn recv_from_udp(fd: RawFd, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
let socket = unsafe { borrow::<UdpSocket>(fd) };
retry(|| socket.recv_from(buf))
}
pub(crate) fn recv_from_unix(fd: RawFd, buf: &mut [u8]) -> io::Result<(usize, unix::SocketAddr)> {
let socket = unsafe { borrow::<UnixDatagram>(fd) };
retry(|| socket.recv_from(buf))
}
pub(crate) fn send_to_udp(fd: RawFd, buf: &[u8], addr: SocketAddr) -> io::Result<usize> {
let socket = unsafe { borrow::<UdpSocket>(fd) };
retry(|| socket.send_to(buf, addr))
}
pub(crate) fn send_to_unix(fd: RawFd, buf: &[u8], addr: &UnixAddr) -> io::Result<usize> {
retry(|| {
cvt(unsafe {
libc::sendto(
fd,
buf.as_ptr().cast(),
buf.len(),
SEND_FLAGS,
addr.as_ptr(),
addr.len,
)
})
})
}
pub(crate) fn unix_addr(
storage: &libc::sockaddr_un,
len: libc::socklen_t,
) -> io::Result<unix::SocketAddr> {
let base = core::mem::offset_of!(libc::sockaddr_un, sun_path);
let path_len = (len as usize)
.saturating_sub(base)
.min(storage.sun_path.len());
let raw: &[u8] =
unsafe { core::slice::from_raw_parts(storage.sun_path.as_ptr().cast(), path_len) };
#[cfg(any(target_os = "linux", target_os = "android"))]
if let [0, name @ ..] = raw
&& !name.is_empty()
{
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
return unix::SocketAddr::from_abstract_name(name);
}
let path = raw.split(|&b| b == 0).next().unwrap_or_default();
if path.is_empty() {
return unnamed();
}
unix::SocketAddr::from_pathname(std::ffi::OsStr::from_bytes(path))
}
fn unnamed() -> io::Result<unix::SocketAddr> {
static UNNAMED: std::sync::OnceLock<unix::SocketAddr> = std::sync::OnceLock::new();
if let Some(addr) = UNNAMED.get() {
return Ok(addr.clone());
}
let addr = UnixDatagram::unbound()?.local_addr()?;
Ok(UNNAMED.get_or_init(|| addr).clone())
}
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> {
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() {
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) fn appends(fd: RawFd) -> io::Result<bool> {
let flags = cvt_int(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
Ok(flags & libc::O_APPEND != 0)
}
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_file(fd: RawFd) -> io::Result<OwnedFd> {
duplicate(fd)
}
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) fn read_uninit(fd: RawFd, buf: &mut [core::mem::MaybeUninit<u8>]) -> io::Result<usize> {
retry(|| cvt(unsafe { libc::read(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> {
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(())
}