Skip to content

Commit

Permalink
Make more Windows socket fixes and improvements
Browse files Browse the repository at this point in the history
This change makes send() / sendto() always block on Windows. It's needed
because poll(POLLOUT) doesn't guarantee a socket is immediately writable
on Windows, and it caused rsync to fail because it made that assumption.
The only exception is when a SO_SNDTIMEO is specified which will EAGAIN.

Tests are added confirming MSG_WAITALL and MSG_NOSIGNAL work as expected
on all our supported OSes. Most of the platform-specific MSG_FOO magnums
have been deleted, with the exception of MSG_FASTOPEN. Your --strace log
will now show MSG_FOO flags as symbols rather than numbers.

I've also removed cv_wait_example_test because it's 0.3% flaky with Qemu
under system load since it depends on a process being readily scheduled.
  • Loading branch information
jart committed Sep 19, 2024
1 parent ce2fbf9 commit 87a6669
Show file tree
Hide file tree
Showing 41 changed files with 583 additions and 183 deletions.
6 changes: 4 additions & 2 deletions libc/intrin/describeflags.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ struct thatispacked DescribeFlags {
const char *name;
};

const char *_DescribeFlags(char *, size_t, const struct DescribeFlags *,
size_t, const char *, unsigned) libcesque;
const char *_DescribeFlags(char *, size_t, const struct DescribeFlags *, size_t,
const char *, unsigned) libcesque;

const char *_DescribeArchPrctlCode(char[12], int) libcesque;
const char *_DescribeCancelState(char[12], int, int *) libcesque;
Expand All @@ -27,6 +27,7 @@ const char *_DescribeItimer(char[12], int) libcesque;
const char *_DescribeMapFlags(char[64], int) libcesque;
const char *_DescribeMapping(char[8], int, int) libcesque;
const char *_DescribeMremapFlags(char[30], int) libcesque;
const char *_DescribeMsg(char[16], int) libcesque;
const char *_DescribeMsyncFlags(char[48], int) libcesque;
const char *_DescribeNtConsoleInFlags(char[256], uint32_t) libcesque;
const char *_DescribeNtConsoleOutFlags(char[128], uint32_t) libcesque;
Expand Down Expand Up @@ -84,6 +85,7 @@ const char *_DescribeWhichPrio(char[12], int) libcesque;
#define DescribeMapFlags(x) _DescribeMapFlags(alloca(64), x)
#define DescribeMapping(x, y) _DescribeMapping(alloca(8), x, y)
#define DescribeMremapFlags(x) _DescribeMremapFlags(alloca(30), x)
#define DescribeMsg(x) _DescribeMsg(alloca(16), x)
#define DescribeMsyncFlags(x) _DescribeMsyncFlags(alloca(48), x)
#define DescribeNtConsoleInFlags(x) _DescribeNtConsoleInFlags(alloca(256), x)
#define DescribeNtConsoleOutFlags(x) _DescribeNtConsoleOutFlags(alloca(128), x)
Expand Down
36 changes: 36 additions & 0 deletions libc/intrin/describemsg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*-*- 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/intrin/describeflags.h"
#include "libc/macros.h"
#include "libc/sysv/consts/msg.h"

