use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::pin::Pin;
use std::task::{Context, Poll, ready};
use tokio::io::unix::AsyncFd;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
pub struct VsockStream {
inner: VsockStreamInner,
shutdown: VsockShutdown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VsockShutdown {
HalfClose,
CloseOnDropOnly,
}
enum VsockStreamInner {
Fd(AsyncFd<OwnedFd>),
Unix(tokio::net::UnixStream),
}
impl VsockStream {
fn set_nonblocking(fd: RawFd) -> io::Result<()> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
let ret = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub fn from_fd(fd: OwnedFd) -> io::Result<Self> {
Self::from_fd_with_shutdown(fd, VsockShutdown::HalfClose)
}
pub fn from_fd_with_shutdown(fd: OwnedFd, shutdown: VsockShutdown) -> io::Result<Self> {
Self::set_nonblocking(fd.as_raw_fd())?;
Ok(Self {
inner: VsockStreamInner::Fd(AsyncFd::new(fd)?),
shutdown,
})
}
pub unsafe fn from_raw_fd(fd: RawFd) -> io::Result<Self> {
Self::from_fd(unsafe { OwnedFd::from_raw_fd(fd) })
}
pub unsafe fn from_raw_fd_with_shutdown(
fd: RawFd,
shutdown: VsockShutdown,
) -> io::Result<Self> {
Self::from_fd_with_shutdown(unsafe { OwnedFd::from_raw_fd(fd) }, shutdown)
}
pub fn from_unix_stream(stream: tokio::net::UnixStream) -> Self {
Self::from_unix_stream_with_shutdown(stream, VsockShutdown::HalfClose)
}
pub fn from_unix_stream_with_shutdown(
stream: tokio::net::UnixStream,
shutdown: VsockShutdown,
) -> Self {
Self {
inner: VsockStreamInner::Unix(stream),
shutdown,
}
}
pub fn as_raw_fd(&self) -> RawFd {
match &self.inner {
VsockStreamInner::Fd(afd) => afd.as_raw_fd(),
VsockStreamInner::Unix(us) => us.as_raw_fd(),
}
}
}
impl AsyncRead for VsockStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match &mut self.get_mut().inner {
VsockStreamInner::Unix(us) => Pin::new(us).poll_read(cx, buf),
VsockStreamInner::Fd(inner) => loop {
let mut guard = ready!(inner.poll_read_ready(cx))?;
let unfilled = buf.initialize_unfilled();
match guard.try_io(|inner| {
loop {
let n = unsafe {
libc::read(
inner.as_raw_fd(),
unfilled.as_mut_ptr().cast::<libc::c_void>(),
unfilled.len(),
)
};
if n >= 0 {
return Ok(n as usize);
}
let err = io::Error::last_os_error();
if err.kind() != io::ErrorKind::Interrupted {
return Err(err);
}
}
}) {
Ok(Ok(n)) => {
buf.advance(n);
return Poll::Ready(Ok(()));
}
Ok(Err(e)) => return Poll::Ready(Err(e)),
Err(_would_block) => {}
}
},
}
}
}
impl AsyncWrite for VsockStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut self.get_mut().inner {
VsockStreamInner::Unix(us) => Pin::new(us).poll_write(cx, buf),
VsockStreamInner::Fd(inner) => loop {
let mut guard = ready!(inner.poll_write_ready(cx))?;
match guard.try_io(|inner| {
loop {
let n = unsafe {
libc::write(
inner.as_raw_fd(),
buf.as_ptr().cast::<libc::c_void>(),
buf.len(),
)
};
if n >= 0 {
return Ok(n as usize);
}
let err = io::Error::last_os_error();
if err.kind() != io::ErrorKind::Interrupted {
return Err(err);
}
}
}) {
Ok(result) => return Poll::Ready(result),
Err(_would_block) => {}
}
},
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut self.get_mut().inner {
VsockStreamInner::Unix(us) => Pin::new(us).poll_flush(cx),
VsockStreamInner::Fd(_) => Poll::Ready(Ok(())),
}
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let this = self.get_mut();
if this.shutdown == VsockShutdown::CloseOnDropOnly {
return Poll::Ready(Ok(()));
}
match &mut this.inner {
VsockStreamInner::Unix(us) => Pin::new(us).poll_shutdown(cx),
VsockStreamInner::Fd(inner) => loop {
let mut guard = ready!(inner.poll_write_ready(cx))?;
match guard.try_io(|inner| {
let ret = unsafe { libc::shutdown(inner.as_raw_fd(), libc::SHUT_WR) };
if ret < 0 {
let err = io::Error::last_os_error();
if matches!(err.raw_os_error(), Some(libc::ENOTCONN | libc::EINVAL)) {
Ok(())
} else {
Err(err)
}
} else {
Ok(())
}
}) {
Ok(result) => return Poll::Ready(result),
Err(_would_block) => {}
}
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
#[tokio::test]
async fn half_close_shutdown_closes_write_half() {
let (stream, mut peer) = tokio::net::UnixStream::pair().unwrap();
let mut stream =
VsockStream::from_unix_stream_with_shutdown(stream, VsockShutdown::HalfClose);
stream.shutdown().await.unwrap();
let mut buf = [0_u8; 1];
let n = peer.read(&mut buf).await.unwrap();
assert_eq!(n, 0);
}
#[tokio::test]
async fn close_on_drop_only_shutdown_keeps_connection_open_until_drop() {
let (stream, mut peer) = tokio::net::UnixStream::pair().unwrap();
let mut stream =
VsockStream::from_unix_stream_with_shutdown(stream, VsockShutdown::CloseOnDropOnly);
stream.shutdown().await.unwrap();
let mut buf = [0_u8; 1];
let read = tokio::time::timeout(Duration::from_millis(20), peer.read(&mut buf)).await;
assert!(read.is_err(), "shutdown must not half-close the peer");
drop(stream);
let n = peer.read(&mut buf).await.unwrap();
assert_eq!(n, 0);
}
}