diff --git a/src/net/mod.rs b/src/net/mod.rs index d941d8010..8373a49cb 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -33,7 +33,9 @@ mod udp; #[cfg(not(all(target_os = "wasi", target_env = "p1")))] pub use self::udp::UdpSocket; -#[cfg(unix)] +#[cfg(any(unix, windows))] mod uds; #[cfg(unix)] pub use self::uds::{UnixDatagram, UnixListener, UnixStream}; +#[cfg(windows)] +pub use self::uds::{SocketAddr, UnixListener, UnixStream}; diff --git a/src/net/uds/listener.rs b/src/net/uds/listener.rs index a255972a5..a1b77cfa3 100644 --- a/src/net/uds/listener.rs +++ b/src/net/uds/listener.rs @@ -1,15 +1,26 @@ +#[cfg(unix)] use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +#[cfg(unix)] use std::os::unix::net::{self, SocketAddr}; +#[cfg(windows)] +use std::os::windows::io::{ + AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket, +}; use std::path::Path; use std::{fmt, io}; use crate::io_source::IoSource; use crate::net::UnixStream; +#[cfg(windows)] +use crate::sys::uds::{Socket, SocketAddr}; use crate::{event, sys, Interest, Registry, Token}; /// A non-blocking Unix domain socket server. pub struct UnixListener { + #[cfg(unix)] inner: IoSource, + #[cfg(windows)] + inner: IoSource, } impl UnixListener { @@ -21,7 +32,19 @@ impl UnixListener { /// Creates a new `UnixListener` bound to the specified socket `address`. pub fn bind_addr(address: &SocketAddr) -> io::Result { - sys::uds::listener::bind_addr(address).map(UnixListener::from_std) + #[cfg(unix)] + { + sys::uds::listener::bind_addr(address).map(UnixListener::from_std) + } + + // Once std::os::windows::net::UnixListener is stabilized, this can be removed. + #[cfg(windows)] + { + let socket = sys::uds::listener::bind_addr(address)?; + Ok(UnixListener { + inner: IoSource::new(Socket::from(socket)), + }) + } } /// Creates a new `UnixListener` from a standard `net::UnixListener`. @@ -30,6 +53,7 @@ impl UnixListener { /// standard library in the Mio equivalent. The conversion assumes nothing /// about the underlying listener; it is left up to the user to set it in /// non-blocking mode. + #[cfg(unix)] pub fn from_std(listener: net::UnixListener) -> UnixListener { UnixListener { inner: IoSource::new(listener), @@ -85,18 +109,21 @@ impl fmt::Debug for UnixListener { } } +#[cfg(unix)] impl IntoRawFd for UnixListener { fn into_raw_fd(self) -> RawFd { self.inner.into_inner().into_raw_fd() } } +#[cfg(unix)] impl AsRawFd for UnixListener { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } +#[cfg(unix)] impl FromRawFd for UnixListener { /// Converts a `RawFd` to a `UnixListener`. /// @@ -109,6 +136,7 @@ impl FromRawFd for UnixListener { } } +#[cfg(unix)] impl From for net::UnixListener { fn from(listener: UnixListener) -> Self { // Safety: This is safe since we are extracting the raw fd from a well-constructed @@ -118,20 +146,78 @@ impl From for net::UnixListener { } } +#[cfg(unix)] impl From for OwnedFd { fn from(unix_listener: UnixListener) -> Self { unix_listener.inner.into_inner().into() } } +#[cfg(unix)] impl AsFd for UnixListener { fn as_fd(&self) -> BorrowedFd<'_> { self.inner.as_fd() } } +#[cfg(unix)] impl From for UnixListener { fn from(fd: OwnedFd) -> Self { UnixListener::from_std(From::from(fd)) } } + +#[cfg(windows)] +impl AsRawSocket for UnixListener { + fn as_raw_socket(&self) -> RawSocket { + self.inner.as_raw_socket() + } +} + +#[cfg(windows)] +impl IntoRawSocket for UnixListener { + fn into_raw_socket(self) -> RawSocket { + self.inner.into_inner().into_raw_socket() + } +} + +#[cfg(windows)] +impl FromRawSocket for UnixListener { + /// # Safety + /// + /// The socket must be a valid, bound, listening `AF_UNIX` socket in + /// non-blocking mode. + unsafe fn from_raw_socket(sock: RawSocket) -> UnixListener { + UnixListener { + inner: IoSource::new(unsafe { Socket::from_raw_socket(sock) }), + } + } +} + +#[cfg(windows)] +impl AsSocket for UnixListener { + fn as_socket(&self) -> BorrowedSocket<'_> { + // SAFETY: the raw socket is valid for the lifetime of `self`. + unsafe { BorrowedSocket::borrow_raw(self.inner.as_raw_socket()) } + } +} + +#[cfg(windows)] +impl From for OwnedSocket { + fn from(listener: UnixListener) -> Self { + listener.inner.into_inner().into() + } +} + +#[cfg(windows)] +impl From for UnixListener { + /// # Notes + /// + /// The caller is responsible for ensuring that the socket is in + /// non-blocking mode. + fn from(socket: OwnedSocket) -> Self { + UnixListener { + inner: IoSource::new(Socket::from(socket)), + } + } +} diff --git a/src/net/uds/mod.rs b/src/net/uds/mod.rs index e02fd80dc..7c2ad5377 100644 --- a/src/net/uds/mod.rs +++ b/src/net/uds/mod.rs @@ -1,4 +1,6 @@ +#[cfg(unix)] mod datagram; +#[cfg(unix)] pub use self::datagram::UnixDatagram; mod listener; @@ -6,3 +8,7 @@ pub use self::listener::UnixListener; mod stream; pub use self::stream::UnixStream; + +// This is a stand-in until std::os::windows::net::SocketAddr is stabilized. +#[cfg(windows)] +pub use crate::sys::uds::SocketAddr; diff --git a/src/net/uds/stream.rs b/src/net/uds/stream.rs index 244f40455..227436bda 100644 --- a/src/net/uds/stream.rs +++ b/src/net/uds/stream.rs @@ -1,16 +1,27 @@ use std::fmt; use std::io::{self, IoSlice, IoSliceMut, Read, Write}; use std::net::Shutdown; +#[cfg(unix)] use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +#[cfg(unix)] use std::os::unix::net::{self, SocketAddr}; +#[cfg(windows)] +use std::os::windows::io::{ + AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket, +}; use std::path::Path; use crate::io_source::IoSource; +#[cfg(windows)] +use crate::sys::uds::{Socket, SocketAddr}; use crate::{event, sys, Interest, Registry, Token}; /// A non-blocking Unix stream socket. pub struct UnixStream { + #[cfg(unix)] inner: IoSource, + #[cfg(windows)] + inner: IoSource, } impl UnixStream { @@ -28,7 +39,19 @@ impl UnixStream { /// This may return a `WouldBlock` in which case the socket connection /// cannot be completed immediately. Usually it means the backlog is full. pub fn connect_addr(address: &SocketAddr) -> io::Result { - sys::uds::stream::connect_addr(address).map(UnixStream::from_std) + #[cfg(unix)] + { + sys::uds::stream::connect_addr(address).map(UnixStream::from_std) + } + + // Once std::os::windows::net::UnixStream is stabilized, this can be removed. + #[cfg(windows)] + { + let socket = sys::uds::stream::connect_addr(address)?; + Ok(UnixStream { + inner: IoSource::new(Socket::from(socket)), + }) + } } /// Creates a new `UnixStream` from a standard `net::UnixStream`. @@ -43,6 +66,7 @@ impl UnixStream { /// The Unix stream here will not have `connect` called on it, so it /// should already be connected via some other means (be it manually, or /// the standard library). + #[cfg(unix)] pub fn from_std(stream: net::UnixStream) -> UnixStream { UnixStream { inner: IoSource::new(stream), @@ -52,6 +76,7 @@ impl UnixStream { /// Creates an unnamed pair of connected sockets. /// /// Returns two `UnixStream`s which are connected to each other. + #[cfg(unix)] pub fn pair() -> io::Result<(UnixStream, UnixStream)> { sys::uds::stream::pair().map(|(stream1, stream2)| { (UnixStream::from_std(stream1), UnixStream::from_std(stream2)) @@ -95,7 +120,8 @@ impl UnixStream { /// /// # Examples /// - /// ``` + #[cfg_attr(unix, doc = "```")] + #[cfg_attr(not(unix), doc = "```ignore")] /// # use std::error::Error; /// # /// # fn main() -> Result<(), Box> { @@ -143,6 +169,61 @@ impl UnixStream { /// # Ok(()) /// # } /// ``` + /// + #[cfg_attr(windows, doc = "```")] + #[cfg_attr(not(windows), doc = "```ignore")] + /// # use std::error::Error; + /// # + /// # fn main() -> Result<(), Box> { + /// use std::io; + /// use std::os::windows::io::AsRawSocket; + /// use std::{env, fs, thread, time::Duration}; + /// use mio::net::{UnixListener, UnixStream}; + /// use windows_sys::Win32::Networking::WinSock; + /// + /// let path = env::temp_dir().join("mio-uds-try_io-example"); + /// let _ = fs::remove_file(&path); + /// let listener = UnixListener::bind(&path)?; + /// let connect_path = path.clone(); + /// let handle = thread::spawn(move || UnixStream::connect(&connect_path).unwrap()); + /// + /// // Spin briefly until the connection is accepted. + /// let stream = loop { + /// match listener.accept() { + /// Ok((s, _)) => break s, + /// Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + /// thread::sleep(Duration::from_millis(10)); + /// } + /// Err(e) => return Err(e.into()), + /// } + /// }; + /// let _peer = handle.join().unwrap(); + /// + /// // Write to the stream using a direct WinSock call, of course the + /// // `io::Write` implementation would be easier to use. + /// let buf = b"hello"; + /// let n = stream.try_io(|| { + /// let res = unsafe { + /// WinSock::send( + /// stream.as_raw_socket() as _, + /// buf.as_ptr(), + /// buf.len() as i32, + /// 0, + /// ) + /// }; + /// if res != -1 { + /// Ok(res as usize) + /// } else { + /// // If WSAEWOULDBLOCK is set by WinSock::send, the closure + /// // should return `WouldBlock` error. + /// Err(io::Error::last_os_error()) + /// } + /// })?; + /// eprintln!("write {} bytes", n); + /// # let _ = fs::remove_file(&path); + /// # Ok(()) + /// # } + /// ``` pub fn try_io(&self, f: F) -> io::Result where F: FnOnce() -> io::Result, @@ -229,18 +310,21 @@ impl fmt::Debug for UnixStream { } } +#[cfg(unix)] impl IntoRawFd for UnixStream { fn into_raw_fd(self) -> RawFd { self.inner.into_inner().into_raw_fd() } } +#[cfg(unix)] impl AsRawFd for UnixStream { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } +#[cfg(unix)] impl FromRawFd for UnixStream { /// Converts a `RawFd` to a `UnixStream`. /// @@ -253,6 +337,7 @@ impl FromRawFd for UnixStream { } } +#[cfg(unix)] impl From for net::UnixStream { fn from(stream: UnixStream) -> Self { // Safety: This is safe since we are extracting the raw fd from a well-constructed @@ -262,20 +347,78 @@ impl From for net::UnixStream { } } +#[cfg(unix)] impl From for OwnedFd { fn from(unix_stream: UnixStream) -> Self { unix_stream.inner.into_inner().into() } } +#[cfg(unix)] impl AsFd for UnixStream { fn as_fd(&self) -> BorrowedFd<'_> { self.inner.as_fd() } } +#[cfg(unix)] impl From for UnixStream { fn from(fd: OwnedFd) -> Self { UnixStream::from_std(From::from(fd)) } } + +#[cfg(windows)] +impl AsRawSocket for UnixStream { + fn as_raw_socket(&self) -> RawSocket { + self.inner.as_raw_socket() + } +} + +#[cfg(windows)] +impl IntoRawSocket for UnixStream { + fn into_raw_socket(self) -> RawSocket { + self.inner.into_inner().into_raw_socket() + } +} + +#[cfg(windows)] +impl FromRawSocket for UnixStream { + /// # Safety + /// + /// The socket must be a valid, connected `AF_UNIX` stream socket in + /// non-blocking mode. + unsafe fn from_raw_socket(sock: RawSocket) -> UnixStream { + UnixStream { + inner: IoSource::new(unsafe { Socket::from_raw_socket(sock) }), + } + } +} + +#[cfg(windows)] +impl AsSocket for UnixStream { + fn as_socket(&self) -> BorrowedSocket<'_> { + // SAFETY: the raw socket is valid for the lifetime of `self`. + unsafe { BorrowedSocket::borrow_raw(self.inner.as_raw_socket()) } + } +} + +#[cfg(windows)] +impl From for OwnedSocket { + fn from(stream: UnixStream) -> Self { + stream.inner.into_inner().into() + } +} + +#[cfg(windows)] +impl From for UnixStream { + /// # Notes + /// + /// The caller is responsible for ensuring that the socket is in + /// non-blocking mode. + fn from(socket: OwnedSocket) -> Self { + UnixStream { + inner: IoSource::new(Socket::from(socket)), + } + } +} diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs index 8761dd5cf..9e6715016 100644 --- a/src/sys/windows/mod.rs +++ b/src/sys/windows/mod.rs @@ -35,6 +35,7 @@ cfg_net! { pub(crate) mod tcp; pub(crate) mod udp; + pub(crate) mod uds; pub use selector::{SelectorInner, SockState}; } diff --git a/src/sys/windows/uds/listener.rs b/src/sys/windows/uds/listener.rs new file mode 100644 index 000000000..fe5d03d5e --- /dev/null +++ b/src/sys/windows/uds/listener.rs @@ -0,0 +1,67 @@ +use std::io; +use std::mem; +use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket}; + +use windows_sys::Win32::Networking::WinSock::{self as ws, INVALID_SOCKET, SOCKADDR_UN}; + +use super::{cvt, last_error, new_socket, Socket, SocketAddr}; +use crate::io_source::IoSource; +use crate::net::UnixStream; + +/// Creates and binds an `AF_UNIX` listening socket in non-blocking mode to +/// the given `SocketAddr`. +pub(crate) fn bind_addr(addr: &SocketAddr) -> io::Result { + let socket = new_socket()?; + let raw = socket.as_raw_socket(); + + unsafe { + cvt(ws::bind( + raw as _, + &addr.addr as *const _ as *const _, + addr.len, + ))?; + cvt(ws::listen(raw as _, 128))?; + } + + Ok(socket) +} + +/// Accept a new connection from `listener`, returning a non-blocking +/// `UnixStream` and the peer's address. +pub(crate) fn accept(listener: &IoSource) -> io::Result<(UnixStream, SocketAddr)> { + listener.do_io(|socket| { + let (client, addr) = accept_raw(socket.as_raw_socket())?; + // SAFETY: `accept_raw` returned a valid, connected, non-blocking + // AF_UNIX socket. + let stream = unsafe { + ::from_raw_socket( + ::into_raw_socket(client), + ) + }; + Ok((stream, addr)) + }) +} + +fn accept_raw(raw: RawSocket) -> io::Result<(OwnedSocket, SocketAddr)> { + let mut storage: SOCKADDR_UN = unsafe { mem::zeroed() }; + let mut len = mem::size_of::() as i32; + + let client = unsafe { ws::accept(raw as _, &mut storage as *mut _ as *mut _, &mut len) }; + if client == INVALID_SOCKET { + return Err(last_error()); + } + + let client_raw = client as RawSocket; + // Accepted sockets inherit the listener's non-blocking mode on Windows, + // but set `FIONBIO` explicitly to uphold mio's non-blocking contract + // regardless of how the listening socket was obtained. + let mut nonblocking: u32 = 1; + if let Err(err) = cvt(unsafe { ws::ioctlsocket(client as _, ws::FIONBIO, &mut nonblocking) }) { + unsafe { ws::closesocket(client as _) }; + return Err(err); + } + + let addr = SocketAddr::from_parts(storage, len)?; + // SAFETY: WinSock returned a valid socket on success. + Ok((unsafe { OwnedSocket::from_raw_socket(client_raw) }, addr)) +} diff --git a/src/sys/windows/uds/mod.rs b/src/sys/windows/uds/mod.rs new file mode 100644 index 000000000..26aa4ed94 --- /dev/null +++ b/src/sys/windows/uds/mod.rs @@ -0,0 +1,354 @@ +//! AF_UNIX support for Windows. +//! +//! Provides a `SocketAddr` type (stand-in for the still-unstable +//! `std::os::windows::net::SocketAddr`) and a `Socket` newtype around +//! `OwnedSocket` that implements `Read`/`Write`/`AsRawSocket` so it can be +//! wrapped in `IoSource`. The per-type entry points (`bind_addr`, +//! `connect_addr`, `accept`) live in the `listener` and `stream` submodules, +//! mirroring the layout of `sys::unix::uds`. + +use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket}; +use std::path::Path; +use std::{fmt, io, mem, ptr}; + +use windows_sys::Win32::Networking::WinSock::{ + self as ws, AF_UNIX, SOCKADDR, SOCKADDR_UN, SOCKET_ERROR, SOCK_STREAM, WSABUF, +}; + +use crate::sys::windows::net; + +pub(crate) mod listener; +pub(crate) mod stream; + +/// An address associated with a Unix domain socket. +/// +/// On Windows this is a stand-in for `std::os::windows::net::SocketAddr`, +/// which is still gated behind the unstable `windows_unix_domain_sockets` +/// feature. +#[derive(Clone, Copy)] +pub struct SocketAddr { + pub(super) addr: SOCKADDR_UN, + pub(super) len: i32, +} + +impl SocketAddr { + /// Constructs a `SocketAddr` with the family `AF_UNIX` and the provided + /// path. + pub fn from_pathname>(path: P) -> io::Result { + let (addr, len) = sockaddr_un(path.as_ref())?; + Ok(SocketAddr { addr, len }) + } + + /// Returns the contents of this address if it is a pathname address. + pub fn as_pathname(&self) -> Option<&Path> { + let path_len = self.len as usize - SUN_PATH_OFFSET; + if path_len == 0 || self.addr.sun_path[0] == 0 { + return None; + } + let bytes = + unsafe { std::slice::from_raw_parts(self.addr.sun_path.as_ptr().cast(), path_len) }; + // Truncate at the first null byte (Windows may return the full + // `sun_path` buffer). + let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); + let s = std::str::from_utf8(&bytes[..end]).ok()?; + Some(Path::new(s)) + } + + /// Returns `true` if the address is unnamed. + pub fn is_unnamed(&self) -> bool { + // Not actually supported at time of writing, but worth being defensive. + let path_len = self.len as usize - SUN_PATH_OFFSET; + path_len == 0 + } + + pub(super) fn from_parts(addr: SOCKADDR_UN, len: i32) -> io::Result { + if addr.sun_family != AF_UNIX { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid address family", + )) + } else if (len as usize) < SUN_PATH_OFFSET || (len as usize) > mem::size_of::() + { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid address length", + )) + } else { + Ok(SocketAddr { addr, len }) + } + } + + fn from_raw(f: impl FnOnce(*mut SOCKADDR, *mut i32) -> i32) -> io::Result { + unsafe { + let mut addr: SOCKADDR_UN = mem::zeroed(); + let mut len = mem::size_of::() as i32; + cvt(f(&mut addr as *mut SOCKADDR_UN as *mut SOCKADDR, &mut len))?; + SocketAddr::from_parts(addr, len) + } + } +} + +impl fmt::Debug for SocketAddr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(path) = self.as_pathname() { + write!(f, "{path:?} (pathname)") + } else if self.is_unnamed() { + write!(f, "(unnamed)") + } else { + write!(f, "(abstract)") + } + } +} + +/// Newtype around `OwnedSocket` that implements `Read`, `Write`, and +/// `AsRawSocket` so it can be wrapped in `IoSource`. +pub(crate) struct Socket { + inner: OwnedSocket, +} + +impl Socket { + pub(crate) fn local_addr(&self) -> io::Result { + let raw = self.inner.as_raw_socket(); + SocketAddr::from_raw(|addr, len| unsafe { ws::getsockname(raw as _, addr, len) }) + } + + pub(crate) fn peer_addr(&self) -> io::Result { + let raw = self.inner.as_raw_socket(); + SocketAddr::from_raw(|addr, len| unsafe { ws::getpeername(raw as _, addr, len) }) + } + + pub(crate) fn shutdown(&self, how: std::net::Shutdown) -> io::Result<()> { + let how = match how { + std::net::Shutdown::Read => ws::SD_RECEIVE, + std::net::Shutdown::Write => ws::SD_SEND, + std::net::Shutdown::Both => ws::SD_BOTH, + }; + cvt(unsafe { ws::shutdown(self.inner.as_raw_socket() as _, how as _) })?; + Ok(()) + } + + pub(crate) fn take_error(&self) -> io::Result> { + let mut optval: i32 = 0; + let mut optlen = mem::size_of::() as i32; + cvt(unsafe { + ws::getsockopt( + self.inner.as_raw_socket() as _, + ws::SOL_SOCKET, + ws::SO_ERROR, + &mut optval as *mut _ as *mut _, + &mut optlen, + ) + })?; + if optval == 0 { + Ok(None) + } else { + Ok(Some(io::Error::from_raw_os_error(optval))) + } + } +} + +impl io::Read for Socket { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (&*self).read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + (&*self).read_vectored(bufs) + } +} + +impl io::Read for &Socket { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let res = unsafe { + ws::recv( + self.inner.as_raw_socket() as _, + buf.as_mut_ptr(), + buf.len() as i32, + 0, + ) + }; + if res == SOCKET_ERROR { + let err = unsafe { ws::WSAGetLastError() }; + // Map shutdown to EOF, matching POSIX behaviour. + if err == ws::WSAESHUTDOWN { + Ok(0) + } else { + Err(io::Error::from_raw_os_error(err)) + } + } else { + Ok(res as usize) + } + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + // `IoSliceMut` is guaranteed ABI-compatible with `WSABUF` on + // Windows, so we can pass the slice straight to `WSARecv`. + let mut bytes_recvd: u32 = 0; + let mut flags: u32 = 0; + let res = unsafe { + ws::WSARecv( + self.inner.as_raw_socket() as _, + bufs.as_mut_ptr() as *const WSABUF, + bufs.len() as u32, + &mut bytes_recvd, + &mut flags, + ptr::null_mut(), + None, + ) + }; + if res == SOCKET_ERROR { + let err = unsafe { ws::WSAGetLastError() }; + // Map shutdown to EOF, matching POSIX behaviour. + if err == ws::WSAESHUTDOWN { + Ok(0) + } else { + Err(io::Error::from_raw_os_error(err)) + } + } else { + Ok(bytes_recvd as usize) + } + } +} + +impl io::Write for Socket { + fn write(&mut self, buf: &[u8]) -> io::Result { + (&*self).write(buf) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + (&*self).write_vectored(bufs) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl io::Write for &Socket { + fn write(&mut self, buf: &[u8]) -> io::Result { + let res = unsafe { + ws::send( + self.inner.as_raw_socket() as _, + buf.as_ptr(), + buf.len() as i32, + 0, + ) + }; + if res == SOCKET_ERROR { + Err(last_error()) + } else { + Ok(res as usize) + } + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + // `IoSlice` is guaranteed ABI-compatible with `WSABUF` on Windows. + let mut bytes_sent: u32 = 0; + let res = unsafe { + ws::WSASend( + self.inner.as_raw_socket() as _, + bufs.as_ptr() as *const WSABUF, + bufs.len() as u32, + &mut bytes_sent, + 0, + ptr::null_mut(), + None, + ) + }; + if res == SOCKET_ERROR { + Err(last_error()) + } else { + Ok(bytes_sent as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl AsRawSocket for Socket { + fn as_raw_socket(&self) -> RawSocket { + self.inner.as_raw_socket() + } +} + +impl IntoRawSocket for Socket { + fn into_raw_socket(self) -> RawSocket { + self.inner.into_raw_socket() + } +} + +impl FromRawSocket for Socket { + unsafe fn from_raw_socket(sock: RawSocket) -> Self { + Socket { + inner: unsafe { OwnedSocket::from_raw_socket(sock) }, + } + } +} + +impl From for OwnedSocket { + fn from(socket: Socket) -> OwnedSocket { + socket.inner + } +} + +impl From for Socket { + fn from(inner: OwnedSocket) -> Socket { + Socket { inner } + } +} + +impl fmt::Debug for Socket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Socket") + .field("socket", &self.inner.as_raw_socket()) + .finish() + } +} + +const SUN_PATH_OFFSET: usize = mem::offset_of!(SOCKADDR_UN, sun_path); + +/// Create a new non-blocking `AF_UNIX, SOCK_STREAM` socket. +pub(super) fn new_socket() -> io::Result { + let raw = net::new_socket(AF_UNIX as u32, SOCK_STREAM)?; + // SAFETY: `net::new_socket` returned a valid SOCKET on success. + Ok(unsafe { OwnedSocket::from_raw_socket(raw as RawSocket) }) +} + +fn sockaddr_un(path: &Path) -> io::Result<(SOCKADDR_UN, i32)> { + let mut addr: SOCKADDR_UN = unsafe { mem::zeroed() }; + addr.sun_family = AF_UNIX; + + let bytes = path + .to_str() + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "path must be valid UTF-8"))? + .as_bytes(); + + if bytes.len() >= addr.sun_path.len() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "path too long")); + } + + unsafe { + ptr::copy_nonoverlapping( + bytes.as_ptr(), + addr.sun_path.as_mut_ptr().cast(), + bytes.len(), + ); + } + + let len = (SUN_PATH_OFFSET + bytes.len() + 1) as i32; + Ok((addr, len)) +} + +pub(super) fn last_error() -> io::Error { + io::Error::from_raw_os_error(unsafe { ws::WSAGetLastError() }) +} + +pub(super) fn cvt(result: i32) -> io::Result { + if result == SOCKET_ERROR { + Err(last_error()) + } else { + Ok(result) + } +} diff --git a/src/sys/windows/uds/stream.rs b/src/sys/windows/uds/stream.rs new file mode 100644 index 000000000..b354fe796 --- /dev/null +++ b/src/sys/windows/uds/stream.rs @@ -0,0 +1,26 @@ +use std::io; +use std::os::windows::io::{AsRawSocket, OwnedSocket}; + +use windows_sys::Win32::Networking::WinSock::{self as ws, SOCKET_ERROR}; + +use super::{last_error, new_socket, SocketAddr}; + +/// Creates an `AF_UNIX` stream socket and connects it to the given +/// `SocketAddr`. The socket is left in non-blocking mode. +pub(crate) fn connect_addr(addr: &SocketAddr) -> io::Result { + let socket = new_socket()?; + let raw = socket.as_raw_socket(); + + // The socket is non-blocking; `connect` may return `WSAEWOULDBLOCK` + // which we treat as success (the user will be notified when the + // connection completes). + let res = unsafe { ws::connect(raw as _, &addr.addr as *const _ as *const _, addr.len) }; + if res == SOCKET_ERROR { + let err = last_error(); + if err.kind() != io::ErrorKind::WouldBlock { + return Err(err); + } + } + + Ok(socket) +} diff --git a/tests/unix_listener.rs b/tests/unix_listener.rs index 7666f9ff2..228e22892 100644 --- a/tests/unix_listener.rs +++ b/tests/unix_listener.rs @@ -1,13 +1,15 @@ -#![cfg(all(unix, feature = "os-poll", feature = "net"))] +#![cfg(all(any(unix, windows), feature = "os-poll", feature = "net"))] -use mio::net::UnixListener; -use mio::{Interest, Token}; use std::io::{self, Read}; +#[cfg(unix)] use std::os::unix::net; use std::path::{Path, PathBuf}; use std::sync::{Arc, Barrier}; use std::thread; +use mio::net::{UnixListener, UnixStream}; +use mio::{Interest, Token}; + #[macro_use] mod util; use util::{ @@ -31,6 +33,7 @@ fn unix_listener_smoke() { } #[test] +#[cfg(unix)] fn unix_listener_from_std() { smoke_test( |path| { @@ -235,7 +238,7 @@ fn open_connections( ) -> thread::JoinHandle<()> { thread::spawn(move || { for _ in 0..n_connections { - let conn = net::UnixStream::connect(path.clone()).unwrap(); + let conn = UnixStream::connect(path.clone()).unwrap(); barrier.wait(); drop(conn); } diff --git a/tests/unix_stream.rs b/tests/unix_stream.rs index 8a12a90aa..25f9a59ba 100644 --- a/tests/unix_stream.rs +++ b/tests/unix_stream.rs @@ -1,14 +1,17 @@ -#![cfg(all(unix, feature = "os-poll", feature = "net"))] +#![cfg(all(any(unix, windows), feature = "os-poll", feature = "net"))] -use mio::net::UnixStream; -use mio::{Interest, Token}; use std::io::{self, IoSlice, IoSliceMut, Read, Write}; use std::net::Shutdown; +#[cfg(unix)] use std::os::unix::net; use std::path::Path; use std::sync::mpsc::channel; use std::sync::{Arc, Barrier}; use std::thread; +use std::time::Duration; + +use mio::net::{SocketAddr, UnixListener, UnixStream}; +use mio::{Interest, Token}; #[macro_use] mod util; @@ -19,11 +22,12 @@ use util::{ }; const DATA1: &[u8] = b"Hello same host!"; -const DATA2: &[u8] = b"Why hello mio!"; const DATA1_LEN: usize = 16; +const DATA2: &[u8] = b"Why hello mio!"; const DATA2_LEN: usize = 14; const DEFAULT_BUF_SIZE: usize = 64; const TOKEN_1: Token = Token(0); +#[cfg(unix)] const TOKEN_2: Token = Token(1); #[test] @@ -48,12 +52,12 @@ fn unix_stream_connect() { let barrier = Arc::new(Barrier::new(2)); let path = temp_file("unix_stream_connect"); - let listener = net::UnixListener::bind(path.clone()).unwrap(); + let listener = UnixListener::bind(path.clone()).unwrap(); let mut stream = UnixStream::connect(path).unwrap(); let barrier_clone = barrier.clone(); let handle = thread::spawn(move || { - let (stream, _) = listener.accept().unwrap(); + let (stream, _) = accept_blocking(&listener); barrier_clone.wait(); drop(stream); }); @@ -91,15 +95,14 @@ fn unix_stream_connect_addr() { let barrier = Arc::new(Barrier::new(2)); let path = temp_file("unix_stream_connect_addr"); - let listener = net::UnixListener::bind(path.clone()).unwrap(); - let mio_listener = mio::net::UnixListener::from_std(listener); + let mio_listener = UnixListener::bind(path).unwrap(); let local_addr = mio_listener.local_addr().unwrap(); let mut stream = UnixStream::connect_addr(&local_addr).unwrap(); let barrier_clone = barrier.clone(); let handle = thread::spawn(move || { - let (stream, _) = mio_listener.accept().unwrap(); + let (stream, _) = accept_blocking(&mio_listener); barrier_clone.wait(); drop(stream); }); @@ -128,6 +131,7 @@ fn unix_stream_connect_addr() { } #[test] +#[cfg(unix)] #[cfg_attr( target_os = "hurd", ignore = "getting pathname isn't supported on GNU/Hurd" @@ -146,6 +150,7 @@ fn unix_stream_from_std() { } #[test] +#[cfg(unix)] fn unix_stream_pair() { let (mut poll, mut events) = init_with_poll(); @@ -332,7 +337,11 @@ fn unix_stream_shutdown_write() { ); let err = stream.write(DATA2).unwrap_err(); + #[cfg(unix)] assert_eq!(err.kind(), io::ErrorKind::BrokenPipe); + // Windows returns WSAESHUTDOWN (10058) which Rust maps to Uncategorized. + #[cfg(windows)] + assert_eq!(err.raw_os_error(), Some(10058)); // Read should be ok let mut buf = [0; DEFAULT_BUF_SIZE]; @@ -405,7 +414,7 @@ fn unix_stream_shutdown_both() { #[cfg(unix)] assert_eq!(err.kind(), io::ErrorKind::BrokenPipe); #[cfg(windows)] - assert_eq!(err.kind(), io::ErrorKind::ConnectionAbroted); + assert_eq!(err.kind(), io::ErrorKind::ConnectionAborted); // Close the connection to allow the remote to shutdown drop(stream); @@ -609,16 +618,16 @@ where fn new_echo_listener( connections: usize, test_name: &'static str, -) -> (thread::JoinHandle<()>, net::SocketAddr) { +) -> (thread::JoinHandle<()>, SocketAddr) { let (addr_sender, addr_receiver) = channel(); let handle = thread::spawn(move || { let path = temp_file(test_name); - let listener = net::UnixListener::bind(path).unwrap(); + let listener = UnixListener::bind(path).unwrap(); let local_addr = listener.local_addr().unwrap(); addr_sender.send(local_addr).unwrap(); for _ in 0..connections { - let (mut stream, _) = listener.accept().unwrap(); + let (mut stream, _) = accept_blocking(&listener); // On Linux based system it will cause a connection reset // error when the reading side of the peer connection is @@ -631,7 +640,10 @@ fn new_echo_listener( read += amount; amount } - Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => continue, + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(1)); + continue; + } Err(ref err) if matches!( err.kind(), @@ -647,7 +659,10 @@ fn new_echo_listener( } match stream.write(&buf[..n]) { Ok(amount) => written += amount, - Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => continue, + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(1)); + continue; + } Err(ref err) if err.kind() == io::ErrorKind::BrokenPipe => break, Err(err) => panic!("{}", err), }; @@ -662,16 +677,16 @@ fn new_noop_listener( connections: usize, barrier: Arc, test_name: &'static str, -) -> (thread::JoinHandle<()>, net::SocketAddr) { +) -> (thread::JoinHandle<()>, SocketAddr) { let (sender, receiver) = channel(); let handle = thread::spawn(move || { let path = temp_file(test_name); - let listener = net::UnixListener::bind(path).unwrap(); + let listener = UnixListener::bind(path).unwrap(); let local_addr = listener.local_addr().unwrap(); sender.send(local_addr).unwrap(); for _ in 0..connections { - let (stream, _) = listener.accept().unwrap(); + let (stream, _) = accept_blocking(&listener); barrier.wait(); stream.shutdown(Shutdown::Write).unwrap(); barrier.wait(); @@ -680,3 +695,20 @@ fn new_noop_listener( }); (handle, receiver.recv().unwrap()) } + +/// Spin on `accept` until a connection is available. The listener is +/// non-blocking, so we sleep briefly between attempts. +/// We previously used std:: types to allow us to do blocking accepts, +/// but, since this isn't stabilized for Windows at time of writing, +/// we changed to mio types. +fn accept_blocking(listener: &UnixListener) -> (UnixStream, SocketAddr) { + loop { + match listener.accept() { + Ok(pair) => return pair, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(1)); + } + Err(e) => panic!("accept failed: {e}"), + } + } +}