Fix bugs in poll(), select(), ppoll(), and pselect()

poll() and select() now delegate to ppoll() and pselect() for assurances
that both polyfill implementations are correct and well-tested. Poll now
polyfills XNU and BSD quirks re: the hanndling of POLLNVAL and the other
similar status flags. This change resolves a misunderstanding concerning
how select(exceptfds) is intended to map to POLPRI. We now use E2BIG for
bouncing requests that exceed the 64 handle limit on Windows. With pipes
and consoles on Windows our poll impl will now report POLLHUP correctly.

Issues with Windows path generation have been fixed. For example, it was
problematic on Windows to say: posix_spawn_file_actions_addchdir_np("/")
due to the need to un-UNC paths in some additional places. Calling fstat
on UNC style volume path handles will now work. posix_spawn now supports
simulating the opening of /dev/null and other special paths on Windows.

Cosmopolitan no longer defines epoll(). I think wepoll is a nice project
for using epoll() on Windows socket handles. However we need generalized
file descriptor support to make epoll() for Windows work well enough for
inclusion in a C library. It's also not worth having epoll() if we can't
get it to work on XNU and BSD OSes which provide different abstractions.
Even epoll() on Linux isn't that great of an abstraction since it's full
of footguns. Last time I tried to get it to be useful I had little luck.
Considering how long it took to get poll() and select() to be consistent
across platforms, we really have no business claiming to have epoll too.
While it'd be nice to have fully implemented, the only software that use
epoll() are event i/o libraries used by things like nodejs. Event i/o is
not the best paradigm for handling i/o; threads make so much more sense.
This commit is contained in:
Justine Tunney 2024-09-01 19:29:47 -07:00
parent 39e7f24947
commit 2ec413b5a9
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
27 changed files with 664 additions and 2132 deletions

View file

@ -86,147 +86,281 @@
// performance is critical.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <unistd.h>
#define max(X, Y) ((Y) < (X) ? (X) : (Y))
#define USE_SELECT 0 // want poll() or select()? they both work great
#define PIPE_READ 0
#define PIPE_WRITE 1
int main() {
pid_t pid;
int status, ret;
posix_spawnattr_t attr;
posix_spawn_file_actions_t actions;
char *const argv[] = {"ls", "--dired", NULL};
int pipe_stdout[2], pipe_stderr[2];
errno_t err;
// Initialize file actions
ret = posix_spawnattr_init(&attr);
if (ret != 0) {
fprintf(stderr, "posix_spawnattr_init failed: %s\n", strerror(ret));
return 1;
// Create spawn attributes object.
posix_spawnattr_t attr;
err = posix_spawnattr_init(&attr);
if (err != 0) {
fprintf(stderr, "posix_spawnattr_init failed: %s\n", strerror(err));
exit(1);
}
// Explicitly request vfork() from posix_spawn() implementation
// Explicitly request vfork() from posix_spawn() implementation.
//
// This is currently the default for Cosmopolitan Libc, however you
// may want to set this anyway, for portability with other platforms.
// Please note that vfork() isn't officially specified by POSIX, so
// portable code may want to omit this and just use the default.
ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
if (ret != 0) {
fprintf(stderr, "posix_spawnattr_setflags failed: %s\n", strerror(ret));
return 1;
err = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
if (err != 0) {
fprintf(stderr, "posix_spawnattr_setflags: %s\n", strerror(err));
exit(2);
}
// Initialize file actions
ret = posix_spawn_file_actions_init(&actions);
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_init failed: %s\n",
strerror(ret));
return 1;
// Create file actions object.
posix_spawn_file_actions_t actions;
err = posix_spawn_file_actions_init(&actions);
if (err != 0) {
fprintf(stderr, "posix_spawn_file_actions_init: %s\n", strerror(err));
exit(3);
}
// Change directory to $HOME
ret = posix_spawn_file_actions_addchdir_np(&actions, getenv("HOME"));
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_addchdir_np failed: %s\n",
strerror(ret));
return 1;
// Change directory to root directory in child process.
err = posix_spawn_file_actions_addchdir_np(&actions, "/");
if (err != 0) {
fprintf(stderr, "posix_spawn_file_actions_addchdir_np: %s\n",
strerror(err));
exit(4);
}
// Create pipes for stdout and stderr
if (pipe(pipe_stdout) == -1 || pipe(pipe_stderr) == -1) {
// Disable stdin in child process.
//
// By default, if you launch this example in your terminal, then child
// processes can read from your teletypewriter's keyboard too. You can
// avoid this by assigning /dev/null to standard input so if the child
// tries to read input, read() will return zero, indicating eof.
if ((err = posix_spawn_file_actions_addopen(&actions, STDIN_FILENO,
"/dev/null", O_RDONLY, 0644))) {
fprintf(stderr, "posix_spawn_file_actions_addopen: %s\n", strerror(err));
exit(5);
}
// Create pipes for stdout and stderr.
//
// Using O_DIRECT puts the pipe in message mode. This way we have some
// visibility into how the child process is using write(). It can also
// help ensure that logged lines won't be chopped up here, which could
// happen more frequently on platforms like Windows, which is somewhat
// less sophisticated than Linux with how it performs buffering.
//
// You can also specify O_CLOEXEC, which is a nice touch that lets you
// avoid needing to call posix_spawn_file_actions_addclose() later on.
// That's because all file descriptors are inherited by child programs
// by default. This is even the case with Cosmopolitan Libc on Windows
//
// XXX: We assume that stdin/stdout/stderr exist in this process. It's
// possible for a rogue parent process to launch this example, in
// a way where the following spawn logic will break.
int pipe_stdout[2];
int pipe_stderr[2];
if (pipe2(pipe_stdout, O_DIRECT) == -1 ||
pipe2(pipe_stderr, O_DIRECT) == -1) {
perror("pipe");
return 1;
exit(6);
}
// Redirect child's stdout to pipe
ret = posix_spawn_file_actions_adddup2(&actions, pipe_stdout[PIPE_WRITE],
STDOUT_FILENO);
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_adddup2 (stdout) failed: %s\n",
strerror(ret));
return 1;
// Redirect child's stdout/stderr to pipes
if ((err = posix_spawn_file_actions_adddup2(&actions, pipe_stdout[PIPE_WRITE],
STDOUT_FILENO)) ||
(err = posix_spawn_file_actions_adddup2(&actions, pipe_stderr[PIPE_WRITE],
STDERR_FILENO))) {
fprintf(stderr, "posix_spawn_file_actions_adddup2: %s\n", strerror(err));
exit(7);
}
// Redirect child's stderr to pipe
ret = posix_spawn_file_actions_adddup2(&actions, pipe_stderr[PIPE_WRITE],
STDERR_FILENO);
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_adddup2 (stderr) failed: %s\n",
strerror(ret));
return 1;
}
// Close unwanted write ends of pipes in the child process
if ((err = posix_spawn_file_actions_addclose(&actions,
pipe_stdout[PIPE_READ])) ||
(err = posix_spawn_file_actions_addclose(&actions,
pipe_stderr[PIPE_READ]))) {
fprintf(stderr, "posix_spawn_file_actions_addclose: %s\n", strerror(err));
exit(8);
};
// Close unused write ends of pipes in the child process
ret = posix_spawn_file_actions_addclose(&actions, pipe_stdout[PIPE_READ]);
if (ret != 0) {
fprintf(stderr,
"posix_spawn_file_actions_addclose (stdout read) failed: %s\n",
strerror(ret));
return 1;
}
ret = posix_spawn_file_actions_addclose(&actions, pipe_stderr[PIPE_READ]);
if (ret != 0) {
fprintf(stderr,
"posix_spawn_file_actions_addclose (stderr read) failed: %s\n",
strerror(ret));
return 1;
}
// Spawn the child process
ret = posix_spawnp(&pid, "ls", &actions, NULL, argv, NULL);
if (ret != 0) {
fprintf(stderr, "posix_spawn failed: %s\n", strerror(ret));
return 1;
// Asynchronously launch the child process.
pid_t pid;
char *const argv[] = {"ls", "--dired", NULL};
printf("** Launching `ls --dired` in root directory\n");
err = posix_spawnp(&pid, argv[0], &actions, NULL, argv, NULL);
if (err) {
fprintf(stderr, "posix_spawn: %s\n", strerror(err));
exit(9);
}
// Close unused write ends of pipes in the parent process
close(pipe_stdout[PIPE_WRITE]);
close(pipe_stderr[PIPE_WRITE]);
// Read and print output from child process
char buffer[4096];
ssize_t bytes_read;
printf("Stdout from child process:\n");
while ((bytes_read = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer))) >
0) {
write(STDOUT_FILENO, buffer, bytes_read);
// we need poll() or select() because we're multiplexing output
// both poll() and select() work across all supported platforms
#if USE_SELECT
// Relay output from child process using select()
char buffer[512];
ssize_t got_stdout = 1;
ssize_t got_stderr = 1;
while (got_stdout > 0 || got_stderr > 0) {
fd_set rfds;
FD_ZERO(&rfds);
if (got_stdout > 0)
FD_SET(pipe_stdout[PIPE_READ], &rfds);
if (got_stderr > 0)
FD_SET(pipe_stderr[PIPE_READ], &rfds);
int nfds = max(pipe_stdout[PIPE_READ], pipe_stderr[PIPE_READ]) + 1;
if (select(nfds, &rfds, 0, 0, 0) == -1) {
perror("select");
exit(10);
}
if (FD_ISSET(pipe_stdout[PIPE_READ], &rfds)) {
got_stdout = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer));
printf("\n");
if (got_stdout > 0) {
printf("** Got stdout from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stdout);
} else if (!got_stdout) {
printf("** Got stdout EOF from child process\n");
} else {
printf("** Got stdout read() error from child process: %s\n",
strerror(errno));
}
}
if (FD_ISSET(pipe_stderr[PIPE_READ], &rfds)) {
got_stderr = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer));
printf("\n");
if (got_stderr > 0) {
printf("** Got stderr from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stderr);
} else if (!got_stderr) {
printf("** Got stderr EOF from child process\n");
} else {
printf("** Got stderr read() error from child process: %s\n",
strerror(errno));
}
}
}
printf("\nStderr from child process:\n");
while ((bytes_read = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer))) >
0) {
write(STDERR_FILENO, buffer, bytes_read);
#else
// Relay output from child process using poll()
char buffer[512];
ssize_t got_stdout = 1;
ssize_t got_stderr = 1;
while (got_stdout > 0 || got_stderr > 0) {
struct pollfd fds[2];
fds[0].fd = got_stdout > 0 ? pipe_stdout[PIPE_READ] : -1;
fds[0].events = POLLIN; // POLLHUP, POLLNVAL, and POLLERR are implied
fds[1].fd = got_stderr > 0 ? pipe_stderr[PIPE_READ] : -1;
fds[1].events = POLLIN; // POLLHUP, POLLNVAL, and POLLERR are implied
if (poll(fds, 2, -1) == -1) {
perror("select");
exit(10);
}
if (fds[0].revents) {
printf("\n");
if (fds[0].revents & POLLIN)
printf("** Got POLLIN on stdout from child process\n");
if (fds[0].revents & POLLHUP)
printf("** Got POLLHUP on stdout from child process\n");
if (fds[0].revents & POLLERR)
printf("** Got POLLERR on stdout from child process\n");
if (fds[0].revents & POLLNVAL)
printf("** Got POLLNVAL on stdout from child process\n");
got_stdout = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer));
if (got_stdout > 0) {
printf("** Got stdout from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stdout);
} else if (!got_stdout) {
printf("** Got stdout EOF from child process\n");
} else {
printf("** Got stdout read() error from child process: %s\n",
strerror(errno));
}
}
if (fds[1].revents) {
printf("\n");
if (fds[1].revents & POLLIN)
printf("** Got POLLIN on stderr from child process\n");
if (fds[1].revents & POLLHUP)
printf("** Got POLLHUP on stderr from child process\n");
if (fds[1].revents & POLLERR)
printf("** Got POLLERR on stderr from child process\n");
if (fds[1].revents & POLLNVAL)
printf("** Got POLLNVAL on stderr from child process\n");
got_stderr = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer));
if (got_stderr > 0) {
printf("** Got stderr from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stderr);
} else if (!got_stderr) {
printf("** Got stderr EOF from child process\n");
} else {
printf("** Got stderr read() error from child process: %s\n",
strerror(errno));
}
}
}
#endif
// Wait for the child process to complete
if (waitpid(pid, &status, 0) == -1) {
// Wait for child process to die.
int wait_status;
if (waitpid(pid, &wait_status, 0) == -1) {
perror("waitpid");
return 1;
exit(11);
}
// Clean up
// Clean up resources.
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attr);
close(pipe_stdout[PIPE_READ]);
close(pipe_stderr[PIPE_READ]);
if (WIFEXITED(status)) {
printf("Child process exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process terminated with signal %s\n",
strsignal(WTERMSIG(status)));
// Report wait status.
//
// When a process dies, it's almost always due to calling _Exit() or
// being killed due to an unhandled signal. On both UNIX and Windows
// this information will be propagated to the parent. That status is
// able to be propagated to the parent of this process too.
printf("\n");
if (WIFEXITED(wait_status)) {
printf("** Child process exited with exit code %d\n",
WEXITSTATUS(wait_status));
exit(WEXITSTATUS(wait_status));
} else if (WIFSIGNALED(wait_status)) {
printf("** Child process terminated with signal %s\n",
strsignal(WTERMSIG(wait_status)));
fflush(stdout);
sigset_t sm;
sigemptyset(&sm);
sigaddset(&sm, WTERMSIG(wait_status));
sigprocmask(SIG_UNBLOCK, &sm, 0);
signal(SIGABRT, SIG_DFL);
raise(WTERMSIG(wait_status));
exit(128 + WTERMSIG(wait_status));
} else {
printf("Child process did not exit normally\n");
printf("** Child process exited weirdly with wait status 0x%08x\n",
wait_status);
exit(12);
}
return 0;
}

