Rewrite Windows connect()

Our old code wasn't working with projects like Qt that call connect() in
O_NONBLOCK mode multiple times. This change overhauls connect() to use a
simpler WSAConnect() API and follows the same pattern as cosmo accept().
This change also reduces the binary footprint of read(), which no longer
needs to depend on our enormous clock_gettime() function.
This commit is contained in:
Justine Tunney 2024-09-12 23:01:20 -07:00
parent 5469202ea8
commit e142124730
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
25 changed files with 556 additions and 277 deletions

View file

@ -0,0 +1,26 @@
/*-*- 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/struct/timespec.internal.h"
#include "libc/nt/time.h"
textwindows struct timespec sys_clock_gettime_monotonic_nt(void) {
uint64_t hectons;
QueryUnbiasedInterruptTimePrecise(&hectons);
return timespec_fromnanos(hectons * 100);
}

View file

@ -21,6 +21,7 @@
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/fds.h"
@ -58,14 +59,8 @@
#define POLLPRI_ 0x0400 // MSDN unsupported
// </sync libc/sysv/consts.sh>
textwindows dontinline static struct timespec sys_poll_nt_now(void) {
uint64_t hectons;
QueryUnbiasedInterruptTimePrecise(&hectons);
return timespec_fromnanos(hectons * 100);
}
textwindows static uint32_t sys_poll_nt_waitms(struct timespec deadline) {
struct timespec now = sys_poll_nt_now();
struct timespec now = sys_clock_gettime_monotonic_nt();
if (timespec_cmp(now, deadline) < 0) {
struct timespec remain = timespec_sub(deadline, now);
int64_t millis = timespec_tomillis(remain);
@ -340,7 +335,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
int rc;
struct timespec now, timeout, deadline;
BLOCK_SIGNALS;
now = ms ? sys_poll_nt_now() : timespec_zero;
now = ms ? sys_clock_gettime_monotonic_nt() : 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);

View file

@ -24,6 +24,7 @@
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/cosmo.h"
#include "libc/ctype.h"
@ -837,7 +838,8 @@ textwindows static int CountConsoleInputBytesBlockingImpl(uint32_t ms,
uint32_t wi;
struct timespec now, deadline;
InitConsole();
deadline = timespec_add(timespec_mono(), timespec_frommillis(ms));
deadline =
timespec_add(sys_clock_gettime_monotonic_nt(), timespec_frommillis(ms));
RestartOperation:
if (_check_cancel() == -1)
return -1;
@ -870,7 +872,7 @@ RestartOperation:
// this can happen for multiple reasons. first our driver controls
// user interactions in canonical mode. secondly we could lose the
// race with another thread that's reading input.
now = timespec_mono();
now = sys_clock_gettime_monotonic_nt();
if (timespec_cmp(now, deadline) >= 0)
return etimedout();
ms = timespec_tomillis(timespec_sub(deadline, now));

View file

@ -25,6 +25,7 @@ int sys_sem_timedwait(int64_t, const struct timespec *);
int sys_utimensat(int, const char *, const struct timespec[2], int);
int sys_utimensat_nt(int, const char *, const struct timespec[2], int);
int sys_utimensat_old(int, const char *, const struct timespec[2], int);
struct timespec sys_clock_gettime_monotonic_nt(void);
const char *_DescribeTimespec(char[45], int, const struct timespec *);
#define DescribeTimespec(rc, ts) _DescribeTimespec(alloca(45), rc, ts)

View file

@ -28,6 +28,7 @@ struct Cursor {
struct Fd {
char kind;
bool isbound;
char connecting;
unsigned flags;
unsigned mode;
long handle;
@ -38,7 +39,6 @@ struct Fd {
unsigned sndtimeo; /* millis; 0 means wait forever */
void *connect_op;
struct Cursor *cursor;
struct sockaddr_storage peer;
};
struct Fds {

View file

@ -34,6 +34,7 @@ LIBC_SOCK_A_DIRECTDEPS = \
LIBC_NT_IPHLPAPI \
LIBC_NT_KERNEL32 \
LIBC_NT_NTDLL \
LIBC_NT_REALTIME \
LIBC_NT_WS2_32 \
LIBC_RUNTIME \
LIBC_STDIO \

View file

@ -18,15 +18,12 @@
*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.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/syscall_fd.internal.h"
#include "libc/sysv/consts/fio.h"
#include "libc/sysv/consts/o.h"
@ -38,8 +35,6 @@
#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;
textwindows static int sys_accept_nt_impl(struct Fd *f,
@ -61,7 +56,6 @@ textwindows static int sys_accept_nt_impl(struct Fd *f,
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)
@ -76,7 +70,7 @@ textwindows static int sys_accept_nt_impl(struct Fd *f,
return -1;
}
// we're done if user wants non-blocking
// check for non-blocking
if (f->flags & O_NONBLOCK)
return eagain();
@ -91,13 +85,6 @@ textwindows static int sys_accept_nt_impl(struct Fd *f,
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(handle, SOL_SOCKET, kNtSoUpdateAcceptContext, &f->handle,
sizeof(f->handle));
// create file descriptor for new socket
// don't inherit the file open mode bits
int oflags = 0;

View file

@ -22,6 +22,9 @@
/**
* Creates client socket file descriptor for incoming connection.
*
* On Windows, when this function blocks, there may be a 10 millisecond
* delay on the handling of signals or thread cancelation.
*
* @param fd is the server socket file descriptor
* @param opt_out_addr will receive the remote address
* @param opt_inout_addrsize provides and receives addr's byte length

View file

@ -30,12 +30,23 @@
/**
* Creates client socket file descriptor for incoming connection.
*
* When `fd` is in `O_NONBLOCK` mode, this function will raise `EAGAIN`
* when no client is available to accept. To wait until a client exists
* the poll() function may be called using `POLLIN`.
*
* On Linux, your `SO_RCVTIMEO` will timeout accept4(). Other OSes (i.e.
* Windows, MacOS, and BSDs) do not support this and will block forever.
*
* On Windows, when this function blocks, there may be a 10 millisecond
* delay on the handling of signals or thread cancelation.
*
* @param fd is the server socket file descriptor
* @param opt_out_addr will receive the remote address
* @param opt_inout_addrsize provides and receives out_addr's byte length
* @param flags can have SOCK_{CLOEXEC,NONBLOCK}, which may apply to
* both the newly created socket and the server one
* @return client fd which needs close(), or -1 w/ errno
* @raise EAGAIN if `O_NONBLOCK` and no clients pending
* @cancelationpoint
* @asyncsignalsafe
* @restartable (unless SO_RCVTIMEO)

View file

@ -16,8 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/weaken.h"
#include "libc/mem/mem.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
@ -32,9 +30,6 @@ __msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
* This function should only be called by close().
*/
textwindows int sys_closesocket_nt(struct Fd *f) {
if (_weaken(sys_connect_nt_cleanup)) {
_weaken(sys_connect_nt_cleanup)(f, true);
}
if (!__imp_closesocket(f->handle)) {
return 0;
} else {

View file

@ -16,92 +16,47 @@
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/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/cosmo.h"
#include "libc/errno.h"
#include "libc/intrin/fds.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/nt/enum/wsaid.h"
#include "libc/nt/errors.h"
#include "libc/nt/struct/guid.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/thread.h"
#include "libc/nt/struct/fdset.h"
#include "libc/nt/struct/pollfd.h"
#include "libc/nt/struct/timeval.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/syscall_fd.internal.h"
#include "libc/sock/wsaid.internal.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/fio.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#include "libc/sock/yoink.inc"
__msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
#define UNCONNECTED 0
#define CONNECTING 1
#define CONNECTED 2
struct ConnectArgs {
const void *addr;
uint32_t addrsize;
};
#define POLL_INTERVAL_MS 10
static struct {
atomic_uint once;
bool32 (*__msabi lpConnectEx)(int64_t hSocket, const struct sockaddr *name,
int namelen, const void *opt_lpSendBuffer,
uint32_t dwSendDataLength,
uint32_t *opt_out_lpdwBytesSent,
struct NtOverlapped *lpOverlapped);
} g_connectex;
__msabi extern typeof(__sys_getsockopt_nt) *const __imp_getsockopt;
__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket;
__msabi extern typeof(__sys_select_nt) *const __imp_select;
static void connectex_init(void) {
static struct NtGuid ConnectExGuid = WSAID_CONNECTEX;
g_connectex.lpConnectEx = __get_wsaid(&ConnectExGuid);
}
textwindows static int sys_connect_nt_impl(struct Fd *f, const void *addr,
uint32_t addrsize,
sigset_t waitmask) {
void sys_connect_nt_cleanup(struct Fd *f, bool cancel) {
struct NtOverlapped *overlap;
if ((overlap = f->connect_op)) {
uint32_t got, flags;
if (cancel)
CancelIoEx(f->handle, overlap);
if (WSAGetOverlappedResult(f->handle, overlap, &got, cancel, &flags) ||
WSAGetLastError() != kNtErrorIoIncomplete) {
WSACloseEvent(overlap->hEvent);
free(overlap);
f->connect_op = 0;
}
}
}
// check if already connected
if (f->connecting == 2)
return eisconn();
static int sys_connect_nt_start(int64_t hSocket,
struct NtOverlapped *lpOverlapped,
uint32_t *flags, void *arg) {
struct ConnectArgs *args = arg;
if (g_connectex.lpConnectEx(hSocket, args->addr, args->addrsize, 0, 0, 0,
lpOverlapped)) {
return 0;
} else {
return -1;
}
}
static textwindows int sys_connect_nt_impl(struct Fd *f, const void *addr,
uint32_t addrsize, sigset_t mask) {
// get connect function from winsock api
cosmo_once(&g_connectex.once, connectex_init);
// fail if previous connect() is still in progress
if (f->connect_op)
return ealready();
// ConnectEx() requires bind() be called beforehand
// winsock requires bind() be called beforehand
if (!f->isbound) {
struct sockaddr_storage ss = {0};
ss.ss_family = ((struct sockaddr *)addr)->sa_family;
@ -109,60 +64,121 @@ static textwindows int sys_connect_nt_impl(struct Fd *f, const void *addr,
return -1;
}
// perform normal connect
if (!(f->flags & O_NONBLOCK)) {
f->peer.ss_family = AF_UNSPEC;
ssize_t rc = __winsock_block(f->handle, 0, false, f->sndtimeo, mask,
sys_connect_nt_start,
&(struct ConnectArgs){addr, addrsize});
if (rc == -1 && errno == EAGAIN) {
// return ETIMEDOUT if SO_SNDTIMEO elapsed
// note that Linux will return EINPROGRESS
errno = etimedout();
} else if (!rc) {
__imp_setsockopt(f->handle, SOL_SOCKET, kNtSoUpdateConnectContext, 0, 0);
if (f->connecting == UNCONNECTED) {
// make sure winsock is in non-blocking mode
uint32_t mode = 1;
if (__imp_ioctlsocket(f->handle, FIONBIO, &mode))
return __winsockerr();
// perform non-blocking connect
if (!WSAConnect(f->handle, addr, addrsize, 0, 0, 0, 0)) {
f->connecting = CONNECTED;
return 0;
}
// check for errors
switch (WSAGetLastError()) {
case WSAEISCONN:
f->connecting = CONNECTED;
return eisconn();
case WSAEALREADY:
f->connecting = CONNECTING;
break;
case WSAEWOULDBLOCK:
break;
default:
return __winsockerr();
}
// handle non-blocking
if (f->flags & O_NONBLOCK) {
if (f->connecting == UNCONNECTED) {
f->connecting = CONNECTING;
return einprogress();
} else {
return ealready();
}
} else {
f->connecting = CONNECTING;
}
return rc;
}
// win32 getpeername() stops working in non-blocking connect mode
if (addrsize)
memcpy(&f->peer, addr, MIN(addrsize, sizeof(struct sockaddr_storage)));
for (;;) {
// perform nonblocking connect(), i.e.
// 1. connect(O_NONBLOCK) → EINPROGRESS
// 2. poll(POLLOUT)
bool32 ok;
struct NtOverlapped *overlap = calloc(1, sizeof(struct NtOverlapped));
if (!overlap)
return -1;
overlap->hEvent = WSACreateEvent();
ok = g_connectex.lpConnectEx(f->handle, addr, addrsize, 0, 0, 0, overlap);
if (ok) {
uint32_t dwBytes, dwFlags;
ok = WSAGetOverlappedResult(f->handle, overlap, &dwBytes, false, &dwFlags);
WSACloseEvent(overlap->hEvent);
free(overlap);
if (!ok) {
return __winsockerr();
// check for signals and thread cancelation
// connect() will restart if SA_RESTART is used
if (!(f->flags & O_NONBLOCK))
if (__sigcheck(waitmask, true) == -1)
return -1;
//
// "Use select to determine the completion of the connection request
// by checking if the socket is writable."
//
// —Quoth MSDN § WSAConnect function
//
// "If a socket is processing a connect call (nonblocking), failure
// of the connect attempt is indicated in exceptfds (application
// must then call getsockopt SO_ERROR to determine the error value
// to describe why the failure occurred). This document does not
// define which other errors will be included."
//
// —Quoth MSDN § select function
//
struct NtFdSet wrfds;
struct NtFdSet exfds;
struct NtTimeval timeout;
wrfds.fd_count = 1;
wrfds.fd_array[0] = f->handle;
exfds.fd_count = 1;
exfds.fd_array[0] = f->handle;
if (f->flags & O_NONBLOCK) {
timeout.tv_sec = 0;
timeout.tv_usec = 0;
} else {
timeout.tv_sec = POLL_INTERVAL_MS / 1000;
timeout.tv_usec = POLL_INTERVAL_MS % 1000 * 1000;
}
__imp_setsockopt(f->handle, SOL_SOCKET, kNtSoUpdateConnectContext, 0, 0);
int ready = __imp_select(1, 0, &wrfds, &exfds, &timeout);
if (ready == -1)
return __winsockerr();
// check if we still need more time
if (!ready) {
if (f->flags & O_NONBLOCK) {
return ealready();
} else {
continue;
}
}
// check if connect failed
if (exfds.fd_count) {
int err;
uint32_t len = sizeof(err);
if (__imp_getsockopt(f->handle, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
return __winsockerr();
if (!err)
return eio(); // should be impossible
errno = __dos2errno(err);
return -1;
}
// handle successful connection
if (!wrfds.fd_count)
return eio(); // should be impossible
f->connecting = CONNECTED;
return 0;
} else if (WSAGetLastError() == kNtErrorIoPending) {
f->connect_op = overlap;
return einprogress();
} else {
WSACloseEvent(overlap->hEvent);
free(overlap);
return __winsockerr();
}
}
textwindows int sys_connect_nt(struct Fd *f, const void *addr,
uint32_t addrsize) {
sigset_t mask = __sig_block();
int rc = sys_connect_nt_impl(f, addr, addrsize, mask);
__sig_unblock(mask);
int rc;
BLOCK_SIGNALS;
rc = sys_connect_nt_impl(f, addr, addrsize, _SigMask);
ALLOW_SIGNALS;
return rc;
}

View file

@ -31,12 +31,23 @@
/**
* Connects socket to remote end.
*
* ProTip: Connectionless sockets, e.g. UDP, can be connected too. The
* benefit is not needing to specify the remote address on each send. It
* also means getsockname() can be called to retrieve routing details.
* When `fd` is in `O_NONBLOCK` mode, this raises `EINPROGRESS`. To wait
* for establishment poll() function may be called using `POLLOUT`. Then
* `SO_ERROR` may be used to check for errors.
*
* Connectionless sockets, e.g. UDP, can be connected too. The benefit
* is not needing to specify the remote address on each send. It also
* means getsockname() can be called to retrieve routing details.
*
* On Linux, your `SO_SNDTIMEO` will timeout connect(). Other OSes (i.e.
* Windows, MacOS, and BSDs) do not support this and will block forever.
*
* On Windows, when this function blocks, there may be a 10 millisecond
* delay on the handling of signals or thread cancelation.
*
* @return 0 on success or -1 w/ errno
* @raise EALREADY if a non-blocking connection request already happened
* @raise EINPROGRESS if `O_NONBLOCK` and connecting process initiated
* @raise EALREADY if a `O_NONBLOCK` connecting already in flight
* @raise EADDRINUSE if local address is already in use
* @raise EINTR if a signal handler was called instead
* @raise ENETUNREACH if network is unreachable

View file

@ -46,12 +46,7 @@ static int __getsockpeername(int fd, struct sockaddr *out_addr,
if (IsWindows()) {
if (__isfdkind(fd, kFdSocket)) {
if ((rc = impl_win32(g_fds.p[fd].handle, &ss, &size))) {
if (impl_win32 == __imp_getpeername &&
g_fds.p[fd].peer.ss_family != AF_UNSPEC) {
ss = g_fds.p[fd].peer;
rc = 0;
} else if (impl_win32 == __imp_getsockname &&
WSAGetLastError() == WSAEINVAL) {
if (impl_win32 == __imp_getsockname && WSAGetLastError() == WSAEINVAL) {
// The socket has not been bound to an address with bind, or
// ADDR_ANY is specified in bind but connection has not yet
// occurred. -MSDN

View file

@ -47,6 +47,19 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname,
in_optlen = 0;
}
if (level == SOL_SOCKET && optname == SO_ERROR) {
if (in_optlen >= sizeof(int)) {
int err;
uint32_t len = sizeof(err);
if (__imp_getsockopt(fd->handle, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
return __winsockerr();
*(int *)out_opt_optval = __dos2errno(err);
*inout_optlen = sizeof(int);
} else {
return einval();
}
}
if (level == SOL_SOCKET &&
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
if (in_optlen >= sizeof(struct timeval)) {

View file

@ -17,9 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/intrin/fds.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
@ -50,9 +50,8 @@ static textwindows int sys_recv_nt_start(int64_t handle,
textwindows ssize_t sys_recv_nt(int fd, const struct iovec *iov, size_t iovlen,
uint32_t flags) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_PEEK | _MSG_WAITALL)) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_PEEK | _MSG_WAITALL))
return einval();
}
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();

View file

@ -17,9 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/intrin/fds.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"

View file

@ -17,9 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/intrin/fds.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"

View file

@ -38,6 +38,7 @@
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/sendfile.internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sock/wsaid.internal.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/errfuns.h"
@ -58,7 +59,7 @@ static void transmitfile_init(void) {
g_transmitfile.lpTransmitFile = __get_wsaid(&TransmitfileGuid);
}
static dontinline textwindows ssize_t sys_sendfile_nt(
textwindows dontinline static ssize_t sys_sendfile_nt(
int outfd, int infd, int64_t *opt_in_out_inoffset, uint32_t uptobytes) {
ssize_t rc;
uint32_t flags = 0;

View file

@ -17,9 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/intrin/fds.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"

View file

@ -6,7 +6,6 @@
#include "libc/sock/struct/sockaddr.h"
COSMOPOLITAN_C_START_
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 *);

View file

@ -19,6 +19,10 @@
dir=libc/sysv/consts
. libc/sysv/gen.sh
# syscon errno EALREADY 114 114 37 37 37 37 37 10037 # connection already in progress; bsd consensus; WSAEALREADY; raised by connect(2), send(2), ip(7)
# syscon errno EINPROGRESS 115 115 36 36 36 36 36 10036 # bsd consensus; WSAEINPROGRESS; raised by connect(2) w/ O_NONBLOCK
# syscon errno EISCONN 106 106 56 56 56 56 56 10056 # socket is connected; bsd consensus; WSAEISCONN; raised by connect(2), send(2), unix(7), ip(7)
# The Fifth Bell System, Community Edition
# » catalogue of carnage
#

View file

@ -1,113 +0,0 @@
/*-*- 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/atomic.h"
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/intrin/atomic.h"
#include "libc/macros.h"
#include "libc/runtime/runtime.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
#include "libc/thread/thread2.h"
int cpu_count;
void SetUpOnce(void) {
cpu_count = __get_cpu_count();
}
////////////////////////////////////////////////////////////////////////////////
// AFFINITY TEST
TEST(sched_getcpu, affinity_test) {
if (IsXnu())
return;
if (IsNetbsd())
return;
if (IsOpenbsd())
return;
SPAWN(fork);
int n = cpu_count;
for (int i = 0; i < n; ++i) {
cpu_set_t affinity;
CPU_ZERO(&affinity);
CPU_SET(i, &affinity);
ASSERT_EQ(
0, pthread_setaffinity_np(pthread_self(), sizeof(affinity), &affinity));
EXPECT_EQ(i, sched_getcpu());
}
EXITS(0);
}
////////////////////////////////////////////////////////////////////////////////
// KLUDGE TEST
#define THREADS 2
#define ITERATIONS 100000
int g_hits[256];
atomic_int g_sync;
int call_sched_getcpu(void) {
int res = sched_getcpu();
ASSERT_NE(-1, res);
ASSERT_GE(res, 0);
ASSERT_LT(res, cpu_count);
return res;
}
void *worker(void *arg) {
int ith = (long)arg;
int nth = THREADS;
for (int i = 0; i < ITERATIONS; ++i) {
// help execution of threads be interleaved
int sync = atomic_fetch_add(&g_sync, 1);
if (sync % nth == ith) {
g_hits[call_sched_getcpu() % ARRAYLEN(g_hits)]++;
}
}
return 0;
}
TEST(sched_getcpu, kludge_test) {
#ifdef __x86_64__
if (IsXnu())
return;
#endif
if (IsNetbsd())
return;
if (IsOpenbsd())
return;
if (cpu_count < THREADS)
return;
pthread_t th[THREADS];
for (int i = 0; i < THREADS; ++i)
ASSERT_EQ(0, pthread_create(th + i, 0, worker, (void *)(long)i));
for (int i = 0; i < THREADS; ++i)
ASSERT_EQ(0, pthread_join(th[i], 0));
int hit = 0;
for (int i = 0; i < ARRAYLEN(g_hits); ++i)
hit += !!g_hits[i];
ASSERT_GE(hit, THREADS);
}

View file

@ -121,12 +121,6 @@ TEST(connect, nonblocking) {
ASSERT_SYS(0, 3, socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
ASSERT_SYS(EINPROGRESS, -1,
connect(3, (struct sockaddr *)&addr, sizeof(addr)));
if (!IsLinux() && !IsNetbsd() && !IsXnu()) {
// this doens't work on linux and netbsd
// on MacOS this can EISCONN before accept() is called
ASSERT_SYS(EALREADY, -1,
connect(3, (struct sockaddr *)&addr, sizeof(addr)));
}
ASSERT_SYS(EAGAIN, -1, read(3, buf, 16));
*sem = 1;
{ // wait until connected

View file

@ -0,0 +1,260 @@
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
char buffer[1024];
fd_set wset;
int listenfd, connfd, sockfd;
int s, error;
pid_t pid;
socklen_t len;
struct sockaddr_in serv_addr, cli_addr;
uint16_t port;
printf("\n");
/* Create listening socket */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket() failed");
exit(1);
}
/* Initialize server address */
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
serv_addr.sin_port = htons(0);
/* Bind socket */
if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
exit(2);
}
/* Get the assigned port number */
len = sizeof(serv_addr);
if (getsockname(listenfd, (struct sockaddr *)&serv_addr, &len) < 0) {
perror("getsockname");
exit(3);
}
port = ntohs(serv_addr.sin_port);
/* Listen on the socket */
if (listen(listenfd, 1) < 0) {
perror("listen");
exit(4);
}
/* Fork a child process */
pid = fork();
if (pid < 0) {
perror("fork");
exit(5);
} else if (pid == 0) {
/* Child process: acts as the client */
close(listenfd); /* Close the listening socket in the child */
/* Create socket */
sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sockfd < 0) {
perror("socket");
exit(6);
}
/* Initialize server address */
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* 127.0.0.1 */
serv_addr.sin_port = htons(port); /* Assigned port */
/* Try calling read() before connection is established */
s = read(sockfd, buffer, sizeof(buffer));
if (s < 0) {
if (errno == ENOTCONN) {
printf("read #1 enotconn\n");
/* good */
} else {
perror("read #1");
exit(6);
}
} else {
printf("read #1 succeeded\n");
exit(6);
}
#if 0
/* Try calling read() before connection is established */
s = write(sockfd, buffer, sizeof(buffer));
if (s < 0) {
if (errno == ENOTCONN) {
/* good */
} else {
perror("write");
}
} else {
printf("Wrote %d bytes: %.*s\n", s, s, buffer);
}
#endif
/* Attempt to connect */
s = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (s == 0) {
printf("connect #1 success\n");
} else if (s < 0 && errno == EINPROGRESS) {
printf("connect #1 einprogress\n");
} else {
perror("connect #1");
exit(10);
}
/* Try calling read() before connection is established */
s = read(sockfd, buffer, sizeof(buffer));
if (s < 0) {
if (errno == EAGAIN) {
printf("read #2 eagain\n");
} else {
perror("read #2");
exit(10);
}
} else {
printf("read #2 succeeded\n");
exit(10);
}
/* Try calling connect() again to trigger EALREADY */
s = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (!s) {
printf("connect #2 succeeded\n");
} else if (s < 0 && errno == EALREADY) {
printf("connect #2 ealready\n");
} else if (s < 0 && errno == EISCONN) {
printf("connect #2 eisconn\n");
} else if (s < 0) {
perror("connect #2");
exit(11);
}
/* Try calling connect() again to trigger EALREADY */
s = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (!s) {
printf("connect #3 succeeded\n");
} else if (errno == EALREADY) {
printf("connect #3 ealready\n");
} else if (errno == EISCONN) {
printf("connect #3 eisconn\n");
} else {
perror("connect");
exit(11);
}
/* Try calling read() before connection is established */
s = read(sockfd, buffer, sizeof(buffer));
if (s < 0) {
if (errno == EAGAIN) {
/* good */
} else {
perror("read");
}
} else {
printf("Read %d bytes: %.*s\n", s, s, buffer);
}
/* Use select() to wait for the socket to be writable */
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
s = select(sockfd + 1, NULL, &wset, NULL, 0);
if (s == 0) {
printf("not possible\n");
exit(11);
} else if (s < 0) {
perror("select");
exit(12);
}
/* Check if socket is writable */
if (FD_ISSET(sockfd, &wset)) {
/* Check for error */
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
exit(13);
if (error) {
printf("connection failed after select(): %s\n", strerror(error));
exit(14);
}
} else {
exit(16);
}
/* Try calling connect() again to trigger EISCONN */
s = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (!s) {
printf("connect #4 succeeded\n");
} else if (s < 0 && errno == EISCONN) {
printf("connect #4 eisconn\n");
} else if (s < 0) {
exit(17);
}
if (close(sockfd))
exit(15);
exit(0);
} else {
/* Accept connection */
len = sizeof(cli_addr);
connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
if (connfd < 0) {
close(listenfd);
wait(NULL);
exit(18);
}
/* Read data from client */
s = read(connfd, buffer, sizeof(buffer));
if (s < 0) {
exit(51);
} else if (!s) {
/* got close */
} else {
exit(50);
}
/* Close connected socket */
if (close(connfd)) {
close(listenfd);
wait(NULL);
exit(19);
}
/* Close listening socket */
if (close(listenfd)) {
wait(NULL);
exit(20);
}
/* Wait for child process to finish */
int status;
if (waitpid(pid, &status, 0) < 0)
exit(21);
printf("\n");
if (WIFEXITED(status)) {
exit(WEXITSTATUS(status)); /* Return child's exit status */
} else {
exit(22);
}
}
exit(23);
}

View file

@ -0,0 +1,79 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int listenfd, connfd;
struct sockaddr_in serv_addr;
struct timeval timeout;
socklen_t len;
struct sockaddr_in cli_addr;
// only linux really does this
if (!IsLinux())
return 0;
// create listening socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket");
exit(1);
}
// initialize server address
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
serv_addr.sin_port = htons(0);
// bind socket
if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
close(listenfd);
exit(2);
}
// listen on the socket
if (listen(listenfd, 5) < 0) {
perror("listen");
close(listenfd);
exit(3);
}
// accept for 200ms
timeout.tv_sec = 0;
timeout.tv_usec = 200e3;
if (setsockopt(listenfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout))) {
perror("setsockopt");
close(listenfd);
exit(4);
}
// Accept connection
len = sizeof(cli_addr);
connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
if (connfd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* printf("accept() timed out\n"); */
} else {
perror("accept");
}
} else {
printf("Connection accepted from client.\n");
// Close connected socket
close(connfd);
}
// Close listening socket
close(listenfd);
}