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/intrin/weaken.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
int _check_cancel(void) { textwindows int _check_cancel(void) {
if (_weaken(_pthread_cancel_ack) && // if (_weaken(_pthread_cancel_ack) && //
_pthread_self() && !(_pthread_self()->pt_flags & PT_NOCANCEL) && _pthread_self() && !(_pthread_self()->pt_flags & PT_NOCANCEL) &&
atomic_load_explicit(&_pthread_self()->pt_canceled, atomic_load_explicit(&_pthread_self()->pt_canceled,

View file

@ -25,6 +25,7 @@ int __ensurefds(int);
uint32_t sys_getuid_nt(void); uint32_t sys_getuid_nt(void);
int __ensurefds_unlocked(int); int __ensurefds_unlocked(int);
void __printfds(struct Fd *, size_t); void __printfds(struct Fd *, size_t);
int __sigcheck(sigset_t, bool);
int CountConsoleInputBytes(void); int CountConsoleInputBytes(void);
int FlushConsoleInputBytes(void); int FlushConsoleInputBytes(void);
int64_t GetConsoleInputHandle(void); int64_t GetConsoleInputHandle(void);

View file

@ -17,25 +17,19 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/internal.h" #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/atomic.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/synchronization.h" #include "libc/nt/synchronization.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__ #ifdef __x86_64__
// returns 0 on timeout or spurious wakeup // returns 0 on timeout or spurious wakeup
// raises EINTR if a signal delivery interrupted wait operation // raises EINTR if a signal delivery interrupted wait operation
// raises ECANCELED if this POSIX thread was canceled in masked mode // 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) { bool restartable) {
int sig, handler_was_called; if (__sigcheck(waitmask, restartable) == -1)
if (_check_cancel() == -1)
return -1; return -1;
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask)))
goto HandleSignal;
int expect = 0; int expect = 0;
atomic_int futex = 0; atomic_int futex = 0;
struct PosixThread *pt = _pthread_self(); 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); atomic_store_explicit(&pt->pt_blocker, &futex, memory_order_release);
bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay); bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
if (ok && _weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { if (ok && __sigcheck(waitmask, restartable) == -1)
HandleSignal: return -1;
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; return 0;
} }

View file