const char *_DescribeMsg(char buf[16], int x) {
const struct DescribeFlags kMsgFlags[] = {
{MSG_FASTOPEN, "FASTOPEN"}, // order matters
{MSG_OOB, "OOB"}, //
{MSG_PEEK, "PEEK"}, //
{MSG_DONTROUTE, "DONTROUTE"}, //
{MSG_DONTWAIT, "DONTWAIT"}, //
{MSG_WAITALL, "WAITALL"}, //
{MSG_NOSIGNAL, "NOSIGNAL"}, //
{MSG_TRUNC, "TRUNC"}, //
{MSG_CTRUNC, "CTRUNC"}, //
};
return _DescribeFlags(buf, 16, kMsgFlags, ARRAYLEN(kMsgFlags), "MSG_", x);
}
2 changes: 1 addition & 1 deletion libc/sock/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ int sys_select_nt(int, fd_set *, fd_set *, fd_set *, struct timeval *,

size_t __iovec2nt(struct NtIovec[hasatleast 16], const struct iovec *, size_t);

ssize_t __winsock_block(int64_t, uint32_t, int, uint32_t, uint64_t,
ssize_t __winsock_block(int64_t, uint32_t, bool, uint32_t, uint64_t,
int (*)(int64_t, struct NtOverlapped *, uint32_t *,
void *),
void *);
Expand Down
33 changes: 24 additions & 9 deletions libc/sock/recv-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,26 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.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/struct/overlapped.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/fio.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#include "libc/vga/vga.internal.h"
#ifdef __x86_64__

#define _MSG_OOB 1
#define _MSG_PEEK 2
#define _MSG_WAITALL 8
#define _MSG_DONTWAIT 64

__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket;

struct RecvArgs {
const struct iovec *iov;
size_t iovlen;
Expand All @@ -54,13 +58,24 @@ textwindows ssize_t sys_recv_nt(int fd, const struct iovec *iov, size_t iovlen,
return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
bool nonblock = !(flags & _MSG_WAITALL) &&
((f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT));
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags, nonblock, f->rcvtimeo, m,
sys_recv_nt_start, &(struct RecvArgs){iov, iovlen});
__sig_unblock(m);
sigset_t waitmask = __sig_block();

// "Be aware that if the underlying transport provider does not
// support MSG_WAITALL, or if the socket is in a non-blocking mode,
// then this call will fail with WSAEOPNOTSUPP. Also, if MSG_WAITALL
// is specified along with MSG_OOB, MSG_PEEK, or MSG_PARTIAL, then
// this call will fail with WSAEOPNOTSUPP."
// —Quoth MSDN § WSARecv
if (flags & _MSG_WAITALL)
__imp_ioctlsocket(f->handle, FIONBIO, (uint32_t[]){0});

rc = __winsock_block(f->handle, flags & ~_MSG_DONTWAIT,
(f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT),
f->rcvtimeo, waitmask, sys_recv_nt_start,
&(struct RecvArgs){iov, iovlen});

__sig_unblock(waitmask);

return rc;
}

Expand Down
31 changes: 28 additions & 3 deletions libc/sock/recv.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,40 @@
#include "libc/calls/internal.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/strace.h"
#include "libc/sock/internal.h"
#include "libc/sock/sock.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/msg.h"
#include "libc/sysv/errfuns.h"

/**
* Receives data from network socket.
*
* Calling `recv(fd, p, n, 0)` is equivalent to `read(fd, p, n)`.
*
* Unlike files where the OS tries very hard to fulfill the entire
* requested `size` before returning, read operations on sockets aim to
* return as quickly as possible. For example, if 10 bytes are requested
* and a packet comes in with only 5 bytes, then recv() will most likely
* return those 5 bytes before waiting longer. The `MSG_WAITALL` flag
* may be passed when waiting longer is desired. In that case, short
* reads should only be possible when the connection status changes or
* the receive operation is interrupted by a signal.
*
* @param fd is the file descriptor returned by socket()
* @param buf is where received network data gets copied
* @param size is the byte capacity of buf
* @param flags can have `MSG_OOB`, `MSG_PEEK`, `MSG_DONTWAIT`, `MSG_WAITALL`
* @return number of bytes received, 0 on remote close, or -1 w/ errno
* @raise EINTR if signal handler was called instead
* @raise EINVAL if unknown bits were passed in `flags`
* @raise EINVAL if flag isn't supported by host operating system
* @raise EINVAL if `MSG_WAITALL` and `MSG_PEEK` were both passed
* @raise EBADF if `fd` is an invalid file descriptor
* @raise EAGAIN if `MSG_DONTWAIT` was passed and no data was available
* @raise EAGAIN if `O_NONBLOCK` is in play and no data was available
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
* @cancelationpoint
Expand All @@ -44,7 +64,11 @@ ssize_t recv(int fd, void *buf, size_t size, int flags) {
ssize_t rc;
BEGIN_CANCELATION_POINT;

if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
if ((flags & (MSG_WAITALL | MSG_PEEK)) == (MSG_WAITALL | MSG_PEEK)) {
// this is possible on some OSes like Linux but it breaks FreeBSD
// and Windows will raise EOPNOTSUPP when it gets passed together
return einval();
} else if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
rc = enotsock();
} else if (!IsWindows()) {
rc = sys_recvfrom(fd, buf, size, flags, 0, 0);
Expand All @@ -65,7 +89,8 @@ ssize_t recv(int fd, void *buf, size_t size, int flags) {
}

END_CANCELATION_POINT;
DATATRACE("recv(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc);
DATATRACE("recv(%d, [%#.*hhs%s], %'zu, %s) → %'ld% lm", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size,
DescribeMsg(flags), rc);
return rc;
}
11 changes: 5 additions & 6 deletions libc/sock/recvfrom-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,13 @@ textwindows ssize_t sys_recvfrom_nt(int fd, const struct iovec *iov,
return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT);
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags, nonblock, f->rcvtimeo, m,
sys_recvfrom_nt_start,
sigset_t waitmask = __sig_block();
rc = __winsock_block(f->handle, flags & ~_MSG_DONTWAIT,
(f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT),
f->rcvtimeo, waitmask, sys_recvfrom_nt_start,
&(struct RecvFromArgs){iov, iovlen, opt_out_srcaddr,
opt_inout_srcaddrsize});
__sig_unblock(m);
__sig_unblock(waitmask);
return rc;
}

Expand Down
9 changes: 7 additions & 2 deletions libc/sock/recvfrom.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/strace.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
Expand Down Expand Up @@ -95,7 +96,11 @@ ssize_t recvfrom(int fd, void *buf, size_t size, int flags,
}

END_CANCELATION_POINT;
DATATRACE("recvfrom(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc);
DATATRACE(
"recvfrom(%d, [%#.*hhs%s], %'zu, %s, %s) → %'ld% lm", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, DescribeMsg(flags),
DescribeSockaddr(opt_out_srcaddr,
opt_inout_srcaddrsize ? *opt_inout_srcaddrsize : 0),
rc);
return rc;
}
38 changes: 22 additions & 16 deletions libc/sock/send-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/intrin/fds.h"
#include "libc/errno.h"
#include "libc/nt/errors.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/vga/vga.internal.h"
#ifdef __x86_64__

#define _MSG_OOB 1
#define _MSG_DONTROUTE 4
#define _MSG_DONTWAIT 64
#define _MSG_NOSIGNAL 0x10000000

struct SendArgs {
const struct iovec *iov;
Expand All @@ -49,23 +54,24 @@ textwindows static int sys_send_nt_start(int64_t handle,

textwindows ssize_t sys_send_nt(int fd, const struct iovec *iov, size_t iovlen,
uint32_t flags) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE))
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE | _MSG_NOSIGNAL))
return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
sigset_t waitmask = __sig_block();

// we don't check O_NONBLOCK because we want to avoid needing to call
// WSAPoll() every time we write() to a non-blocking socket. WIN32 is
// unsafe at canceling socket sends. lots of code doesn't check write
// return status. good programs that sincerely want to avoid blocking
// on send() operations should have already called poll() beforehand.
bool nonblock = flags & _MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags & ~(_MSG_DONTWAIT | _MSG_NOSIGNAL),
false, f->sndtimeo, waitmask, sys_send_nt_start,
&(struct SendArgs){iov, iovlen});

__sig_unblock(waitmask);

if (rc == -1 && errno == WSAESHUTDOWN) { // ESHUTDOWN
errno = kNtErrorBrokenPipe; // EPIPE
if (!(flags & _MSG_NOSIGNAL))
__sig_raise(SIGPIPE, SI_KERNEL);
}

flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags, -nonblock, f->sndtimeo, m,
sys_send_nt_start, &(struct SendArgs){iov, iovlen});
__sig_unblock(m);
return rc;
}

Expand Down
5 changes: 3 additions & 2 deletions libc/sock/send.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/strace.h"
#include "libc/macros.h"
#include "libc/sock/internal.h"
Expand Down Expand Up @@ -78,7 +79,7 @@ ssize_t send(int fd, const void *buf, size_t size, int flags) {
}

END_CANCELATION_POINT;
DATATRACE("send(%d, %#.*hhs%s, %'zu, %#x) → %'ld% lm", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc);
DATATRACE("send(%d, %#.*hhs%s, %'zu, %s) → %'ld% lm", fd, MAX(0, MIN(40, rc)),
buf, rc > 40 ? "..." : "", size, DescribeMsg(flags), rc);
return rc;
}
30 changes: 22 additions & 8 deletions libc/sock/sendto-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,26 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/errno.h"
#include "libc/intrin/fds.h"
#include "libc/nt/errors.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__

#define _MSG_OOB 1
#define _MSG_DONTROUTE 4
#define _MSG_DONTWAIT 64
#define _MSG_NOSIGNAL 0x10000000

struct SendToArgs {
const struct iovec *iov;
Expand All @@ -52,17 +58,25 @@ static textwindows int sys_sendto_nt_start(int64_t handle,
textwindows ssize_t sys_sendto_nt(int fd, const struct iovec *iov,
size_t iovlen, uint32_t flags,
void *opt_in_addr, uint32_t in_addrsize) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE))
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE | _MSG_NOSIGNAL))
return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT);
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(
f->handle, flags, -nonblock, f->sndtimeo, m, sys_sendto_nt_start,
&(struct SendToArgs){iov, iovlen, opt_in_addr, in_addrsize});
__sig_unblock(m);
sigset_t waitmask = __sig_block();

rc = __winsock_block(f->handle, flags & ~(_MSG_DONTWAIT | _MSG_NOSIGNAL),
false, f->sndtimeo, waitmask, sys_sendto_nt_start,
&(struct SendToArgs){iov, iovlen, //
opt_in_addr, in_addrsize});

__sig_unblock(waitmask);

if (rc == -1 && errno == WSAESHUTDOWN) { // ESHUTDOWN
errno = kNtErrorBrokenPipe; // EPIPE
if (!(flags & _MSG_NOSIGNAL))
__sig_raise(SIGPIPE, SI_KERNEL);
}

return rc;
}

Expand Down
Loading

0 comments on commit 87a6669

Please sign in to comment.