View file

@ -52,11 +52,6 @@ textwindows int sys_close_nt(int fd, int fildes) {
FlushFileBuffers(f->handle);
}
break;
case kFdEpoll:
if (_weaken(sys_close_epoll_nt)) {
return _weaken(sys_close_epoll_nt)(fd);
}
break;
case kFdSocket:
if (_weaken(sys_closesocket_nt)) {
return _weaken(sys_closesocket_nt)(g_fds.p + fd);

View file

@ -20,12 +20,12 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/state.internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
@ -74,7 +74,6 @@ static int close_impl(int fd) {
* - openat()
* - socket()
* - accept()
* - epoll_create()
* - landlock_create_ruleset()
*
* This function should never be reattempted if an error is returned;

View file

@ -19,13 +19,13 @@
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/fmt/wintime.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsr.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/strace.h"
#include "libc/macros.h"
#include "libc/mem/alloca.h"
@ -119,7 +119,6 @@ textwindows int sys_fstat_nt_handle(int64_t handle, const char16_t *path,
// Always set st_blksize to avoid divide by zero issues.
// The Linux kernel sets this for /dev/tty and similar too.
// TODO(jart): GetVolumeInformationByHandle?
st.st_blksize = 4096;
st.st_gid = st.st_uid = sys_getuid_nt();
@ -141,59 +140,66 @@ textwindows int sys_fstat_nt_handle(int64_t handle, const char16_t *path,
break;
case kNtFileTypeDisk: {
struct NtByHandleFileInformation wst;
if (!GetFileInformationByHandle(handle, &wst)) {
return __winerr();
}
st.st_mode = 0444 & ~umask;
if ((wst.dwFileAttributes & kNtFileAttributeDirectory) ||
IsWindowsExecutable(handle, path)) {
st.st_mode |= 0111 & ~umask;
}
st.st_flags = wst.dwFileAttributes;
if (!(wst.dwFileAttributes & kNtFileAttributeReadonly)) {
st.st_mode |= 0222 & ~umask;
}
if (wst.dwFileAttributes & kNtFileAttributeReparsePoint) {
st.st_mode |= S_IFLNK;
} else if (wst.dwFileAttributes & kNtFileAttributeDirectory) {
st.st_mode |= S_IFDIR;
} else {
st.st_mode |= S_IFREG;
}
st.st_atim = FileTimeToTimeSpec(wst.ftLastAccessFileTime);
st.st_mtim = FileTimeToTimeSpec(wst.ftLastWriteFileTime);
st.st_birthtim = FileTimeToTimeSpec(wst.ftCreationFileTime);
// compute time of last status change
if (timespec_cmp(st.st_atim, st.st_mtim) > 0) {
st.st_ctim = st.st_atim;
} else {
st.st_ctim = st.st_mtim;
}
st.st_size = (wst.nFileSizeHigh + 0ull) << 32 | wst.nFileSizeLow;
st.st_dev = wst.dwVolumeSerialNumber;
st.st_ino = (wst.nFileIndexHigh + 0ull) << 32 | wst.nFileIndexLow;
st.st_nlink = wst.nNumberOfLinks;
if (S_ISLNK(st.st_mode)) {
if (!st.st_size) {
long size = GetSizeOfReparsePoint(handle);
if (size == -1)
return -1;
st.st_size = size;
if (GetFileInformationByHandle(handle, &wst)) {
st.st_mode = 0444 & ~umask;
if ((wst.dwFileAttributes & kNtFileAttributeDirectory) ||
IsWindowsExecutable(handle, path)) {
st.st_mode |= 0111 & ~umask;
}
} else {
// st_size = uncompressed size
// st_blocks*512 = physical size
uint64_t physicalsize;
struct NtFileCompressionInfo fci;
if (!(wst.dwFileAttributes &
(kNtFileAttributeDirectory | kNtFileAttributeReparsePoint)) &&
GetFileInformationByHandleEx(handle, kNtFileCompressionInfo, &fci,
sizeof(fci))) {
physicalsize = fci.CompressedFileSize;
st.st_flags = wst.dwFileAttributes;
if (!(wst.dwFileAttributes & kNtFileAttributeReadonly)) {
st.st_mode |= 0222 & ~umask;
}
if (wst.dwFileAttributes & kNtFileAttributeReparsePoint) {
st.st_mode |= S_IFLNK;
} else if (wst.dwFileAttributes & kNtFileAttributeDirectory) {
st.st_mode |= S_IFDIR;
} else {
physicalsize = st.st_size;
st.st_mode |= S_IFREG;
}
st.st_blocks = ROUNDUP(physicalsize, st.st_blksize) / 512;
st.st_atim = FileTimeToTimeSpec(wst.ftLastAccessFileTime);
st.st_mtim = FileTimeToTimeSpec(wst.ftLastWriteFileTime);
st.st_birthtim = FileTimeToTimeSpec(wst.ftCreationFileTime);
// compute time of last status change
if (timespec_cmp(st.st_atim, st.st_mtim) > 0) {
st.st_ctim = st.st_atim;
} else {
st.st_ctim = st.st_mtim;
}
st.st_size = (wst.nFileSizeHigh + 0ull) << 32 | wst.nFileSizeLow;
st.st_dev = wst.dwVolumeSerialNumber;
st.st_ino = (wst.nFileIndexHigh + 0ull) << 32 | wst.nFileIndexLow;
st.st_nlink = wst.nNumberOfLinks;
if (S_ISLNK(st.st_mode)) {
if (!st.st_size) {
long size = GetSizeOfReparsePoint(handle);
if (size == -1)
return -1;
st.st_size = size;
}
} else {
// st_size = uncompressed size
// st_blocks*512 = physical size
uint64_t physicalsize;
struct NtFileCompressionInfo fci;
if (!(wst.dwFileAttributes &
(kNtFileAttributeDirectory | kNtFileAttributeReparsePoint)) &&
GetFileInformationByHandleEx(handle, kNtFileCompressionInfo, &fci,
sizeof(fci))) {
physicalsize = fci.CompressedFileSize;
} else {
physicalsize = st.st_size;
}
st.st_blocks = ROUNDUP(physicalsize, st.st_blksize) / 512;
}
} else if (GetVolumeInformationByHandle(
handle, 0, 0, &wst.dwVolumeSerialNumber, 0, 0, 0, 0)) {
st.st_dev = wst.dwVolumeSerialNumber;
st.st_mode = S_IFDIR | (0444 & ~umask);
} else {
// both GetFileInformationByHandle and
// GetVolumeInformationByHandle failed
return __winerr();
}
break;
}

View file

@ -62,8 +62,18 @@ static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
dir[dirlen] = u'\\';
memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t));
memcpy(file, dir, ((n = dirlen + 1 + filelen) + 1) * sizeof(char16_t));
int res = __normntpath(file, n);
return res;
n = __normntpath(file, n);
// UNC paths break some things when they are not needed.
if (n > 4 && n < 260 && //
file[0] == '\\' && //
file[1] == '\\' && //
file[2] == '?' && //
file[3] == '\\') {
memmove(file, file + 4, (n - 4 + 1) * sizeof(char16_t));
}
return n;
} else {
return filelen;
}

View file

@ -56,6 +56,19 @@
#define POLL_INTERVAL_MS 10
// <sync libc/sysv/consts.sh>
#define POLLERR_ 0x0001 // implied in events
#define POLLHUP_ 0x0002 // implied in events
#define POLLNVAL_ 0x0004 // implied in events
#define POLLIN_ 0x0300
#define POLLRDNORM_ 0x0100
#define POLLRDBAND_ 0x0200
#define POLLOUT_ 0x0010
#define POLLWRNORM_ 0x0010
#define POLLWRBAND_ 0x0020 // MSDN undocumented
#define POLLPRI_ 0x0400 // MSDN unsupported
// </sync libc/sysv/consts.sh>
// Polls on the New Technology.
//
// This function is used to implement poll() and select(). You may poll
@ -86,16 +99,16 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
if (__isfdopen(fds[i].fd)) {
if (__isfdkind(fds[i].fd, kFdSocket)) {
if (sn < ARRAYLEN(sockfds)) {
// the magnums for POLLIN/OUT/PRI on NT include the other ones too
// we need to clear ones like POLLNVAL or else WSAPoll shall whine
// WSAPoll whines if we pass POLLNVAL, POLLHUP, or POLLERR.
sockindices[sn] = i;
sockfds[sn].handle = g_fds.p[fds[i].fd].handle;
sockfds[sn].events = fds[i].events & (POLLPRI | POLLIN | POLLOUT);
sockfds[sn].events =
fds[i].events & (POLLRDNORM_ | POLLRDBAND_ | POLLWRNORM_);
sockfds[sn].revents = 0;
++sn;
} else {
// too many socket fds
rc = enomem();
rc = e2big();
break;
}
} else if (pn < ARRAYLEN(pipefds)) {
@ -105,13 +118,13 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
pipefds[pn].revents = 0;
switch (g_fds.p[fds[i].fd].flags & O_ACCMODE) {
case O_RDONLY:
pipefds[pn].events = fds[i].events & POLLIN;
pipefds[pn].events = fds[i].events & POLLIN_;
break;
case O_WRONLY:
pipefds[pn].events = fds[i].events & POLLOUT;
pipefds[pn].events = fds[i].events & POLLOUT_;
break;
case O_RDWR:
pipefds[pn].events = fds[i].events & (POLLIN | POLLOUT);
pipefds[pn].events = fds[i].events & (POLLIN_ | POLLOUT_);
break;
default:
break;
@ -119,7 +132,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
++pn;
} else {
// too many non-socket fds
rc = enomem();
rc = e2big();
break;
}
} else {
@ -127,52 +140,60 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
}
}
__fds_unlock();
if (rc) {
if (rc)
// failed to create a polling solution
return rc;
}
// perform the i/o and sleeping and looping
for (;;) {
// see if input is available on non-sockets
for (gotpipes = i = 0; i < pn; ++i) {
if (pipefds[i].events & POLLOUT) {
if (pipefds[i].events & POLLWRNORM_)
// we have no way of polling if a non-socket is writeable yet
// therefore we assume that if it can happen, it shall happen
pipefds[i].revents |= POLLOUT;
}
if (pipefds[i].events & POLLIN) {
if (GetFileType(pipefds[i].handle) == kNtFileTypePipe) {
ok = PeekNamedPipe(pipefds[i].handle, 0, 0, 0, &avail, 0);
POLLTRACE("PeekNamedPipe(%ld, 0, 0, 0, [%'u], 0) → %hhhd% m",
pipefds[i].handle, avail, ok);
if (ok) {
if (avail) {
pipefds[i].revents |= POLLIN;
}
} else {
pipefds[i].revents |= POLLERR;
}
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
if (CountConsoleInputBytes()) {
pipefds[i].revents |= POLLIN; // both >0 and -1 (eof) are pollin
}
pipefds[i].revents |= POLLWRNORM_;
if (GetFileType(pipefds[i].handle) == kNtFileTypePipe) {
ok = PeekNamedPipe(pipefds[i].handle, 0, 0, 0, &avail, 0);
POLLTRACE("PeekNamedPipe(%ld, 0, 0, 0, [%'u], 0) → {%hhhd, %d}",
pipefds[i].handle, avail, ok, GetLastError());
if (ok) {
if (avail)
pipefds[i].revents |= POLLRDNORM_;
} else if (GetLastError() == kNtErrorHandleEof ||
GetLastError() == kNtErrorBrokenPipe) {
pipefds[i].revents &= ~POLLWRNORM_;
pipefds[i].revents |= POLLHUP_;
} else {
// we have no way of polling if a non-socket is readable yet
// therefore we assume that if it can happen it shall happen
pipefds[i].revents |= POLLIN;
pipefds[i].revents &= ~POLLWRNORM_;
pipefds[i].revents |= POLLERR_;
}
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
switch (CountConsoleInputBytes()) {
case 0:
break;
case -1:
pipefds[i].revents &= ~POLLWRNORM_;
pipefds[i].revents |= POLLHUP_;
break;
default:
pipefds[i].revents |= POLLRDNORM_;
break;
}
} else {
// we have no way of polling if a non-socket is readable yet
// therefore we assume that if it can happen it shall happen
pipefds[i].revents |= POLLRDNORM_;
}
if (pipefds[i].revents) {
if (!(pipefds[i].events & POLLRDNORM_))
pipefds[i].revents &= ~POLLRDNORM_;
if (pipefds[i].revents)
++gotpipes;
}
}
// if we haven't found any good results yet then here we
// compute a small time slice we don't mind sleeping for
if (sn) {
if ((gotsocks = WSAPoll(sockfds, sn, 0)) == -1) {
if ((gotsocks = WSAPoll(sockfds, sn, 0)) == -1)
return __winsockerr();
}
} else {
gotsocks = 0;
}
@ -190,18 +211,16 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
if (waitfor) {
POLLTRACE("poll() sleeping for %'d out of %'lu ms", waitfor,
timespec_tomillis(remain));
if ((rc = _park_norestart(waitfor, sigmask)) == -1) {
if ((rc = _park_norestart(waitfor, sigmask)) == -1)
return -1; // eintr, ecanceled, etc.
}
}
}
}
// we gave all the sockets and all the named pipes a shot
// if we found anything at all then it's time to end work
if (gotinvals || gotpipes || gotsocks || !waitfor) {
if (gotinvals || gotpipes || gotsocks || !waitfor)
break;
}
}
// the system call is going to succeed
@ -210,15 +229,13 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
if (fds[i].fd < 0 || __isfdopen(fds[i].fd)) {
fds[i].revents = 0;
} else {
fds[i].revents = POLLNVAL;
fds[i].revents = POLLNVAL_;
}
}
for (i = 0; i < pn; ++i) {
for (i = 0; i < pn; ++i)
fds[pipeindices[i]].revents = pipefds[i].revents;
}
for (i = 0; i < sn; ++i) {
for (i = 0; i < sn; ++i)
fds[sockindices[i]].revents = sockfds[i].revents;
}
// and finally return
return gotinvals + gotpipes + gotsocks;

View file

@ -16,46 +16,50 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/cp.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.h"
#include "libc/calls/struct/timespec.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/pollfd.internal.h"
#include "libc/stdckdint.h"
#include "libc/sysv/errfuns.h"
/**
* Waits for something to happen on multiple file descriptors at once.
* Checks status on multiple file descriptors at once.
*
* Warning: XNU has an inconsistency with other platforms. If you have
* pollfds with fd0 and none of the meaningful events flags are added
* e.g. POLLIN then XNU won't check for POLLNVAL. This matters because
* one of the use-cases for poll() is quickly checking for open files.
* Servers that need to handle an unbounded number of client connections
* should just create a separate thread for each client. poll() isn't a
* scalable i/o solution on any platform.
*
* Note: Polling works best on Windows for sockets. We're able to poll
* input on named pipes. But for anything that isn't a socket, or pipe
* with POLLIN, (e.g. regular file) then POLLIN/POLLOUT are always set
* into revents if they're requested, provided they were opened with a
* mode that permits reading and/or writing.
* 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 64 pipes/terminals at the same time. Furthermore,
* elements whose fd field is set to a negative number are ignored and
* will not count against this limit.
*
* Note: Windows has a limit of 64 file descriptors and ENOMEM with -1
* is returned if that limit is exceeded. In practice the limit is not
* this low. For example, pollfds with fd<0 don't count. So the caller
* could flip the sign bit with a short timeout, to poll a larger set.
* One of the use cases for poll() is to quickly check if a number of
* file descriptors are valid. The canonical way to do this is to set
* events to 0 which prevents blocking and causes only the invalid,
* hangup, and error statuses to be checked.
*
* On XNU, the POLLHUP and POLLERR statuses aren't checked unless either
* POLLIN, POLLOUT, or POLLPRI are specified in the events field. Cosmo
* will however polyfill the checking of POLLNVAL on XNU with the events
* doesn't specify any of the above i/o events.
*
* 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.
*
* @param fds[𝑖].fd should be a socket, input pipe, or conosle input
* and if it's a negative number then the entry is ignored
* and if it's a negative number then the entry is ignored, plus
* revents will be set to zero
* @param fds[𝑖].events flags can have POLLIN, POLLOUT, POLLPRI,
* POLLRDNORM, POLLWRNORM, POLLRDBAND, POLLWRBAND as well as
* POLLERR, POLLHUP, and POLLNVAL although the latter are
* always implied (assuming fd0) so they're ignored here
* @param timeout_ms if 0 means don't wait and -1 means wait forever
* @return number of items fds whose revents field has been set to
* nonzero to describe its events, or 0 if the timeout elapsed,
* or -1 w/ errno
* @param timeout_ms if 0 means don't wait and negative waits forever
* @return number of `fds` whose revents field has been set to a nonzero
* number, 0 if the timeout elapsed without events, or -1 w/ errno
* @return fds[𝑖].revents is always zero initializaed and then will
* be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something
* was determined about the file descriptor
* @raise E2BIG if we exceeded the 64 socket limit on Windows
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @cancelationpoint
@ -63,22 +67,14 @@
* @norestart
*/
int poll(struct pollfd *fds, size_t nfds, int timeout_ms) {
int rc;
BEGIN_CANCELATION_POINT;
if (!IsWindows()) {
if (!IsMetal()) {
rc = sys_poll(fds, nfds, timeout_ms);
} else {
rc = sys_poll_metal(fds, nfds, timeout_ms);
}
struct timespec ts;
struct timespec *tsp;
if (timeout_ms >= 0) {
ts.tv_sec = timeout_ms / 1000;
ts.tv_nsec = timeout_ms % 1000 * 1000000;
tsp = &ts;
} else {
uint32_t ms = timeout_ms >= 0 ? timeout_ms : -1u;
rc = sys_poll_nt(fds, nfds, &ms, 0);
tsp = 0;
}
END_CANCELATION_POINT;
STRACE("poll(%s, %'zu, %'d) → %d% lm", DescribePollFds(rc, fds, nfds), nfds,
timeout_ms, rc);
return rc;
return ppoll(fds, nfds, tsp, 0);
}

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
@ -24,14 +25,18 @@
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/strace.h"
#include "libc/runtime/stack.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/pollfd.internal.h"
#include "libc/stdckdint.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
/**
* Waits for something to happen on multiple file descriptors at once.
* Checks status on multiple file descriptors at once.
*
* This function is the same as saying:
*
@ -41,16 +46,51 @@
* sigprocmask(SIG_SETMASK, old, 0);
*
* Except it happens atomically when the kernel supports doing that. On
* kernel such as XNU and NetBSD which don't, this wrapper will fall
* back to using the example above. Consider using pselect() which is
* atomic on all supported platforms.
* kernels such as XNU and NetBSD which don't, this wrapper will fall
* back to using the example above. If you need ironclad assurances of
* signal mask atomicity, then consider using pselect() which Cosmo Libc
* guarantees to be atomic on all supported platforms.
*
* The Linux Kernel modifies the timeout parameter. This wrapper gives
* it a local variable due to POSIX requiring that `timeout` be const.
* If you need that information from the Linux Kernel use sys_ppoll().
* Servers that need to handle an unbounded number of client connections
* should just create a separate thread for each client. poll(), ppoll()
* and select() aren't scalable i/o solutions on any platform.
*
* On Windows it's only possible to poll 64 file descriptors at a time;
* it's a limitation imposed by WSAPoll(). Cosmopolitan Libc's ppoll()
* polyfill can go higher in some cases; for example, It's possible to
* poll 64 sockets and 64 pipes/terminals at the same time. Furthermore,
* elements whose fd field is set to a negative number are ignored and
* will not count against this limit.
*
* One of the use cases for poll() is to quickly check if a number of
* file descriptors are valid. The canonical way to do this is to set
* events to 0 which prevents blocking and causes only the invalid,
* hangup, and error statuses to be checked.
*
* On XNU, the POLLHUP and POLLERR statuses aren't checked unless either
* POLLIN, POLLOUT, or POLLPRI are specified in the events field. Cosmo
* will however polyfill the checking of POLLNVAL on XNU with the events
* doesn't specify any of the above i/o events.
*
* 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.
*
* @param fds[𝑖].fd should be a socket, input pipe, or conosle input
* and if it's a negative number then the entry is ignored, plus
* revents will be set to zero
* @param fds[𝑖].events flags can have POLLIN, POLLOUT, POLLPRI,
* POLLRDNORM, POLLWRNORM, POLLRDBAND, POLLWRBAND as well as
* POLLERR, POLLHUP, and POLLNVAL although the latter are
* always implied (assuming fd0) so they're ignored here
* @param timeout_ms if 0 means don't wait and negative waits forever
* @return number of `fds` whose revents field has been set to a nonzero
* number, 0 if the timeout elapsed without events, or -1 w/ errno
* @return fds[𝑖].revents is always zero initializaed and then will
* be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something
* was determined about the file descriptor
* @param timeout if null will block indefinitely
* @param sigmask may be null in which case no mask change happens
* @raise E2BIG if we exceeded the 64 socket limit on Windows
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @cancelationpoint
@ -59,11 +99,32 @@
*/
int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
const sigset_t *sigmask) {
int e, rc;
int e, fdcount;
sigset_t oldmask;
struct timespec ts, *tsp;
BEGIN_CANCELATION_POINT;
// The OpenBSD poll() man pages claims it'll ignore POLLERR, POLLHUP,
// and POLLNVAL in pollfd::events except it doesn't actually do this.
size_t bytes = 0;
struct pollfd *fds2 = 0;
if (IsOpenbsd()) {
if (ckd_mul(&bytes, nfds, sizeof(struct pollfd)))
return einval();
#pragma GCC push_options
#pragma GCC diagnostic ignored "-Walloca-larger-than="
#pragma GCC diagnostic ignored "-Wanalyzer-out-of-bounds"
fds2 = alloca(bytes);
#pragma GCC pop_options
CheckLargeStackAllocation(fds2, bytes);
memcpy(fds2, fds, bytes);
for (size_t i = 0; i < nfds; ++i)
fds2[i].events &= ~(POLLERR | POLLHUP | POLLNVAL);
struct pollfd *swap = fds;
fds = fds2;
fds2 = swap;
}
if (!IsWindows()) {
e = errno;
if (timeout) {
@ -72,8 +133,8 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
} else {
tsp = 0;
}
rc = sys_ppoll(fds, nfds, tsp, sigmask, 8);
if (rc == -1 && errno == ENOSYS) {
fdcount = sys_ppoll(fds, nfds, tsp, sigmask, 8);
if (fdcount == -1 && errno == ENOSYS) {
int ms;
errno = e;
if (!timeout || ckd_add(&ms, timeout->tv_sec,
@ -82,7 +143,7 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
}
if (sigmask)
sys_sigprocmask(SIG_SETMASK, sigmask, &oldmask);
rc = poll(fds, nfds, ms);
fdcount = sys_poll(fds, nfds, ms);
if (sigmask)
sys_sigprocmask(SIG_SETMASK, &oldmask, 0);
}
@ -92,11 +153,38 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) {
ms = -1u;
}
rc = sys_poll_nt(fds, nfds, &ms, sigmask);
fdcount = sys_poll_nt(fds, nfds, &ms, sigmask);
}
if (IsOpenbsd() && fdcount != -1) {
struct pollfd *swap = fds;
fds = fds2;
fds2 = swap;
memcpy(fds, fds2, bytes);
}
// One of the use cases for poll() is checking if a large number of
// file descriptors exist. However on XNU if none of the meaningful
// event flags are specified (e.g. POLLIN, POLLOUT) then it doesn't
// perform the POLLNVAL check that's implied on all other platforms
if (IsXnu() && fdcount != -1) {
for (size_t i = 0; i < nfds; ++i) {
if (fds[i].fd >= 0 && //
!fds[i].revents && //
!(fds[i].events & (POLLIN | POLLOUT | POLLPRI))) {
int err = errno;
if (fcntl(fds[i].fd, F_GETFL) == -1) {
errno = err;
fds[i].revents = POLLNVAL;
++fdcount;
}
}
}
}
END_CANCELATION_POINT;
STRACE("ppoll(%s, %'zu, %s, %s) → %d% lm", DescribePollFds(rc, fds, nfds),
nfds, DescribeTimespec(0, timeout), DescribeSigset(0, sigmask), rc);
return rc;
STRACE("ppoll(%s, %'zu, %s, %s) → %d% lm",
DescribePollFds(fdcount, fds, nfds), nfds,
DescribeTimespec(0, timeout), DescribeSigset(0, sigmask), fdcount);
return fdcount;
}

View file

@ -32,7 +32,7 @@
#include "libc/sysv/errfuns.h"
/**
* Does what poll() does except with bitset API.
* Checks status on multiple file descriptors at once.
*
* This function is the same as saying:
*
@ -41,15 +41,23 @@
* select(nfds, readfds, writefds, exceptfds, timeout);
* sigprocmask(SIG_SETMASK, old, 0);
*
* Except it happens atomically.
*
* The Linux Kernel modifies the timeout parameter. This wrapper gives
* it a local variable due to POSIX requiring that `timeout` be const.
* If you need that information from the Linux Kernel use sys_pselect.
*
* This system call is supported on all platforms. It's like select()
* except that it atomically changes the sigprocmask() during the op.
* Except it happens atomically. Unlike ppoll() Cosmo guarantees this is
* atomic on all supported platforms.
*
* @param nfds is the number of the highest file descriptor set in these
* bitsets by the caller, plus one; this value can't be greater than
* `FD_SETSIZE` which Cosmopolitan currently defines as 1024 because
* `fd_set` has a static size
* @param readfds may be used to be notified when you can call read() on
* a file descriptor without it blocking; this includes when data is
* is available to be read as well as eof and error conditions
* @param writefds may be used to be notified when write() may be called
* on a file descriptor without it blocking
* @param exceptfds may be used to be notified of exceptional conditions
* such as out-of-band data on a socket; it is equivalent to POLLPRI
* in the revents of poll()
* @param timeout if null will block indefinitely
* @param sigmask may be null in which case no mask change happens
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @cancelationpoint
@ -74,7 +82,7 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
fd_set *old_exceptfds_ptr = 0;
BEGIN_CANCELATION_POINT;
if (nfds < 0) {
if (nfds < 0 || nfds > FD_SETSIZE) {
rc = einval();
} else {
if (readfds) {

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/timeval.h"
@ -31,37 +30,48 @@
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
// <sync libc/sysv/consts.sh>
#define POLLERR_ 0x0001 // implied in events
#define POLLHUP_ 0x0002 // implied in events
#define POLLNVAL_ 0x0004 // implied in events
#define POLLIN_ 0x0300
#define POLLRDNORM_ 0x0100
#define POLLRDBAND_ 0x0200
#define POLLOUT_ 0x0010
#define POLLWRNORM_ 0x0010
#define POLLWRBAND_ 0x0020 // MSDN undocumented
#define POLLPRI_ 0x0400 // MSDN unsupported
// </sync libc/sysv/consts.sh>
int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout,
const sigset_t *sigmask) {
int i, pfds, events, fdcount;
int pfds = 0;
// convert bitsets to pollfd
struct pollfd fds[64];
for (pfds = i = 0; i < nfds; ++i) {
events = 0;
if (readfds && FD_ISSET(i, readfds))
events |= POLLIN;
if (writefds && FD_ISSET(i, writefds))
events |= POLLOUT;
if (exceptfds && FD_ISSET(i, exceptfds))
events |= POLLERR;
struct pollfd fds[128];
for (int fd = 0; fd < nfds; ++fd) {
int events = 0;
if (readfds && FD_ISSET(fd, readfds))
events |= POLLIN_;
if (writefds && FD_ISSET(fd, writefds))
events |= POLLOUT_;
if (exceptfds && FD_ISSET(fd, exceptfds))
events |= POLLPRI_;
if (events) {
if (pfds < ARRAYLEN(fds)) {
fds[pfds].fd = i;
fds[pfds].events = events;
fds[pfds].revents = 0;
pfds += 1;
} else {
return enomem();
}
if (pfds == ARRAYLEN(fds))
return e2big();
fds[pfds].fd = fd;
fds[pfds].events = events;
fds[pfds].revents = 0;
++pfds;
}
}
// convert the wait time to a word
uint32_t millis;
if (!timeout) {
millis = -1;
millis = -1u;
} else {
int64_t ms = timeval_tomillis(*timeout);
if (ms < 0 || ms > UINT32_MAX) {
@ -72,9 +82,8 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds,
}
// call our nt poll implementation
fdcount = sys_poll_nt(fds, pfds, &millis, sigmask);
unassert(fdcount < 64);
if (fdcount < 0)
int fdcount = sys_poll_nt(fds, pfds, &millis, sigmask);
if (fdcount == -1)
return -1;
// convert pollfd back to bitsets
@ -85,20 +94,20 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds,
if (exceptfds)
FD_ZERO(exceptfds);
int bits = 0;
for (i = 0; i < pfds; ++i) {
if (fds[i].revents & POLLIN) {
for (int i = 0; i < pfds; ++i) {
if (fds[i].revents & (POLLIN_ | POLLHUP_ | POLLERR_ | POLLNVAL_)) {
if (readfds) {
FD_SET(fds[i].fd, readfds);
++bits;
}
}
if (fds[i].revents & POLLOUT) {
if (fds[i].revents & POLLOUT_) {
if (writefds) {
FD_SET(fds[i].fd, writefds);
++bits;
}
}
if (fds[i].revents & (POLLERR | POLLNVAL)) {
if (fds[i].revents & POLLPRI_) {
if (exceptfds) {
FD_SET(fds[i].fd, exceptfds);
++bits;
@ -107,9 +116,8 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds,
}
// store remaining time back in caller's timeval
if (timeout) {
if (timeout)
*timeout = timeval_frommillis(millis);
}
return bits;
}

View file

@ -17,26 +17,25 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/sock/select.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/strace.h"
#include "libc/sock/internal.h"
#include "libc/sock/select.h"
#include "libc/sock/select.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Does what poll() does except with bitset API.
*
* This system call is supported on all platforms. However, on Windows,
* this is polyfilled to translate into poll(). So it's recommended that
* poll() be used instead.
* Checks status on multiple file descriptors at once.
*
* @param readfds may be used to be notified when you can call read() on
* a file descriptor without it blocking; this includes when data is
* is available to be read as well as eof and error conditions
* @param writefds may be used to be notified when write() may be called
* on a file descriptor without it blocking
* @param exceptfds may be used to be notified of exceptional conditions
* such as out-of-band data on a socket; it is equivalent to POLLPRI
* in the revents of poll()
* @param timeout may be null which means to block indefinitely; cosmo's
* implementation of select() never modifies this parameter which is
* how most platforms except Linux work which modifies it to reflect
* elapsed time, noting that POSIX permits either behavior therefore
* portable code should assume that timeout memory becomes undefined
* @raise E2BIG if we exceeded the 64 socket limit on Windows
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @cancelationpoint
@ -45,70 +44,13 @@
*/
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout) {
int rc;
fd_set old_readfds;
fd_set *old_readfds_ptr = 0;
fd_set old_writefds;
fd_set *old_writefds_ptr = 0;
fd_set old_exceptfds;
fd_set *old_exceptfds_ptr = 0;
struct timeval old_timeout;
struct timeval *old_timeout_ptr = 0;
POLLTRACE("select(%d, %p, %p, %p, %s) → ...", nfds, readfds, writefds,
exceptfds, DescribeTimeval(0, timeout));
BEGIN_CANCELATION_POINT;
if (nfds < 0) {
rc = einval();
struct timespec ts;
struct timespec *tsp;
if (timeout) {
ts = timeval_totimespec(*timeout);
tsp = &ts;
} else {
if (readfds) {
old_readfds = *readfds;
old_readfds_ptr = &old_readfds;
}
if (writefds) {
old_writefds = *writefds;
old_writefds_ptr = &old_writefds;
}
if (exceptfds) {
old_exceptfds = *exceptfds;
old_exceptfds_ptr = &old_exceptfds;
}
if (timeout) {
old_timeout = *timeout;
old_timeout_ptr = &old_timeout;
}
if (!IsWindows()) {
#ifdef __aarch64__
struct timespec ts, *tsp;
if (timeout) {
ts = timeval_totimespec(*timeout);
tsp = &ts;
} else {
tsp = 0;
}
rc = sys_pselect(nfds, readfds, writefds, exceptfds, tsp, 0);
if (timeout) {
*timeout = timespec_totimeval(ts);
}
#else
rc = sys_select(nfds, readfds, writefds, exceptfds, timeout);
#endif
} else {
rc = sys_select_nt(nfds, readfds, writefds, exceptfds, timeout, 0);
}
tsp = 0;
}
END_CANCELATION_POINT;
STRACE("select(%d, %s → [%s], %s → [%s], %s → [%s], %s → [%s]) → %d% m", nfds,
DescribeFdSet(rc, nfds, old_readfds_ptr),
DescribeFdSet(rc, nfds, readfds),
DescribeFdSet(rc, nfds, old_writefds_ptr),
DescribeFdSet(rc, nfds, writefds),
DescribeFdSet(rc, nfds, old_exceptfds_ptr),
DescribeFdSet(rc, nfds, exceptfds), //
DescribeTimeval(rc, old_timeout_ptr), //
DescribeTimeval(rc, timeout), rc);
return rc;
return pselect(nfds, readfds, writefds, exceptfds, tsp, 0);
}

View file

@ -4,7 +4,6 @@ COSMOPOLITAN_C_START_
bool32 sys_isatty(int);
int sys_chdir_nt(const char *);
int sys_close_epoll_nt(int);
int sys_dup_nt(int, int, int, int);
int sys_execve_nt(const char *, char *const[], char *const[]);
int sys_faccessat_nt(int, const char *, int, uint32_t);

View file

@ -10,7 +10,7 @@ COSMOPOLITAN_C_START_
#define kFdConsole 4
#define kFdSerial 5
#define kFdZip 6
#define kFdEpoll 7
#define kFdEpoll 7 /* epoll() deleted on 2024-09-01 */
#define kFdReserved 8
#define kFdDevNull 9
#define kFdDevRandom 10

View file

@ -1,5 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_ISYSTEM_SYS_POLL_H_
#define COSMOPOLITAN_LIBC_ISYSTEM_SYS_POLL_H_
#include "libc/calls/weirdtypes.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sysv/consts/poll.h"
#endif /* COSMOPOLITAN_LIBC_ISYSTEM_SYS_POLL_H_ */

View file

@ -196,10 +196,21 @@ static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int64_t dirhand,
errno_t err;
char16_t path16[PATH_MAX];
uint32_t perm, share, disp, attr;
if (!strcmp(path, "/dev/null")) {
strcpy16(path16, u"NUL");
} else if (!strcmp(path, "/dev/stdin")) {
return spawnfds_dup2(fds, 0, fildes);
} else if (!strcmp(path, "/dev/stdout")) {
return spawnfds_dup2(fds, 1, fildes);
} else if (!strcmp(path, "/dev/stderr")) {
return spawnfds_dup2(fds, 2, fildes);
} else {
if (__mkntpathath(dirhand, path, 0, path16) == -1)
return errno;
}
if ((err = spawnfds_ensure(fds, fildes)))
return err;
if (__mkntpathath(dirhand, path, 0, path16) != -1 &&
GetNtOpenFlags(oflag, mode, &perm, &share, &disp, &attr) != -1 &&
if (GetNtOpenFlags(oflag, mode, &perm, &share, &disp, &attr) != -1 &&
(h = CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr, 0))) {
spawnfds_closelater(fds, h);
fds->p[fildes].kind = kFdFile;
@ -366,6 +377,19 @@ static textwindows errno_t posix_spawn_nt_impl(
}
}
// UNC paths break some things when they are not needed.
if (lpCurrentDirectory) {
size_t n = strlen16(lpCurrentDirectory);
if (n > 4 && n < 260 && //
lpCurrentDirectory[0] == '\\' && //
lpCurrentDirectory[1] == '\\' && //
lpCurrentDirectory[2] == '?' && //
lpCurrentDirectory[3] == '\\') {
memmove(lpCurrentDirectory, lpCurrentDirectory + 4,
(n - 4 + 1) * sizeof(char16_t));
}
}
// inherit signal mask
sigset_t childmask;
char maskvar[6 + 21];

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_SOCK_WEPOLL_H_
#define COSMOPOLITAN_LIBC_SOCK_WEPOLL_H_
COSMOPOLITAN_C_START_
#include "libc/calls/struct/sigset.h"
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct thatispacked epoll_event {
uint32_t events;
epoll_data_t data;
};
int epoll_create(int) libcesque;
int epoll_create1(int) libcesque;
int epoll_ctl(int, int, int, struct epoll_event *) libcesque;
int epoll_wait(int, struct epoll_event *, int, int) libcesque;
int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_SOCK_WEPOLL_H_ */

View file

@ -52,11 +52,6 @@ int32_t sys_select(int32_t, fd_set *, fd_set *, fd_set *, struct timeval *);
int sys_pselect(int, fd_set *, fd_set *, fd_set *, struct timespec *,
const void *);
int sys_setsockopt(int, int, int, const void *, uint32_t);
int32_t sys_epoll_create(int32_t);
int32_t sys_epoll_ctl(int32_t, int32_t, int32_t, void *);
int32_t sys_epoll_wait(int32_t, void *, int32_t, int32_t);
int32_t sys_epoll_pwait(int32_t, void *, int32_t, int32_t, const sigset_t *,
size_t);
int sys_socket_nt(int, int, int);

View file

@ -1,31 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_EPOLL_H_
#define COSMOPOLITAN_LIBC_SYSV_CONSTS_EPOLL_H_
#include "libc/sysv/consts/o.h"
#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3
#define EPOLLIN 1
#define EPOLLPRI 2
#define EPOLLOUT 4
#define EPOLLERR 8
#define EPOLLHUP 0x10
#define EPOLLRDNORM 0x40
#define EPOLLRDBAND 0x80
#define EPOLLWRNORM 0x0100
#define EPOLLWRBAND 0x0200
#define EPOLLMSG 0x0400
#define EPOLLRDHUP 0x2000
#define EPOLLEXCLUSIVE 0x10000000
#define EPOLLWAKEUP 0x20000000
#define EPOLLONESHOT 0x40000000
#define EPOLLET 0x80000000
COSMOPOLITAN_C_START_
extern const int EPOLL_CLOEXEC;
#define EPOLL_CLOEXEC O_CLOEXEC
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_EPOLL_H_ */

View file

@ -188,7 +188,6 @@ static errno_t _pthread_cancel_everyone(void) {
* - `connect`
* - `copy_file_range`
* - `creat`
* - `epoll_wait`
* - `fcntl(F_OFD_SETLKW)`
* - `fcntl(F_SETLKW)`
* - `fdatasync`

View file

@ -22,6 +22,7 @@
#include "libc/calls/struct/sigaction.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/describeflags.h"
#include "libc/log/libfatal.internal.h"
#include "libc/mem/gc.h"
#include "libc/nexgen32e/rdtsc.h"
@ -55,12 +56,6 @@ void OnSig(int sig) {
gotsig = true;
}
__wur char *FormatPollFd(struct pollfd p[2]) {
return xasprintf("fd:%d revents:%s\n"
"fd:%d revents:%s\n",
p[0].fd, "<TODO:kPollNames>", p[1].fd, "<TODO:kPollNames>");
}
TEST(poll, allZero_doesNothingPrettyMuch) {
EXPECT_SYS(0, 0, poll(0, 0, 0));
}
@ -94,14 +89,52 @@ TEST(poll, testNegativeOneFd_isIgnored) {
struct sockaddr_in addr = {AF_INET, 0, {htonl(INADDR_LOOPBACK)}};
ASSERT_SYS(0, 0, bind(3, (struct sockaddr *)&addr, sizeof(addr)));
ASSERT_SYS(0, 0, listen(3, 10));
struct pollfd fds[] = {{-1}, {3}};
struct pollfd fds[] = {{-1, 0, -1}, {3, 0, -1}};
EXPECT_SYS(0, 0, poll(fds, ARRAYLEN(fds), 1));
EXPECT_STREQ("fd:-1 revents:<TODO:kPollNames>\n"
"fd:3 revents:<TODO:kPollNames>\n",
gc(FormatPollFd(&fds[0])));
EXPECT_EQ(-1, fds[0].fd);
EXPECT_EQ(0, fds[0].revents);
EXPECT_EQ(3, fds[1].fd);
EXPECT_EQ(0, fds[1].revents);
ASSERT_SYS(0, 0, close(3));
}
TEST(poll, testInvalidFd_POLLIN_isChecked) {
struct pollfd fds[] = {{77, POLLIN, -1}};
EXPECT_SYS(0, 1, poll(fds, ARRAYLEN(fds), 1));
EXPECT_EQ(77, fds[0].fd);
EXPECT_EQ(POLLNVAL, fds[0].revents);
}
TEST(poll, testInvalidFd_POLLOUT_isChecked) {
struct pollfd fds[] = {{77, POLLOUT, -1}};
EXPECT_SYS(0, 1, poll(fds, ARRAYLEN(fds), 1));
EXPECT_EQ(77, fds[0].fd);
EXPECT_EQ(POLLNVAL, fds[0].revents);
}
TEST(poll, testInvalidFd_POLLPRI_isChecked) {
struct pollfd fds[] = {{77, POLLPRI, -1}};
EXPECT_SYS(0, 1, poll(fds, ARRAYLEN(fds), 1));
EXPECT_EQ(77, fds[0].fd);
EXPECT_EQ(POLLNVAL, fds[0].revents);
}
TEST(poll, testInvalidFd_POLLHUP_isChecked) {
// this behavior has to be polyfilled on xnu
struct pollfd fds[] = {{77, POLLHUP, -1}};
EXPECT_SYS(0, 1, poll(fds, ARRAYLEN(fds), 1));
EXPECT_EQ(77, fds[0].fd);
EXPECT_EQ(POLLNVAL, fds[0].revents);
}
TEST(poll, testInvalidFd_ZERO_isChecked) {
// this behavior has to be polyfilled on xnu
struct pollfd fds[] = {{77, 0, -1}};
EXPECT_SYS(0, 1, poll(fds, ARRAYLEN(fds), 1));
EXPECT_EQ(77, fds[0].fd);
EXPECT_EQ(POLLNVAL, fds[0].revents);
}
TEST(poll, pipe_noInput) {
// we can't test stdin here since
// we can't assume it isn't /dev/null
@ -115,6 +148,17 @@ TEST(poll, pipe_noInput) {
EXPECT_SYS(0, 0, close(pipefds[1]));
}
TEST(poll, pipe_broken) {
int pipefds[2];
EXPECT_SYS(0, 0, pipe(pipefds));
EXPECT_SYS(0, 0, close(pipefds[1]));
struct pollfd fds[] = {{pipefds[0], POLLIN}};
EXPECT_SYS(0, 1, poll(fds, 1, 0));
// BSDs also set POLLIN here too even though that's wrong
EXPECT_TRUE(!!(fds[0].revents & POLLHUP));
EXPECT_SYS(0, 0, close(pipefds[0]));
}
TEST(poll, pipe_hasInputFromSameProcess) {
char buf[2];
int pipefds[2];
@ -122,7 +166,7 @@ TEST(poll, pipe_hasInputFromSameProcess) {
struct pollfd fds[] = {{pipefds[0], POLLIN}};
EXPECT_SYS(0, 2, write(pipefds[1], "hi", 2));
EXPECT_SYS(0, 1, poll(fds, 1, 1000)); // flake nt!
EXPECT_EQ(POLLIN, fds[0].revents);
EXPECT_TRUE(!!(fds[0].revents & POLLIN));
EXPECT_SYS(0, 2, read(pipefds[0], buf, 2));
EXPECT_SYS(0, 0, poll(fds, 1, 0));
EXPECT_SYS(0, 0, close(pipefds[0]));
@ -150,7 +194,7 @@ TEST(poll, pipe_hasInput) {
EXPECT_SYS(0, 2, read(pipefds[0], buf, 2));
struct pollfd fds[] = {{pipefds[0], POLLIN}};
EXPECT_SYS(0, 1, poll(fds, 1, -1));
EXPECT_EQ(POLLIN, fds[0].revents & POLLIN);
EXPECT_TRUE(!!(fds[0].revents & POLLIN));
EXPECT_SYS(0, 2, read(pipefds[0], buf, 2));
EXPECT_SYS(0, 0, close(pipefds[0]));
ASSERT_NE(-1, wait(&ws));

View file

@ -40,6 +40,7 @@ TEST_LIBC_STDIO_DIRECTDEPS = \
LIBC_THREAD \
LIBC_LOG \
LIBC_X \
THIRD_PARTY_COMPILER_RT \
THIRD_PARTY_GDTOA \
THIRD_PARTY_MBEDTLS \
THIRD_PARTY_MUSL \

View file

@ -1844,7 +1844,6 @@ THIRD_PARTY_PYTHON_PYTEST_PYMAINS = \
third_party/python/Lib/test/test_enum.py \
third_party/python/Lib/test/test_enumerate.py \
third_party/python/Lib/test/test_eof.py \
third_party/python/Lib/test/test_epoll.py \
third_party/python/Lib/test/test_errno.py \
third_party/python/Lib/test/test_exception_hierarchy.py \
third_party/python/Lib/test/test_exception_variations.py \
@ -2148,8 +2147,6 @@ o/$(MODE)/third_party/python/Lib/test/test_wsgiref.py.runs: private \
/usr/local/etc/httpd/conf/mime.types \
/usr/local/etc/mime.types
o/$(MODE)/third_party/python/Lib/test/test_epoll.py.runs: \
private .PLEDGE = stdio rpath wpath cpath fattr proc inet
o/$(MODE)/third_party/python/Lib/test/test_wsgiref.py.runs: \
private .PLEDGE = stdio rpath wpath cpath fattr proc inet
o/$(MODE)/third_party/python/Lib/test/test_fcntl.py.runs: \
@ -2787,9 +2784,6 @@ o/$(MODE)/third_party/python/Lib/test/test_dis.py.runs: $(PYTHONTESTER)
o/$(MODE)/third_party/python/Lib/test/test_asyncore.py.runs: $(PYTHONTESTER)
@$(COMPILE) -ACHECK -wtT$@ $(PYHARNESSARGS) $(PYTHONTESTER) -m test.test_asyncore $(PYTESTARGS)
o/$(MODE)/third_party/python/Lib/test/test_epoll.py.runs: $(PYTHONTESTER)
@$(COMPILE) -ACHECK -wtT$@ $(PYHARNESSARGS) $(PYTHONTESTER) -m test.test_epoll $(PYTESTARGS)
o/$(MODE)/third_party/python/Lib/test/test_cmd_line.py.runs: $(PYTHONTESTER)
@$(COMPILE) -ACHECK -wtT$@ $(PYHARNESSARGS) $(PYTHONTESTER) -m test.test_cmd_line $(PYTESTARGS)

View file

@ -486,7 +486,7 @@ build_time_vars = {'ABIFLAGS': 'm',
'HAVE_SYS_DEVPOLL_H': 0,
'HAVE_SYS_DIR_H': 1,
'HAVE_SYS_ENDIAN_H': 0,
'HAVE_SYS_EPOLL_H': 1,
'HAVE_SYS_EPOLL_H': 0,
'HAVE_SYS_EVENT_H': 0,
'HAVE_SYS_FILE_H': 1,
'HAVE_SYS_IOCTL_H': 1,

View file

@ -10,11 +10,9 @@
#include "libc/mem/gc.h"
#include "libc/mem/mem.h"
#include "libc/nt/efi.h"
#include "libc/sock/epoll.h"
#include "libc/sock/select.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sysv/consts/epoll.h"
#include "libc/sysv/consts/poll.h"
#include "third_party/python/Include/abstract.h"
#include "third_party/python/Include/boolobject.h"
@ -35,21 +33,6 @@
#include "third_party/python/pyconfig.h"
PYTHON_PROVIDE("select");
PYTHON_PROVIDE("select.EPOLLERR");
PYTHON_PROVIDE("select.EPOLLET");
PYTHON_PROVIDE("select.EPOLLEXCLUSIVE");
PYTHON_PROVIDE("select.EPOLLHUP");
PYTHON_PROVIDE("select.EPOLLIN");
PYTHON_PROVIDE("select.EPOLLMSG");
PYTHON_PROVIDE("select.EPOLLONESHOT");
PYTHON_PROVIDE("select.EPOLLOUT");
PYTHON_PROVIDE("select.EPOLLPRI");
PYTHON_PROVIDE("select.EPOLLRDBAND");
PYTHON_PROVIDE("select.EPOLLRDHUP");
PYTHON_PROVIDE("select.EPOLLRDNORM");
PYTHON_PROVIDE("select.EPOLLWRBAND");
PYTHON_PROVIDE("select.EPOLLWRNORM");
PYTHON_PROVIDE("select.EPOLL_CLOEXEC");
PYTHON_PROVIDE("select.POLLERR");
PYTHON_PROVIDE("select.POLLHUP");
PYTHON_PROVIDE("select.POLLIN");
@ -61,7 +44,6 @@ PYTHON_PROVIDE("select.POLLRDHUP");
PYTHON_PROVIDE("select.POLLRDNORM");
PYTHON_PROVIDE("select.POLLWRBAND");
PYTHON_PROVIDE("select.POLLWRNORM");
PYTHON_PROVIDE("select.epoll");
PYTHON_PROVIDE("select.error");
PYTHON_PROVIDE("select.poll");
PYTHON_PROVIDE("select.select");

View file

@ -122,8 +122,8 @@
#define HAVE_DIRENT_D_TYPE 1
#define HAVE_DUP2 1
#define HAVE_DUP3 1
#define HAVE_EPOLL 1
#define HAVE_EPOLL_CREATE1 1
// #define HAVE_EPOLL 1
// #define HAVE_EPOLL_CREATE1 1
#define HAVE_ERF 1
#define HAVE_ERFC 1
#define HAVE_EXECV 1