From acd6c321843f81b1d380b656b7457c77541d857d Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Thu, 12 Sep 2024 01:18:14 -0700 Subject: [PATCH] Rewrite Windows accept() This change should fix the Windows issues Qt Creator has been having, by ensuring accept() and accept4() work in O_NONBLOCK mode. I switched away from AcceptEx() which is buggy, back to using WSAAccept(). This requires making a tradeoff where we have to accept a busy loop. However it is low latency in nature, just like our new and improved Windows poll() code. I was furthermore able to eliminate a bunch of Windows-related test todos. --- libc/calls/checkcancel.c | 2 +- libc/calls/internal.h | 1 + libc/calls/park.c | 25 +--- libc/calls/poll-nt.c | 114 ++++++++------- libc/calls/poll.c | 13 +- libc/calls/ppoll.c | 11 +- libc/calls/read-nt.c | 14 +- libc/calls/sigcheck.c | 40 ++++++ libc/sock/accept-nt.c | 160 +++++++++------------- libc/sock/listen-nt.c | 22 ++- libc/sock/syscall_fd.internal.h | 3 +- test/libc/sock/nonblock_test.c | 3 - test/libc/sock/recvfrom_test.c | 2 - test/libc/sock/sendfile_test.c | 10 +- test/posix/BUILD.mk | 2 + test/posix/accept4_nonblock_test.c | 71 ++++++++++ test/posix/accept_inherit_nonblock_test.c | 85 ++++++++++++ test/posix/accept_poll_test.c | 94 +++++++++++++ test/posix/nonblock_pipe2_test.c | 75 ++++++++++ test/posix/nonblock_pipe_test.c | 84 ++++++++++++ 20 files changed, 622 insertions(+), 209 deletions(-) create mode 100644 libc/calls/sigcheck.c create mode 100644 test/posix/accept4_nonblock_test.c create mode 100644 test/posix/accept_inherit_nonblock_test.c create mode 100644 test/posix/accept_poll_test.c create mode 100644 test/posix/nonblock_pipe2_test.c create mode 100644 test/posix/nonblock_pipe_test.c diff --git a/libc/calls/checkcancel.c b/libc/calls/checkcancel.c index 8b95bf3cd..b13f0446e 100644 --- a/libc/calls/checkcancel.c +++ b/libc/calls/checkcancel.c @@ -21,7 +21,7 @@ #include "libc/intrin/weaken.h" #include "libc/thread/posixthread.internal.h" -int _check_cancel(void) { +textwindows int _check_cancel(void) { if (_weaken(_pthread_cancel_ack) && // _pthread_self() && !(_pthread_self()->pt_flags & PT_NOCANCEL) && atomic_load_explicit(&_pthread_self()->pt_canceled, diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 28305a9e0..7c2774380 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -25,6 +25,7 @@ int __ensurefds(int); uint32_t sys_getuid_nt(void); int __ensurefds_unlocked(int); void __printfds(struct Fd *, size_t); +int __sigcheck(sigset_t, bool); int CountConsoleInputBytes(void); int FlushConsoleInputBytes(void); int64_t GetConsoleInputHandle(void); diff --git a/libc/calls/park.c b/libc/calls/park.c index eb8f73054..71f203128 100644 --- a/libc/calls/park.c +++ b/libc/calls/park.c @@ -17,25 +17,19 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" -#include "libc/calls/sig.internal.h" +#include "libc/calls/struct/sigset.h" #include "libc/intrin/atomic.h" -#include "libc/intrin/weaken.h" #include "libc/nt/synchronization.h" -#include "libc/sysv/consts/sicode.h" -#include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" #ifdef __x86_64__ // returns 0 on timeout or spurious wakeup // raises EINTR if a signal delivery interrupted wait operation // raises ECANCELED if this POSIX thread was canceled in masked mode -static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask, +textwindows static int _park_thread(uint32_t msdelay, sigset_t waitmask, bool restartable) { - int sig, handler_was_called; - if (_check_cancel() == -1) + if (__sigcheck(waitmask, restartable) == -1) return -1; - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) - goto HandleSignal; int expect = 0; atomic_int futex = 0; struct PosixThread *pt = _pthread_self(); @@ -43,17 +37,8 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask, atomic_store_explicit(&pt->pt_blocker, &futex, memory_order_release); bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay); atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); - if (ok && _weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { - HandleSignal: - handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); - if (_check_cancel() == -1) - return -1; - if (handler_was_called & SIG_HANDLED_NO_RESTART) - return eintr(); - if (handler_was_called & SIG_HANDLED_SA_RESTART) - if (!restartable) - return eintr(); - } + if (ok && __sigcheck(waitmask, restartable) == -1) + return -1; return 0; } diff --git a/libc/calls/poll-nt.c b/libc/calls/poll-nt.c index 867cfd108..4d0000b46 100644 --- a/libc/calls/poll-nt.c +++ b/libc/calls/poll-nt.c @@ -16,23 +16,15 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/calls/calls.h" #include "libc/calls/internal.h" -#include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" -#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/syscall_support-nt.internal.h" -#include "libc/dce.h" -#include "libc/errno.h" #include "libc/intrin/atomic.h" #include "libc/intrin/fds.h" -#include "libc/intrin/strace.h" -#include "libc/intrin/weaken.h" #include "libc/macros.h" -#include "libc/mem/mem.h" #include "libc/nt/console.h" #include "libc/nt/enum/filetype.h" #include "libc/nt/enum/wait.h" @@ -42,22 +34,13 @@ #include "libc/nt/runtime.h" #include "libc/nt/struct/pollfd.h" #include "libc/nt/synchronization.h" -#include "libc/nt/thread.h" -#include "libc/nt/thunk/msabi.h" #include "libc/nt/time.h" #include "libc/nt/winsock.h" -#include "libc/runtime/runtime.h" #include "libc/sock/internal.h" #include "libc/sock/struct/pollfd.h" -#include "libc/sock/struct/pollfd.internal.h" -#include "libc/stdio/sysparam.h" #include "libc/sysv/consts/o.h" -#include "libc/sysv/consts/poll.h" -#include "libc/sysv/consts/sicode.h" -#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" -#include "libc/thread/tls.h" #ifdef __x86_64__ #define POLL_INTERVAL_MS 10 @@ -75,24 +58,22 @@ #define POLLPRI_ 0x0400 // MSDN unsupported // -textwindows static dontinline struct timespec sys_poll_nt_now(void) { +textwindows dontinline static struct timespec sys_poll_nt_now(void) { uint64_t hectons; QueryUnbiasedInterruptTimePrecise(&hectons); return timespec_fromnanos(hectons * 100); } -textwindows static int sys_poll_nt_sigcheck(sigset_t sigmask) { - int sig, handler_was_called; - if (_check_cancel() == -1) - return -1; - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(sigmask))) { - handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, sigmask); - if (_check_cancel() == -1) - return -1; - if (handler_was_called) - return eintr(); +textwindows static uint32_t sys_poll_nt_waitms(struct timespec deadline) { + struct timespec now = sys_poll_nt_now(); + if (timespec_cmp(now, deadline) < 0) { + struct timespec remain = timespec_sub(deadline, now); + int64_t millis = timespec_tomillis(remain); + uint32_t waitfor = MIN(millis, 0xffffffffu); + return MIN(waitfor, POLL_INTERVAL_MS); + } else { + return 0; // we timed out } - return 0; } // Polls on the New Technology. @@ -100,22 +81,17 @@ textwindows static int sys_poll_nt_sigcheck(sigset_t sigmask) { // This function is used to implement poll() and select(). You may poll // on sockets, files and the console at the same time. We also poll for // both signals and posix thread cancelation, while the poll is polling -textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, - uint32_t *ms, sigset_t sigmask) { - bool ok; - uint64_t millis; +textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds, + struct timespec deadline, + sigset_t waitmask) { int fileindices[64]; int sockindices[64]; int64_t filehands[64]; struct PosixThread *pt; int i, rc, ev, kind, gotsocks; struct sys_pollfd_nt sockfds[64]; - struct timespec deadline, remain, now; uint32_t cm, fi, wi, sn, pn, avail, waitfor, already_slept; - waitfor = ms ? *ms : -1u; - deadline = timespec_add(sys_poll_nt_now(), timespec_frommillis(waitfor)); - // ensure revents is cleared for (i = 0; i < nfds; ++i) fds[i].revents = 0; @@ -171,7 +147,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, rc += !!fds[i].revents; } __fds_unlock(); - if (rc) + if (rc == -1) return rc; // perform poll operation @@ -191,10 +167,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, if ((ev & POLLWRNORM_) && !(ev & POLLRDNORM_)) { fds[fi].revents = fds[fi].events & (POLLRDNORM_ | POLLWRNORM_); } else if (GetFileType(filehands[i]) == kNtFileTypePipe) { - ok = PeekNamedPipe(filehands[i], 0, 0, 0, &avail, 0); - POLLTRACE("PeekNamedPipe(%ld, 0, 0, 0, [%'u], 0) → {%hhhd, %d}", - filehands[i], avail, ok, GetLastError()); - if (ok) { + if (PeekNamedPipe(filehands[i], 0, 0, 0, &avail, 0)) { if (avail) fds[fi].revents = POLLRDNORM_; } else if (GetLastError() == kNtErrorHandleEof || @@ -222,15 +195,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, } // determine how long to wait - now = sys_poll_nt_now(); - if (timespec_cmp(now, deadline) < 0) { - remain = timespec_sub(deadline, now); - millis = timespec_tomillis(remain); - waitfor = MIN(millis, 0xffffffffu); - waitfor = MIN(waitfor, POLL_INTERVAL_MS); - } else { - waitfor = 0; // we timed out - } + waitfor = sys_poll_nt_waitms(deadline); // check for events and/or readiness on sockets // we always do this due to issues with POLLOUT @@ -238,7 +203,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, // if we need to wait, then we prefer to wait inside WSAPoll() // this ensures network events are received in ~10µs not ~10ms if (!rc && waitfor) { - if (sys_poll_nt_sigcheck(sigmask)) + if (__sigcheck(waitmask, false)) return -1; already_slept = waitfor; } else { @@ -253,7 +218,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, ++rc; } } else if (already_slept) { - if (sys_poll_nt_sigcheck(sigmask)) + if (__sigcheck(waitmask, false)) return -1; } } else { @@ -269,7 +234,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, // this ensures low latency for apps like emacs which with no sock // here we shall actually report that something can be written too if (!already_slept) { - if (sys_poll_nt_sigcheck(sigmask)) + if (__sigcheck(waitmask, false)) return -1; pt = _pthread_self(); filehands[pn] = pt->pt_semaphore = CreateSemaphore(0, 0, 1, 0); @@ -283,7 +248,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, return __winerr(); } else if (wi == pn) { // our semaphore was signalled - if (sys_poll_nt_sigcheck(sigmask)) + if (__sigcheck(waitmask, false)) return -1; } else if ((wi ^ kNtWaitAbandoned) < pn) { // this is possibly because a process or thread was killed @@ -328,7 +293,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, } else { // should only be possible on kNtWaitTimeout or semaphore abandoned // keep looping for events and we'll catch timeout when appropriate - if (sys_poll_nt_sigcheck(sigmask)) + if (__sigcheck(waitmask, false)) return -1; } } @@ -341,11 +306,44 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, return rc; } +textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, + struct timespec deadline, + const sigset_t waitmask) { + uint32_t waitms; + int i, n, rc, got = 0; + + // fast path + if (nfds <= 63) + return sys_poll_nt_actual(fds, nfds, deadline, waitmask); + + // clumsy path + for (;;) { + for (i = 0; i < nfds; i += 64) { + n = nfds - i; + n = n > 64 ? 64 : n; + rc = sys_poll_nt_actual(fds + i, n, timespec_zero, waitmask); + if (rc == -1) + return -1; + got += rc; + } + if (got) + return got; + if (!(waitms = sys_poll_nt_waitms(deadline))) + return 0; + if (_park_norestart(waitms, waitmask) == -1) + return -1; + } +} + textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms, const sigset_t *sigmask) { int rc; + struct timespec now, timeout, deadline; BLOCK_SIGNALS; - rc = sys_poll_nt_impl(fds, nfds, ms, sigmask ? *sigmask : 0); + now = ms ? sys_poll_nt_now() : timespec_zero; + timeout = ms ? timespec_frommillis(*ms) : timespec_max; + deadline = timespec_add(now, timeout); + rc = sys_poll_nt_impl(fds, nfds, deadline, sigmask ? *sigmask : _SigMask); ALLOW_SIGNALS; return rc; } diff --git a/libc/calls/poll.c b/libc/calls/poll.c index dd4706904..6da322b2e 100644 --- a/libc/calls/poll.c +++ b/libc/calls/poll.c @@ -26,13 +26,6 @@ * should just create a separate thread for each client. poll() isn't a * scalable i/o solution on any platform. * - * On Windows it's only possible to poll 64 file descriptors at a time. - * This is a limitation imposed by WSAPoll(). Cosmopolitan Libc's poll() - * polyfill can go higher in some cases. For example, you can actually - * poll 64 sockets and 63 non-sockets at the same time. Furthermore, - * elements whose fd field is set to a negative number are ignored and - * will not count against this limit. - * * One of the use cases for poll() is to quickly check if a number of * file descriptors are valid. The canonical way to do this is to set * events to 0 which prevents blocking and causes only the invalid, @@ -46,6 +39,12 @@ * When XNU and BSD OSes report POLLHUP, they will always set POLLIN too * when POLLIN is requested, even in cases when there isn't unread data. * + * Your poll() function will check the status of all file descriptors + * before returning. This function won't block unless none of the fds + * had had any reportable status. + * + * The impact shutdown() will have on poll() is a dice roll across OSes. + * * @param fds[𝑖].fd should be a socket, input pipe, or conosle input * and if it's a negative number then the entry is ignored, plus * revents will be set to zero diff --git a/libc/calls/ppoll.c b/libc/calls/ppoll.c index a6a1ca7d1..456dce16e 100644 --- a/libc/calls/ppoll.c +++ b/libc/calls/ppoll.c @@ -156,11 +156,14 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout, } } else { uint32_t ms; - if (!timeout || - ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) { - ms = -1u; + uint32_t *msp; + if (timeout && + !ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) { + msp = &ms; + } else { + msp = 0; } - fdcount = sys_poll_nt(fds, nfds, &ms, sigmask); + fdcount = sys_poll_nt(fds, nfds, msp, sigmask); } if (IsOpenbsd() && fdcount != -1) { diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index 5633c67fd..31eea8e87 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -384,12 +384,14 @@ textwindows static int ProcessMouseEvent(const struct NtInputRecord *r, kNtLeftAltPressed | kNtRightAltPressed))) { // we disable mouse highlighting when the tty is put in raw mode // to mouse wheel events with widely understood vt100 arrow keys - *p++ = 033; - *p++ = !__keystroke.ohno_decckm ? '[' : 'O'; - if (isup) { - *p++ = 'A'; - } else { - *p++ = 'B'; + for (int i = 0; i < 3; ++i) { + *p++ = 033; + *p++ = !__keystroke.ohno_decckm ? '[' : 'O'; + if (isup) { + *p++ = 'A'; + } else { + *p++ = 'B'; + } } } } else if ((bs || currentbs) && (__ttyconf.magic & kTtyXtMouse)) { diff --git a/libc/calls/sigcheck.c b/libc/calls/sigcheck.c new file mode 100644 index 000000000..74cbcafd1 --- /dev/null +++ b/libc/calls/sigcheck.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" +#include "libc/calls/sig.internal.h" +#include "libc/intrin/weaken.h" +#include "libc/sysv/consts/sicode.h" +#include "libc/sysv/errfuns.h" + +textwindows int __sigcheck(sigset_t waitmask, bool restartable) { + int sig, handler_was_called; + if (_check_cancel() == -1) + return -1; + if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { + handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); + if (_check_cancel() == -1) + return -1; + if (handler_was_called & SIG_HANDLED_NO_RESTART) + return eintr(); + if (handler_was_called & SIG_HANDLED_SA_RESTART) + if (!restartable) + return eintr(); + } + return 0; +} diff --git a/libc/sock/accept-nt.c b/libc/sock/accept-nt.c index 839553624..b05963fc6 100644 --- a/libc/sock/accept-nt.c +++ b/libc/sock/accept-nt.c @@ -16,115 +16,87 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/atomic.h" #include "libc/calls/internal.h" -#include "libc/intrin/fds.h" #include "libc/calls/struct/sigset.internal.h" -#include "libc/cosmo.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/errno.h" -#include "libc/nt/enum/wsaid.h" +#include "libc/intrin/kprintf.h" +#include "libc/nt/errors.h" +#include "libc/nt/struct/pollfd.h" #include "libc/nt/thunk/msabi.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" #include "libc/sock/struct/sockaddr.h" -#include "libc/sock/wsaid.internal.h" -#include "libc/str/str.h" +#include "libc/sock/syscall_fd.internal.h" +#include "libc/sysv/consts/fio.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sol.h" -#include "libc/thread/thread.h" +#include "libc/sysv/errfuns.h" #ifdef __x86_64__ +#define POLL_INTERVAL_MS 10 + __msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt; __msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket; +__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket; -union AcceptExAddr { - struct sockaddr_storage addr; - char buf[sizeof(struct sockaddr_storage) + 16]; -}; - -struct AcceptExBuffer { - union AcceptExAddr local; - union AcceptExAddr remote; -}; - -struct AcceptResources { +textwindows static int sys_accept_nt_impl(struct Fd *f, + struct sockaddr_storage *addr, + int accept4_flags, + sigset_t waitmask) { int64_t handle; -}; - -struct AcceptArgs { - int64_t listensock; - struct AcceptExBuffer *buffer; -}; - -static struct { - atomic_uint once; - bool32 (*__msabi lpAcceptEx)( - int64_t sListenSocket, int64_t sAcceptSocket, - void *out_lpOutputBuffer /*[recvlen+local+remoteaddrlen]*/, - uint32_t dwReceiveDataLength, uint32_t dwLocalAddressLength, - uint32_t dwRemoteAddressLength, uint32_t *out_lpdwBytesReceived, - struct NtOverlapped *inout_lpOverlapped); -} g_acceptex; - -static void acceptex_init(void) { - static struct NtGuid AcceptExGuid = WSAID_ACCEPTEX; - g_acceptex.lpAcceptEx = __get_wsaid(&AcceptExGuid); -} - -static void sys_accept_nt_unwind(void *arg) { - struct AcceptResources *resources = arg; - if (resources->handle != -1) { - __imp_closesocket(resources->handle); - } -} - -static int sys_accept_nt_start(int64_t handle, struct NtOverlapped *overlap, - uint32_t *flags, void *arg) { - struct AcceptArgs *args = arg; - cosmo_once(&g_acceptex.once, acceptex_init); - if (g_acceptex.lpAcceptEx(args->listensock, handle, args->buffer, 0, - sizeof(args->buffer->local), - sizeof(args->buffer->remote), 0, overlap)) { - return 0; - } else { - return -1; - } -} - -textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr, - int accept4_flags) { int client = -1; - sigset_t m = __sig_block(); - struct AcceptResources resources = {-1}; - pthread_cleanup_push(sys_accept_nt_unwind, &resources); - // creates resources for child socket - // inherit the listener configuration - if ((resources.handle = WSASocket(f->family, f->type, f->protocol, 0, 0, - kNtWsaFlagOverlapped)) == -1) { - client = __winsockerr(); - goto Finish; - } + // accepting sockets must always be non-blocking at the os level. this + // is because WSAAccept doesn't support overlapped i/o operations. the + // AcceptEx function claims to support overlapped i/o however it can't + // be canceled by CancelIoEx, which makes it quite useless to us sadly + // this can't be called in listen(), because then fork() will break it + uint32_t mode = 1; + if (__imp_ioctlsocket(f->handle, FIONBIO, &mode)) + return __winsockerr(); - // accept network connection - // this operation can re-enter, interrupt, cancel, block, timeout, etc. - struct AcceptExBuffer buffer; - ssize_t bytes_received = __winsock_block( - resources.handle, 0, !!(f->flags & O_NONBLOCK), f->rcvtimeo, m, - sys_accept_nt_start, &(struct AcceptArgs){f->handle, &buffer}); - if (bytes_received == -1) { - __imp_closesocket(resources.handle); - goto Finish; + for (;;) { + + // perform non-blocking accept + // we assume listen() put f->handle in non-blocking mode + int32_t addrsize = sizeof(*addr); + struct sockaddr *paddr = (struct sockaddr *)addr; + if ((handle = WSAAccept(f->handle, paddr, &addrsize, 0, 0)) != -1) + break; + + // return on genuine errors + uint32_t err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { + errno = __dos2errno(err); + if (errno == ECONNRESET) + errno = ECONNABORTED; + return -1; + } + + // we're done if user wants non-blocking + if (f->flags & O_NONBLOCK) + return eagain(); + + // check for signals and thread cancelation + // accept() will restart if SA_RESTART is used + if (__sigcheck(waitmask, true) == -1) + return -1; + + // time to block + struct sys_pollfd_nt fds[1] = {{f->handle, POLLIN}}; + if (WSAPoll(fds, 1, POLL_INTERVAL_MS) == -1) + return __winsockerr(); } // inherit properties of listening socket // errors ignored as if f->handle was created before forking // this fails with WSAENOTSOCK, see // https://github.com/jart/cosmopolitan/issues/1174 - __imp_setsockopt(resources.handle, SOL_SOCKET, kNtSoUpdateAcceptContext, - &f->handle, sizeof(f->handle)); + __imp_setsockopt(handle, SOL_SOCKET, kNtSoUpdateAcceptContext, &f->handle, + sizeof(f->handle)); // create file descriptor for new socket // don't inherit the file open mode bits @@ -141,18 +113,18 @@ textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr, g_fds.p[client].protocol = f->protocol; g_fds.p[client].sndtimeo = f->sndtimeo; g_fds.p[client].rcvtimeo = f->rcvtimeo; - g_fds.p[client].handle = resources.handle; - resources.handle = -1; - memcpy(addr, &buffer.remote.addr, sizeof(*addr)); + g_fds.p[client].handle = handle; g_fds.p[client].kind = kFdSocket; - -Finish: - pthread_cleanup_pop(false); - __sig_unblock(m); - if (client == -1 && errno == ECONNRESET) { - errno = ECONNABORTED; - } return client; } +textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr, + int accept4_flags) { + int rc; + BLOCK_SIGNALS; + rc = sys_accept_nt_impl(f, addr, accept4_flags, _SigMask); + ALLOW_SIGNALS; + return rc; +} + #endif /* __x86_64__ */ diff --git a/libc/sock/listen-nt.c b/libc/sock/listen-nt.c index b82bf4a5d..f39c9b313 100644 --- a/libc/sock/listen-nt.c +++ b/libc/sock/listen-nt.c @@ -20,18 +20,28 @@ #include "libc/nt/thunk/msabi.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" +#include "libc/sock/struct/sockaddr.h" #include "libc/sock/syscall_fd.internal.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/fio.h" #ifdef __x86_64__ __msabi extern typeof(__sys_listen_nt) *const __imp_listen; -textwindows int sys_listen_nt(struct Fd *fd, int backlog) { - npassert(fd->kind == kFdSocket); - if (__imp_listen(fd->handle, backlog) != -1) { - return 0; - } else { - return __winsockerr(); +textwindows int sys_listen_nt(struct Fd *f, int backlog) { + unassert(f->kind == kFdSocket); + + // winsock listen() requires bind() be called beforehand + if (!f->isbound) { + struct sockaddr_in sin = {AF_INET}; + if (sys_bind_nt(f, (struct sockaddr *)&sin, sizeof(sin)) == -1) + return -1; } + + if (__imp_listen(f->handle, backlog) == -1) + return __winsockerr(); + + return 0; } #endif /* __x86_64__ */ diff --git a/libc/sock/syscall_fd.internal.h b/libc/sock/syscall_fd.internal.h index 1c51d00ff..0433ff13d 100644 --- a/libc/sock/syscall_fd.internal.h +++ b/libc/sock/syscall_fd.internal.h @@ -1,7 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_SOCK_SYSCALL_INTERNAL_H_ #define COSMOPOLITAN_LIBC_SOCK_SYSCALL_INTERNAL_H_ -#include "libc/intrin/fds.h" #include "libc/calls/struct/iovec.h" +#include "libc/intrin/fds.h" #include "libc/nt/struct/overlapped.h" #include "libc/sock/struct/sockaddr.h" COSMOPOLITAN_C_START_ @@ -10,6 +10,7 @@ void sys_connect_nt_cleanup(struct Fd *, bool); int sys_accept_nt(struct Fd *, struct sockaddr_storage *, int); int sys_bind_nt(struct Fd *, const void *, uint32_t); int sys_closesocket_nt(struct Fd *); +int sys_ioctlsocket_nt(struct Fd *); int sys_connect_nt(struct Fd *, const void *, uint32_t); int sys_getpeername_nt(struct Fd *, void *, uint32_t *); int sys_getsockname_nt(struct Fd *, void *, uint32_t *); diff --git a/test/libc/sock/nonblock_test.c b/test/libc/sock/nonblock_test.c index 117582a57..e27281ce1 100644 --- a/test/libc/sock/nonblock_test.c +++ b/test/libc/sock/nonblock_test.c @@ -37,9 +37,6 @@ #include "libc/thread/thread.h" TEST(O_NONBLOCK, canBeSetBySocket_toMakeListenNonBlocking) { - // TODO(jart): this doesn't make any sense on windows - if (IsWindows()) - return; char buf[16] = {0}; uint32_t addrsize = sizeof(struct sockaddr_in); struct sockaddr_in addr = { diff --git a/test/libc/sock/recvfrom_test.c b/test/libc/sock/recvfrom_test.c index 4ef3c7a11..a662d914d 100644 --- a/test/libc/sock/recvfrom_test.c +++ b/test/libc/sock/recvfrom_test.c @@ -33,8 +33,6 @@ // two clients send a udp packet containing their local address // server verifies content of packet matches the peer's address TEST(recvfrom, test) { - if (!IsWindows()) - return; uint32_t addrsize = sizeof(struct sockaddr_in); struct sockaddr_in server = { .sin_family = AF_INET, diff --git a/test/libc/sock/sendfile_test.c b/test/libc/sock/sendfile_test.c index c63e2cb1c..2254b1529 100644 --- a/test/libc/sock/sendfile_test.c +++ b/test/libc/sock/sendfile_test.c @@ -41,9 +41,9 @@ void SetUpOnce(void) { if (IsNetbsd()) - exit(0); + exit(0); // no sendfile support if (IsOpenbsd()) - exit(0); + exit(0); // no sendfile support testlib_enable_tmp_setup_teardown(); ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath proc inet", 0)); } @@ -102,9 +102,6 @@ TEST(sendfile, testSeeking) { } TEST(sendfile, testPositioning) { - // TODO(jart): fix test regression on windows - if (IsWindows()) - return; char buf[1024]; uint32_t addrsize = sizeof(struct sockaddr_in); struct sockaddr_in addr = { @@ -130,9 +127,8 @@ TEST(sendfile, testPositioning) { ASSERT_TRUE(errno == EINVAL || errno == EPIPE); errno = 0; // XXX: WSL1 clobbers file offset on failure! - if (!__iswsl1()) { + if (!__iswsl1()) ASSERT_EQ(12, GetFileOffset(5)); - } _Exit(0); } ASSERT_SYS(0, 0, close(3)); diff --git a/test/posix/BUILD.mk b/test/posix/BUILD.mk index 420d6ea31..aabd202d6 100644 --- a/test/posix/BUILD.mk +++ b/test/posix/BUILD.mk @@ -32,7 +32,9 @@ TEST_POSIX_DIRECTDEPS = \ LIBC_INTRIN \ LIBC_MEM \ LIBC_PROC \ + LIBC_LOG \ LIBC_RUNTIME \ + LIBC_SOCK \ LIBC_STDIO \ LIBC_STR \ LIBC_SYSV \ diff --git a/test/posix/accept4_nonblock_test.c b/test/posix/accept4_nonblock_test.c new file mode 100644 index 000000000..2033e3d76 --- /dev/null +++ b/test/posix/accept4_nonblock_test.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + + // Create server socket + int server_fd; + struct sockaddr_in address; + int addrlen = sizeof(address); + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return 1; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = 0; // let os assign random port + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))) + return 2; + if (getsockname(server_fd, (struct sockaddr *)&address, + (socklen_t *)&addrlen)) + return 3; + if (listen(server_fd, SOMAXCONN)) + return 4; + + { + // poll server + struct pollfd fds[2] = { + {server_fd, POLLIN | POLLOUT}, + }; + int ret = poll(fds, 1, 0); + if (ret != 0) + return 5; + } + + // create client socket + int client_fd; + if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return 6; + if (connect(client_fd, (struct sockaddr *)&address, sizeof(address))) + return 7; + + // accept client + int server_client_fd; + if ((server_client_fd = accept4(server_fd, 0, 0, SOCK_NONBLOCK)) == -1) + return 8; + + // check that it's non-blocking + char buf[1]; + if (read(server_client_fd, buf, 1) != -1) + return 9; + if (errno != EAGAIN && errno != EWOULDBLOCK) + return 10; + + // Clean up + if (close(server_client_fd)) + return 12; + if (close(client_fd)) + return 13; + if (close(server_fd)) + return 14; + + CheckForMemoryLeaks(); +} diff --git a/test/posix/accept_inherit_nonblock_test.c b/test/posix/accept_inherit_nonblock_test.c new file mode 100644 index 000000000..42a938e06 --- /dev/null +++ b/test/posix/accept_inherit_nonblock_test.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void on_signal(int sig) { +} + +int main() { + + // Create server socket + int server_fd; + struct sockaddr_in address; + int addrlen = sizeof(address); + if ((server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) + return 1; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = 0; // let os assign random port + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))) + return 2; + if (getsockname(server_fd, (struct sockaddr *)&address, + (socklen_t *)&addrlen)) + return 3; + if (listen(server_fd, SOMAXCONN)) + return 4; + + { + // poll server + struct pollfd fds[] = {{server_fd, POLLIN | POLLOUT}}; + int ret = poll(fds, 1, 0); + if (ret != 0) + return 5; + } + + // verify server socket is non-blocking + if (accept(server_fd, 0, 0) != -1) + return 20; + if (errno != EAGAIN && errno != EWOULDBLOCK) + return 21; + + // create client socket + int client_fd; + if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return 6; + if (connect(client_fd, (struct sockaddr *)&address, sizeof(address))) + return 7; + + // prevent race condition + // impacts platforms like openbsd + fcntl(server_fd, F_SETFL, fcntl(server_fd, F_GETFL) & ~O_NONBLOCK); + + // accept client + int server_client_fd; + if ((server_client_fd = accept(server_fd, 0, 0)) == -1) + return 8; + + // check that non-blocking wasn't inherited from listener + char buf[1]; + sigaction(SIGALRM, &(struct sigaction){.sa_handler = on_signal}, 0); + ualarm(100000, 0); + if (read(server_client_fd, buf, 1) != -1) + return 9; + if (errno != EINTR) + return 10; + + // Clean up + if (close(server_client_fd)) + return 12; + if (close(client_fd)) + return 13; + if (close(server_fd)) + return 14; + + CheckForMemoryLeaks(); +} diff --git a/test/posix/accept_poll_test.c b/test/posix/accept_poll_test.c new file mode 100644 index 000000000..da6dfbcba --- /dev/null +++ b/test/posix/accept_poll_test.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + + // Create server socket + int server_fd; + struct sockaddr_in address; + int addrlen = sizeof(address); + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return 1; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = 0; // let os assign random port + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))) + return 2; + if (getsockname(server_fd, (struct sockaddr *)&address, + (socklen_t *)&addrlen)) + return 3; + if (listen(server_fd, SOMAXCONN)) + return 4; + + { + // poll server + struct pollfd fds[2] = { + {server_fd, POLLIN | POLLOUT}, + }; + int ret = poll(fds, 1, 0); + if (ret != 0) + return 5; + } + + // create client socket + int client_fd; + if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return 6; + if (connect(client_fd, (struct sockaddr *)&address, sizeof(address))) + return 7; + + { + // poll server + struct pollfd fds[] = {{server_fd, POLLIN | POLLOUT}}; + int ret = poll(fds, 1, -1u); + if (ret != 1) + return 8; + if (!(fds[0].revents & POLLIN)) + return 9; + if (fds[0].revents & POLLOUT) + return 10; + if (fds[0].revents & POLLHUP) + return 11; + if (fds[0].revents & POLLERR) + return 12; + } + + { + // poll server with invalid thing + struct pollfd fds[] = { + {server_fd, POLLIN | POLLOUT}, + {666, POLLIN | POLLOUT}, + }; + int ret = poll(fds, 2, -1u); + if (ret != 2) + return 18; + if (!(fds[0].revents & POLLIN)) + return 19; + if (fds[0].revents & POLLOUT) + return 20; + if (fds[1].revents & POLLIN) + return 21; + if (fds[1].revents & POLLOUT) + return 22; + if (!(fds[1].revents & POLLNVAL)) + return 23; + } + + // Clean up + if (close(client_fd)) + return 13; + if (close(server_fd)) + return 14; + + CheckForMemoryLeaks(); +} diff --git a/test/posix/nonblock_pipe2_test.c b/test/posix/nonblock_pipe2_test.c new file mode 100644 index 000000000..3049cb804 --- /dev/null +++ b/test/posix/nonblock_pipe2_test.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + int pipefd[2]; + char buf[PIPE_BUF]; + char buf2[PIPE_BUF]; + ssize_t bytes_read; + ssize_t bytes_written; + + // Create a pipe + if (pipe2(pipefd, O_NONBLOCK) == -1) + exit(1); + + // Test 1: Reading from an empty pipe should fail with EAGAIN + bytes_read = read(pipefd[0], buf, PIPE_BUF); + if (bytes_read != -1 || errno != EAGAIN) + exit(4); + + // Test 2: Writing to the pipe + bytes_written = write(pipefd[1], buf, PIPE_BUF); + if (bytes_written != PIPE_BUF) + exit(5); + + // Test 3: Reading from the pipe after writing + bytes_read = read(pipefd[0], buf2, PIPE_BUF); + if (bytes_read != PIPE_BUF || memcmp(buf, buf2, PIPE_BUF)) + exit(6); + + // Test 4: Fill the pipe buffer + int ch = 10; + size_t total_written = 0; + for (;;) { + memset(buf, ch, PIPE_BUF); + bytes_written = write(pipefd[1], buf, PIPE_BUF); + if (bytes_written == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; // Pipe is full + } else { + exit(7); // Unexpected error + } + } + total_written += bytes_written; + } + + // Test 5: Verify that we can read all the data we wrote + ch = 10; + size_t total_read = 0; + while (total_read < total_written) { + bytes_read = read(pipefd[0], buf2, PIPE_BUF); + if (bytes_read == -1) + exit(8); + memset(buf, ch, PIPE_BUF); + if (memcmp(buf, buf2, PIPE_BUF)) + exit(9); + total_read += bytes_read; + } + if (total_read != total_written) + exit(10); + + // Clean up + if (close(pipefd[0])) + exit(11); + if (close(pipefd[1])) + exit(12); + + CheckForMemoryLeaks(); +} diff --git a/test/posix/nonblock_pipe_test.c b/test/posix/nonblock_pipe_test.c new file mode 100644 index 000000000..c9d21e5f8 --- /dev/null +++ b/test/posix/nonblock_pipe_test.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + int pipefd[2]; + char buf[PIPE_BUF]; + char buf2[PIPE_BUF]; + ssize_t bytes_read; + ssize_t bytes_written; + + // Create a pipe + if (pipe(pipefd) == -1) + exit(1); + + // Set O_NONBLOCK flag on the pipe + for (int i = 0; i < 2; ++i) { + int flags; + if ((flags = fcntl(pipefd[i], F_GETFL, 0)) == -1) + exit(2); + if (fcntl(pipefd[i], F_SETFL, flags | O_NONBLOCK) == -1) + exit(3); + } + + // Test 1: Reading from an empty pipe should fail with EAGAIN + bytes_read = read(pipefd[0], buf, PIPE_BUF); + if (bytes_read != -1 || errno != EAGAIN) + exit(4); + + // Test 2: Writing to the pipe + bytes_written = write(pipefd[1], buf, PIPE_BUF); + if (bytes_written != PIPE_BUF) + exit(5); + + // Test 3: Reading from the pipe after writing + bytes_read = read(pipefd[0], buf2, PIPE_BUF); + if (bytes_read != PIPE_BUF || memcmp(buf, buf2, PIPE_BUF)) + exit(6); + + // Test 4: Fill the pipe buffer + int ch = 10; + size_t total_written = 0; + for (;;) { + memset(buf, ch, PIPE_BUF); + bytes_written = write(pipefd[1], buf, PIPE_BUF); + if (bytes_written == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; // Pipe is full + } else { + exit(7); // Unexpected error + } + } + total_written += bytes_written; + } + + // Test 5: Verify that we can read all the data we wrote + ch = 10; + size_t total_read = 0; + while (total_read < total_written) { + bytes_read = read(pipefd[0], buf2, PIPE_BUF); + if (bytes_read == -1) + exit(8); + memset(buf, ch, PIPE_BUF); + if (memcmp(buf, buf2, PIPE_BUF)) + exit(9); + total_read += bytes_read; + } + if (total_read != total_written) + exit(10); + + // Clean up + if (close(pipefd[0])) + exit(11); + if (close(pipefd[1])) + exit(12); + + CheckForMemoryLeaks(); +}