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.
This commit is contained in:
Justine Tunney 2024-09-12 01:18:14 -07:00
parent 6f868fe1de
commit acd6c32184
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
20 changed files with 622 additions and 209 deletions

View file

@ -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,

View file

@ -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);

View file

@ -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;
}

View file

@ -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
// </sync libc/sysv/consts.sh>
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;
}

View file

@ -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

View file

@ -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) {

View file

@ -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)) {

40
libc/calls/sigcheck.c Normal file
View file

@ -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;
}

View file

@ -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__ */

View file

@ -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__ */

View file

@ -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 *);

View file

@ -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 = {

View file

@ -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,

View file

@ -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));

View file

@ -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 \

View file

@ -0,0 +1,71 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
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();
}

View file

@ -0,0 +1,85 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
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();
}

View file

@ -0,0 +1,94 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
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();
}

View file

@ -0,0 +1,75 @@
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
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();
}

View file

@ -0,0 +1,84 @@
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
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();
}