@ -16,23 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.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/sigset.internal.h"
#include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.h"
#include "libc/calls/syscall_support-nt.internal.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/atomic.h"
#include "libc/intrin/fds.h" #include "libc/intrin/fds.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.h" #include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h" #include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h" #include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/wait.h" #include "libc/nt/enum/wait.h"
@ -42,22 +34,13 @@
#include "libc/nt/runtime.h" #include "libc/nt/runtime.h"
#include "libc/nt/struct/pollfd.h" #include "libc/nt/struct/pollfd.h"
#include "libc/nt/synchronization.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/time.h"
#include "libc/nt/winsock.h" #include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h" #include "libc/sock/internal.h"
#include "libc/sock/struct/pollfd.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/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/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__ #ifdef __x86_64__
#define POLL_INTERVAL_MS 10 #define POLL_INTERVAL_MS 10
@ -75,24 +58,22 @@
#define POLLPRI_ 0x0400 // MSDN unsupported #define POLLPRI_ 0x0400 // MSDN unsupported
// </sync libc/sysv/consts.sh> // </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; uint64_t hectons;
QueryUnbiasedInterruptTimePrecise(&hectons); QueryUnbiasedInterruptTimePrecise(&hectons);
return timespec_fromnanos(hectons * 100); return timespec_fromnanos(hectons * 100);
} }
textwindows static int sys_poll_nt_sigcheck(sigset_t sigmask) { textwindows static uint32_t sys_poll_nt_waitms(struct timespec deadline) {
int sig, handler_was_called; struct timespec now = sys_poll_nt_now();
if (_check_cancel() == -1) if (timespec_cmp(now, deadline) < 0) {
return -1; struct timespec remain = timespec_sub(deadline, now);
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(sigmask))) { int64_t millis = timespec_tomillis(remain);
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, sigmask); uint32_t waitfor = MIN(millis, 0xffffffffu);
if (_check_cancel() == -1) return MIN(waitfor, POLL_INTERVAL_MS);
return -1; } else {
if (handler_was_called) return 0; // we timed out
return eintr();
} }
return 0;
} }
// Polls on the New Technology. // 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 // 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 // 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 // both signals and posix thread cancelation, while the poll is polling
textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds, textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds,
uint32_t *ms, sigset_t sigmask) { struct timespec deadline,
bool ok; sigset_t waitmask) {
uint64_t millis;
int fileindices[64]; int fileindices[64];
int sockindices[64]; int sockindices[64];
int64_t filehands[64]; int64_t filehands[64];
struct PosixThread *pt; struct PosixThread *pt;
int i, rc, ev, kind, gotsocks; int i, rc, ev, kind, gotsocks;
struct sys_pollfd_nt sockfds[64]; struct sys_pollfd_nt sockfds[64];
struct timespec deadline, remain, now;
uint32_t cm, fi, wi, sn, pn, avail, waitfor, already_slept; 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 // ensure revents is cleared
for (i = 0; i < nfds; ++i) for (i = 0; i < nfds; ++i)
fds[i].revents = 0; 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; rc += !!fds[i].revents;
} }
__fds_unlock(); __fds_unlock();
if (rc) if (rc == -1)
return rc; return rc;
// perform poll operation // 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_)) { if ((ev & POLLWRNORM_) && !(ev & POLLRDNORM_)) {
fds[fi].revents = fds[fi].events & (POLLRDNORM_ | POLLWRNORM_); fds[fi].revents = fds[fi].events & (POLLRDNORM_ | POLLWRNORM_);
} else if (GetFileType(filehands[i]) == kNtFileTypePipe) { } else if (GetFileType(filehands[i]) == kNtFileTypePipe) {
ok = PeekNamedPipe(filehands[i], 0, 0, 0, &avail, 0); if (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 (avail) if (avail)
fds[fi].revents = POLLRDNORM_; fds[fi].revents = POLLRDNORM_;
} else if (GetLastError() == kNtErrorHandleEof || } 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 // determine how long to wait
now = sys_poll_nt_now(); waitfor = sys_poll_nt_waitms(deadline);
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
}
// check for events and/or readiness on sockets // check for events and/or readiness on sockets
// we always do this due to issues with POLLOUT // 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() // if we need to wait, then we prefer to wait inside WSAPoll()
// this ensures network events are received in ~10µs not ~10ms // this ensures network events are received in ~10µs not ~10ms
if (!rc && waitfor) { if (!rc && waitfor) {
if (sys_poll_nt_sigcheck(sigmask)) if (__sigcheck(waitmask, false))
return -1; return -1;
already_slept = waitfor; already_slept = waitfor;
} else { } else {
@ -253,7 +218,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
++rc; ++rc;
} }
} else if (already_slept) { } else if (already_slept) {
if (sys_poll_nt_sigcheck(sigmask)) if (__sigcheck(waitmask, false))
return -1; return -1;
} }
} else { } 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 // this ensures low latency for apps like emacs which with no sock
// here we shall actually report that something can be written too // here we shall actually report that something can be written too
if (!already_slept) { if (!already_slept) {
if (sys_poll_nt_sigcheck(sigmask)) if (__sigcheck(waitmask, false))
return -1; return -1;
pt = _pthread_self(); pt = _pthread_self();
filehands[pn] = pt->pt_semaphore = CreateSemaphore(0, 0, 1, 0); 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(); return __winerr();
} else if (wi == pn) { } else if (wi == pn) {
// our semaphore was signalled // our semaphore was signalled
if (sys_poll_nt_sigcheck(sigmask)) if (__sigcheck(waitmask, false))
return -1; return -1;
} else if ((wi ^ kNtWaitAbandoned) < pn) { } else if ((wi ^ kNtWaitAbandoned) < pn) {
// this is possibly because a process or thread was killed // 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 { } else {
// should only be possible on kNtWaitTimeout or semaphore abandoned // should only be possible on kNtWaitTimeout or semaphore abandoned
// keep looping for events and we'll catch timeout when appropriate // keep looping for events and we'll catch timeout when appropriate
if (sys_poll_nt_sigcheck(sigmask)) if (__sigcheck(waitmask, false))
return -1; return -1;
} }
} }
@ -341,11 +306,44 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
return rc; 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, textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
const sigset_t *sigmask) { const sigset_t *sigmask) {
int rc; int rc;
struct timespec now, timeout, deadline;
BLOCK_SIGNALS; 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; ALLOW_SIGNALS;
return rc; return rc;
} }

