From 3c58ecd00cc72c22df141e4ed199638b31a602ad Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 16 Sep 2024 20:49:58 -0700 Subject: [PATCH] Fix bug with send() on Windows in O_NONBLOCK mode There is a bug in WIN32 where using CancelIoEx() on an overlapped i/o op initiated by WSASend() will cause WSAGetOverlappedResult() to report the operation failed when it actually succeeded. We now work around that, by having send and sendto initially consult WSAPoll() on O_NONBLOCK sockets --- libc/sock/internal.h | 2 +- libc/sock/send-nt.c | 2 +- libc/sock/sendto-nt.c | 2 +- libc/sock/winsockblock.c | 21 +++++++++++++++++++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libc/sock/internal.h b/libc/sock/internal.h index 9dbd690dc34..b8704360b68 100644 --- a/libc/sock/internal.h +++ b/libc/sock/internal.h @@ -65,7 +65,7 @@ int sys_select_nt(int, fd_set *, fd_set *, fd_set *, struct timeval *, size_t __iovec2nt(struct NtIovec[hasatleast 16], const struct iovec *, size_t); -ssize_t __winsock_block(int64_t, uint32_t, bool, uint32_t, uint64_t, +ssize_t __winsock_block(int64_t, uint32_t, int, uint32_t, uint64_t, int (*)(int64_t, struct NtOverlapped *, uint32_t *, void *), void *); diff --git a/libc/sock/send-nt.c b/libc/sock/send-nt.c index 63802586f45..24a0cb76010 100644 --- a/libc/sock/send-nt.c +++ b/libc/sock/send-nt.c @@ -56,7 +56,7 @@ textwindows ssize_t sys_send_nt(int fd, const struct iovec *iov, size_t iovlen, sigset_t m = __sig_block(); bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT); flags &= ~_MSG_DONTWAIT; - rc = __winsock_block(f->handle, flags, nonblock, f->sndtimeo, m, + rc = __winsock_block(f->handle, flags, -nonblock, f->sndtimeo, m, sys_send_nt_start, &(struct SendArgs){iov, iovlen}); __sig_unblock(m); return rc; diff --git a/libc/sock/sendto-nt.c b/libc/sock/sendto-nt.c index c7cdcf225d1..41e3520ba56 100644 --- a/libc/sock/sendto-nt.c +++ b/libc/sock/sendto-nt.c @@ -60,7 +60,7 @@ textwindows ssize_t sys_sendto_nt(int fd, const struct iovec *iov, bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT); flags &= ~_MSG_DONTWAIT; rc = __winsock_block( - f->handle, flags, nonblock, f->sndtimeo, m, sys_sendto_nt_start, + f->handle, flags, -nonblock, f->sndtimeo, m, sys_sendto_nt_start, &(struct SendToArgs){iov, iovlen, opt_in_addr, in_addrsize}); __sig_unblock(m); return rc; diff --git a/libc/sock/winsockblock.c b/libc/sock/winsockblock.c index 2e2e45446ec..16b62efa0c7 100644 --- a/libc/sock/winsockblock.c +++ b/libc/sock/winsockblock.c @@ -30,17 +30,19 @@ #include "libc/nt/events.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/overlapped.h" +#include "libc/nt/struct/pollfd.h" #include "libc/nt/synchronization.h" #include "libc/nt/thread.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" +#include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" #ifdef __x86_64__ textwindows ssize_t -__winsock_block(int64_t handle, uint32_t flags, bool nonblock, +__winsock_block(int64_t handle, uint32_t flags, int nonblock, uint32_t srwtimeout, sigset_t waitmask, int StartSocketOp(int64_t handle, struct NtOverlapped *overlap, uint32_t *flags, void *arg), @@ -61,6 +63,21 @@ __winsock_block(int64_t handle, uint32_t flags, bool nonblock, bool got_eagain = false; uint32_t other_error = 0; + // send() and sendto() provide O_NONBLOCK as a negative number + // because winsock has a bug that causes CancelIoEx() to cause + // WSAGetOverlappedResult() to report errors when it succeeded + if (nonblock < 0) { + struct sys_pollfd_nt fds[1] = {{handle, POLLOUT}}; + switch (WSAPoll(fds, 1, 0)) { + case -1: + return __winsockerr(); + case 0: + return eagain(); + default: + break; + } + } + // create event handle for overlapped i/o intptr_t event; if (!(event = WSACreateEvent())) @@ -69,7 +86,7 @@ __winsock_block(int64_t handle, uint32_t flags, bool nonblock, struct NtOverlapped overlap = {.hEvent = event}; bool32 ok = !StartSocketOp(handle, &overlap, &flags, arg); if (!ok && WSAGetLastError() == kNtErrorIoPending) { - if (nonblock) { + if (nonblock > 0) { CancelIoEx(handle, &overlap); got_eagain = true; } else {