Support non-blocking i/o across platforms

This change introduces new tests for `O_NONBLOCK` and `SOCK_NONBLOCK` to
confirm that non-blocking i/o is now working on all supported platforms,
including Windows. For example, you can now say on Windows, MacOS, etc.:

    socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

To create a non-blocking IPv4 TCP socket. Or you can enable non-blocking
i/o on an existing socket / pipe / etc. file descriptor by calling fcntl

    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);

This functionality is polyfilled on older Linux kernels too, e.g. RHEL5.
Now that fcntl() support is much better the FIOCLEX / FIONCLEX polyfills
for ioctl() have been removed since they're ugly non-POSIX diameond APIs

This change fixes a weakness in kprintf() that was causing Windows trace
tools to frequently crash.
This commit is contained in:
Justine Tunney 2023-07-23 02:56:47 -07:00
parent 5c9e03e3e0
commit 1d4eb08fa1
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
102 changed files with 678 additions and 331 deletions

View file

@ -36,10 +36,13 @@
#include "libc/nt/runtime.h"
#include "libc/nt/struct/byhandlefileinformation.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/stdckdint.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h"
#include "libc/sysv/consts/fio.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
@ -307,21 +310,49 @@ static textwindows int sys_fcntl_nt_dupfd(int fd, int cmd, int start) {
return sys_dup_nt(fd, -1, (cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0), start);
}
static textwindows int sys_fcntl_nt_setfl(int fd, unsigned *flags, unsigned arg,
unsigned supported) {
unsigned old, neu, changed, other, allowed;
old = *flags & supported;
other = *flags & ~supported;
neu = arg & supported;
changed = old ^ neu;
// you may change the following access mode flags:
//
// - O_NONBLOCK make read() raise EAGAIN
// - O_NDELAY same thing as O_NONBLOCK
//
allowed = O_NONBLOCK;
if (changed & ~allowed) {
// the following access mode flags are supported, but it's currently
// not possible to change them on windows.
//
// - O_APPEND tried to support but failed
// - O_RANDOM use posix_fadvise() instead
// - O_SEQUENTIAL use posix_fadvise() instead
// - O_DIRECT possibly in future?
// - O_DSYNC possibly in future?
// - O_RSYNC possibly in future?
// - O_SYNC possibly in future?
//
return enotsup();
}
// 1. ignore flags that aren't access mode flags
// 2. return zero if nothing's changed
*flags = other | neu;
return 0;
}
textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) {
int rc;
uint32_t flags;
int access_mode_flags = O_ACCMODE | O_APPEND | O_ASYNC | O_DIRECT |
O_NOATIME | O_NONBLOCK | O_RANDOM | O_SEQUENTIAL;
if (__isfdkind(fd, kFdFile) || __isfdkind(fd, kFdSocket)) {
if (cmd == F_GETFL) {
rc = g_fds.p[fd].flags &
(O_ACCMODE | O_APPEND | O_ASYNC | O_DIRECT | O_NOATIME | O_NONBLOCK |
O_RANDOM | O_SEQUENTIAL);
rc = g_fds.p[fd].flags & access_mode_flags;
} else if (cmd == F_SETFL) {
// O_APPEND doesn't appear to be tunable at cursory glance
// O_NONBLOCK might require we start doing all i/o in threads
// O_DSYNC / O_RSYNC / O_SYNC maybe if we fsync() everything
// O_DIRECT | O_RANDOM | O_SEQUENTIAL | O_NDELAY possible but
// not worth it.
rc = enosys();
rc = sys_fcntl_nt_setfl(fd, &g_fds.p[fd].flags, arg, access_mode_flags);
} else if (cmd == F_GETFD) {
if (g_fds.p[fd].flags & O_CLOEXEC) {
rc = FD_CLOEXEC;

View file

@ -26,9 +26,15 @@
// Applies file descriptor fixups on XNU or old Linux.
// See __fixupnewsockfd() for socket file descriptors.
int __fixupnewfd(int fd, int flags) {
int file_mode;
if (fd != -1) {
if (flags & O_CLOEXEC) {
_npassert(!__sys_fcntl(fd, F_SETFD, FD_CLOEXEC));
_unassert((file_mode = __sys_fcntl(fd, F_GETFD)) != -1);
_unassert(!__sys_fcntl(fd, F_SETFD, file_mode | FD_CLOEXEC));
}
if (flags & O_NONBLOCK) {
_unassert((file_mode = __sys_fcntl(fd, F_GETFL)) != -1);
_unassert(!__sys_fcntl(fd, F_SETFL, file_mode | O_NONBLOCK));
}
}
return fd;

View file

@ -76,6 +76,6 @@ int getdomainname(char *name, size_t len) {
if (!rc && len && !strcmp(name, "(none)")) {
name[0] = 0;
}
STRACE("getdomainname([%#.*s], %'zu) → %d% m", len, name, len, rc);
STRACE("getdomainname([%#.*s], %'zu) → %d% m", (int)len, name, len, rc);
return rc;
}

View file

@ -71,6 +71,6 @@ int gethostname(char *name, size_t len) {
} else {
rc = enosys();
}
STRACE("gethostname([%#.*s], %'zu) → %d% m", len, name, len, rc);
STRACE("gethostname([%#.*s], %'zu) → %d% m", (int)len, name, len, rc);
return rc;
}

View file

@ -558,34 +558,6 @@ static int ioctl_siocgifflags(int fd, void *arg) {
}
}
/**
* Sets "close on exec" on file descriptor the fast way.
*
* @see ioctl(fd, FIOCLEX, 0) dispatches here
*/
static int ioctl_fioclex(int fd, int req) {
int rc;
if (fd >= 0) {
if (IsWindows() || (fd < g_fds.n && g_fds.p[fd].kind == kFdZip)) {
if (__isfdopen(fd)) {
if (req == FIOCLEX) {
g_fds.p[fd].flags |= O_CLOEXEC;
} else {
g_fds.p[fd].flags &= ~O_CLOEXEC;
}
rc = 0;
} else {
rc = ebadf();
}
} else {
rc = sys_ioctl(fd, req);
}
} else {
rc = einval();
}
return rc;
}
/**
* Performs special i/o operation on file descriptor.
*
@ -618,17 +590,6 @@ static int ioctl_fioclex(int fd, int req) {
* - `TIOCNXCL` to give up exclusive mode on terminal. Only
* available on UNIX.
*
* - `FIOCLEX` sets the `O_CLOEXEC` state (no arg) noting that this
* polyfill may be removed in the future, and code should migrate
* to the equivalent fcntl() api.
*
* - `FIONBIO` sets the `O_NONBLOCK` state (arg is `int *enabled`)
* which is supported on Windows for sockets.
*
* - `FIONCLEX` clears the `O_CLOEXEC` state (no arg) noting that
* this polyfill may be removed in the future, and code should
* migrate to the equivalent fcntl() api.
*
* - `SIOCGIFCONF` takes an struct ifconf object of a given size,
* whose arg is `struct ifconf *`. It implements the Linux style
* and modifies the following:
@ -647,6 +608,9 @@ static int ioctl_fioclex(int fd, int req) {
* network broadcast addr. This data structure should be obtained
* by calling `SIOCGIFCONF`.
*
* - `FIONBIO` isn't polyfilled; use `fcntl(F_SETFL, O_NONBLOCK)`
* - `FIOCLEX` isn't polyfilled; use `fcntl(F_SETFD, FD_CLOEXEC)`
* - `FIONCLEX` isn't polyfilled; use `fcntl(F_SETFD, 0)`
* - `TCGETS` isn't polyfilled; use tcgetattr()
* - `TCSETS` isn't polyfilled; use tcsetattr()
* - `TCSETSW` isn't polyfilled; use tcsetattr()
@ -673,10 +637,6 @@ int ioctl(int fd, unsigned long request, ...) {
va_end(va);
if (request == FIONBIO) {
rc = ioctl_default(fd, request, arg);
} else if (request == FIOCLEX) {
rc = ioctl_fioclex(fd, request);
} else if (request == FIONCLEX) {
rc = ioctl_fioclex(fd, request);
} else if (request == TIOCGWINSZ) {
rc = tcgetwinsize(fd, arg);
} else if (request == TIOCSWINSZ) {

View file

@ -33,6 +33,7 @@
#include "libc/nt/runtime.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/synchronization.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
@ -51,6 +52,9 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
if (SleepEx(__SIG_POLLING_INTERVAL_MS, true) == kNtWaitIoCompletion) {
POLLTRACE("IOCP EINTR");
}
if (fd->flags & O_NONBLOCK) {
return eagain();
}
if (_check_interrupts(true, g_fds.p)) {
POLLTRACE("sys_read_nt interrupted");
return -1;

View file

@ -56,6 +56,6 @@ ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz) {
bytes = sys_readlinkat_nt(dirfd, path, buf, bufsiz);
}
STRACE("readlinkat(%s, %#s, [%#.*s]) → %d% m", DescribeDirfd(dirfd), path,
MAX(0, bytes), buf, bytes);
(int)MAX(0, bytes), buf, bytes);
return bytes;
}

View file

@ -42,6 +42,7 @@ unsigned __wincrash(struct NtExceptionPointers *ep) {
ucontext_t ctx;
struct CosmoTib *tib;
static bool noreentry;
noreentry = true;
if ((tib = __tls_enabled ? __get_tls_privileged() : 0)) {
if (~tib->tib_flags & TIB_FLAG_WINCRASHING) {

View file

@ -1,29 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/errno.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
/**
* Error return path for winsock wrappers.
*/
textwindows int64_t __winsockerr(void) {
errno = __dos2errno(WSAGetLastError());
return -1;
}