View file

@ -26,13 +26,6 @@
* should just create a separate thread for each client. poll() isn't a * should just create a separate thread for each client. poll() isn't a
* scalable i/o solution on any platform. * 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 * 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 * file descriptors are valid. The canonical way to do this is to set
* events to 0 which prevents blocking and causes only the invalid, * 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 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. * 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 * @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 * and if it's a negative number then the entry is ignored, plus
* revents will be set to zero * 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 { } else {
uint32_t ms; uint32_t ms;
if (!timeout || uint32_t *msp;
ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) { if (timeout &&
ms = -1u; !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) { if (IsOpenbsd() && fdcount != -1) {

View file

@ -384,12 +384,14 @@ textwindows static int ProcessMouseEvent(const struct NtInputRecord *r,
kNtLeftAltPressed | kNtRightAltPressed))) { kNtLeftAltPressed | kNtRightAltPressed))) {
// we disable mouse highlighting when the tty is put in raw mode // we disable mouse highlighting when the tty is put in raw mode
// to mouse wheel events with widely understood vt100 arrow keys // to mouse wheel events with widely understood vt100 arrow keys
*p++ = 033; for (int i = 0; i < 3; ++i) {
*p++ = !__keystroke.ohno_decckm ? '[' : 'O'; *p++ = 033;
if (isup) { *p++ = !__keystroke.ohno_decckm ? '[' : 'O';
*p++ = 'A'; if (isup) {
} else { *p++ = 'A';
*p++ = 'B'; } else {
*p++ = 'B';
}
} }
} }
} else if ((bs || currentbs) && (__ttyconf.magic & kTtyXtMouse)) { } 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 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/sigset.internal.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/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/thunk/msabi.h"
#include "libc/nt/winsock.h" #include "libc/nt/winsock.h"
#include "libc/sock/internal.h" #include "libc/sock/internal.h"
#include "libc/sock/struct/sockaddr.h" #include "libc/sock/struct/sockaddr.h"
#include "libc/sock/wsaid.internal.h" #include "libc/sock/syscall_fd.internal.h"
#include "libc/str/str.h" #include "libc/sysv/consts/fio.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/sol.h"
#include "libc/thread/thread.h" #include "libc/sysv/errfuns.h"
#ifdef __x86_64__ #ifdef __x86_64__
#define POLL_INTERVAL_MS 10
__msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt; __msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket; __msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket;
union AcceptExAddr { textwindows static int sys_accept_nt_impl(struct Fd *f,
struct sockaddr_storage addr; struct sockaddr_storage *addr,
char buf[sizeof(struct sockaddr_storage) + 16]; int accept4_flags,
}; sigset_t waitmask) {
struct AcceptExBuffer {
union AcceptExAddr local;
union AcceptExAddr remote;
};
struct AcceptResources {
int64_t handle; 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; 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 // accepting sockets must always be non-blocking at the os level. this
// inherit the listener configuration // is because WSAAccept doesn't support overlapped i/o operations. the
if ((resources.handle = WSASocket(f->family, f->type, f->protocol, 0, 0, // AcceptEx function claims to support overlapped i/o however it can't
kNtWsaFlagOverlapped)) == -1) { // be canceled by CancelIoEx, which makes it quite useless to us sadly
client = __winsockerr(); // this can't be called in listen(), because then fork() will break it
goto Finish; uint32_t mode = 1;
} if (__imp_ioctlsocket(f->handle, FIONBIO, &mode))
return __winsockerr();
// accept network connection for (;;) {
// this operation can re-enter, interrupt, cancel, block, timeout, etc.
struct AcceptExBuffer buffer; // perform non-blocking accept
ssize_t bytes_received = __winsock_block( // we assume listen() put f->handle in non-blocking mode
resources.handle, 0, !!(f->flags & O_NONBLOCK), f->rcvtimeo, m, int32_t addrsize = sizeof(*addr);
sys_accept_nt_start, &(struct AcceptArgs){f->handle, &buffer}); struct sockaddr *paddr = (struct sockaddr *)addr;
if (bytes_received == -1) { if ((handle = WSAAccept(f->handle, paddr, &addrsize, 0, 0)) != -1)
__imp_closesocket(resources.handle); break;
goto Finish;
// 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 // inherit properties of listening socket
// errors ignored as if f->handle was created before forking // errors ignored as if f->handle was created before forking
// this fails with WSAENOTSOCK, see // this fails with WSAENOTSOCK, see
// https://github.com/jart/cosmopolitan/issues/1174 // https://github.com/jart/cosmopolitan/issues/1174
__imp_setsockopt(resources.handle, SOL_SOCKET, kNtSoUpdateAcceptContext, __imp_setsockopt(handle, SOL_SOCKET, kNtSoUpdateAcceptContext, &f->handle,
&f->handle, sizeof(f->handle)); sizeof(f->handle));
// create file descriptor for new socket // create file descriptor for new socket
// don't inherit the file open mode bits // 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].protocol = f->protocol;
g_fds.p[client].sndtimeo = f->sndtimeo; g_fds.p[client].sndtimeo = f->sndtimeo;
g_fds.p[client].rcvtimeo = f->rcvtimeo; g_fds.p[client].rcvtimeo = f->rcvtimeo;
g_fds.p[client].handle = resources.handle; g_fds.p[client].handle = handle;
resources.handle = -1;
memcpy(addr, &buffer.remote.addr, sizeof(*addr));
g_fds.p[client].kind = kFdSocket; g_fds.p[client].kind = kFdSocket;
Finish:
pthread_cleanup_pop(false);
__sig_unblock(m);
if (client == -1 && errno == ECONNRESET) {
errno = ECONNABORTED;
}
return client; 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__ */ #endif /* __x86_64__ */

View file

@ -20,18 +20,28 @@
#include "libc/nt/thunk/msabi.h" #include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h" #include "libc/nt/winsock.h"
#include "libc/sock/internal.h" #include "libc/sock/internal.h"
#include "libc/sock/struct/sockaddr.h"
#include "libc/sock/syscall_fd.internal.h" #include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/fio.h"
#ifdef __x86_64__ #ifdef __x86_64__
__msabi extern typeof(__sys_listen_nt) *const __imp_listen; __msabi extern typeof(__sys_listen_nt) *const __imp_listen;
textwindows int sys_listen_nt(struct Fd *fd, int backlog) { textwindows int sys_listen_nt(struct Fd *f, int backlog) {
npassert(fd->kind == kFdSocket); unassert(f->kind == kFdSocket);
if (__imp_listen(fd->handle, backlog) != -1) {
return 0; // winsock listen() requires bind() be called beforehand
} else { if (!f->isbound) {
return __winsockerr(); 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__ */ #endif /* __x86_64__ */

View file

@ -1,7 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_SOCK_SYSCALL_INTERNAL_H_ #ifndef COSMOPOLITAN_LIBC_SOCK_SYSCALL_INTERNAL_H_
#define 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/calls/struct/iovec.h"
#include "libc/intrin/fds.h"
#include "libc/nt/struct/overlapped.h" #include "libc/nt/struct/overlapped.h"
#include "libc/sock/struct/sockaddr.h" #include "libc/sock/struct/sockaddr.h"
COSMOPOLITAN_C_START_ 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_accept_nt(struct Fd *, struct sockaddr_storage *, int);
int sys_bind_nt(struct Fd *, const void *, uint32_t); int sys_bind_nt(struct Fd *, const void *, uint32_t);
int sys_closesocket_nt(struct Fd *); int sys_closesocket_nt(struct Fd *);
int sys_ioctlsocket_nt(struct Fd *);
int sys_connect_nt(struct Fd *, const void *, uint32_t); int sys_connect_nt(struct Fd *, const void *, uint32_t);
int sys_getpeername_nt(struct Fd *, void *, uint32_t *); int sys_getpeername_nt(struct Fd *, void *, uint32_t *);
int sys_getsockname_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" #include "libc/thread/thread.h"
TEST(O_NONBLOCK, canBeSetBySocket_toMakeListenNonBlocking) { TEST(O_NONBLOCK, canBeSetBySocket_toMakeListenNonBlocking) {
// TODO(jart): this doesn't make any sense on windows
if (IsWindows())
return;
char buf[16] = {0}; char buf[16] = {0};
uint32_t addrsize = sizeof(struct sockaddr_in); uint32_t addrsize = sizeof(struct sockaddr_in);
struct sockaddr_in addr = { struct sockaddr_in addr = {

View file

@ -33,8 +33,6 @@
// two clients send a udp packet containing their local address // two clients send a udp packet containing their local address
// server verifies content of packet matches the peer's address // server verifies content of packet matches the peer's address
TEST(recvfrom, test) { TEST(recvfrom, test) {
if (!IsWindows())
return;
uint32_t addrsize = sizeof(struct sockaddr_in); uint32_t addrsize = sizeof(struct sockaddr_in);
struct sockaddr_in server = { struct sockaddr_in server = {
.sin_family = AF_INET, .sin_family = AF_INET,

View file

@ -41,9 +41,9 @@
void SetUpOnce(void) { void SetUpOnce(void) {
if (IsNetbsd()) if (IsNetbsd())
exit(0); exit(0); // no sendfile support
if (IsOpenbsd()) if (IsOpenbsd())
exit(0); exit(0); // no sendfile support
testlib_enable_tmp_setup_teardown(); testlib_enable_tmp_setup_teardown();
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath proc inet", 0)); ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath proc inet", 0));
} }
@ -102,9 +102,6 @@ TEST(sendfile, testSeeking) {
} }
TEST(sendfile, testPositioning) { TEST(sendfile, testPositioning) {
// TODO(jart): fix test regression on windows
if (IsWindows())
return;
char buf[1024]; char buf[1024];
uint32_t addrsize = sizeof(struct sockaddr_in); uint32_t addrsize = sizeof(struct sockaddr_in);
struct sockaddr_in addr = { struct sockaddr_in addr = {
@ -130,9 +127,8 @@ TEST(sendfile, testPositioning) {
ASSERT_TRUE(errno == EINVAL || errno == EPIPE); ASSERT_TRUE(errno == EINVAL || errno == EPIPE);
errno = 0; errno = 0;
// XXX: WSL1 clobbers file offset on failure! // XXX: WSL1 clobbers file offset on failure!
if (!__iswsl1()) { if (!__iswsl1())
ASSERT_EQ(12, GetFileOffset(5)); ASSERT_EQ(12, GetFileOffset(5));
}
_Exit(0); _Exit(0);
} }
ASSERT_SYS(0, 0, close(3)); ASSERT_SYS(0, 0, close(3));

View file

@ -32,7 +32,9 @@ TEST_POSIX_DIRECTDEPS = \
LIBC_INTRIN \ LIBC_INTRIN \
LIBC_MEM \ LIBC_MEM \
LIBC_PROC \ LIBC_PROC \
LIBC_LOG \
LIBC_RUNTIME \ LIBC_RUNTIME \
LIBC_SOCK \
LIBC_STDIO \ LIBC_STDIO \
LIBC_STR \ LIBC_STR \
LIBC_SYSV \ 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();
}