mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
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:
parent
6f868fe1de
commit
acd6c32184
20 changed files with 622 additions and 209 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
40
libc/calls/sigcheck.c
Normal 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;
|
||||
}
|
|
@ -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__ */
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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 *);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 \
|
||||
|
|
71
test/posix/accept4_nonblock_test.c
Normal file
71
test/posix/accept4_nonblock_test.c
Normal 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();
|
||||
}
|
85
test/posix/accept_inherit_nonblock_test.c
Normal file
85
test/posix/accept_inherit_nonblock_test.c
Normal 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();
|
||||
}
|
94
test/posix/accept_poll_test.c
Normal file
94
test/posix/accept_poll_test.c
Normal 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();
|
||||
}
|
75
test/posix/nonblock_pipe2_test.c
Normal file
75
test/posix/nonblock_pipe2_test.c
Normal 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();
|
||||
}
|
84
test/posix/nonblock_pipe_test.c
Normal file
84
test/posix/nonblock_pipe_test.c
Normal 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();
|
||||
}
|
Loading…
Reference in a new issue