diff --git a/examples/spawn.c b/examples/spawn.c index 780a9ef9a33..c118fc7a12c 100644 --- a/examples/spawn.c +++ b/examples/spawn.c @@ -86,147 +86,281 @@ // performance is critical. #define _GNU_SOURCE +#include #include +#include +#include #include #include #include #include +#include #include #include +#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) { - perror("pipe"); - return 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); } - // 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; + // 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"); + exit(6); } - // 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; + // 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); } - // 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; - } + // 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); + }; - // 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; } diff --git a/libc/calls/close-nt.c b/libc/calls/close-nt.c index 1952e4daa4d..16557582f77 100644 --- a/libc/calls/close-nt.c +++ b/libc/calls/close-nt.c @@ -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); diff --git a/libc/calls/close.c b/libc/calls/close.c index 979d249376e..2de6a55ef14 100644 --- a/libc/calls/close.c +++ b/libc/calls/close.c @@ -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; diff --git a/libc/calls/fstat-nt.c b/libc/calls/fstat-nt.c index a15c42bb271..97f77eae7b9 100644 --- a/libc/calls/fstat-nt.c +++ b/libc/calls/fstat-nt.c @@ -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 { + 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 { - physicalsize = st.st_size; + st.st_ctim = st.st_mtim; } - st.st_blocks = ROUNDUP(physicalsize, st.st_blksize) / 512; + 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; } diff --git a/libc/calls/mkntpathat.c b/libc/calls/mkntpathat.c index 090a9660a21..1b99927043d 100644 --- a/libc/calls/mkntpathat.c +++ b/libc/calls/mkntpathat.c @@ -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; } diff --git a/libc/calls/poll-nt.c b/libc/calls/poll-nt.c index 4735c7f4045..fab3a86489b 100644 --- a/libc/calls/poll-nt.c +++ b/libc/calls/poll-nt.c @@ -56,6 +56,19 @@ #define POLL_INTERVAL_MS 10 +// +#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 +// + // 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; diff --git a/libc/calls/poll.c b/libc/calls/poll.c index 5e13677a56a..285be26f012 100644 --- a/libc/calls/poll.c +++ b/libc/calls/poll.c @@ -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 fd≥0 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 fd≥0) 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); } diff --git a/libc/calls/ppoll.c b/libc/calls/ppoll.c index a50cfc5e029..5d9a1974735 100644 --- a/libc/calls/ppoll.c +++ b/libc/calls/ppoll.c @@ -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 fd≥0) 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; } diff --git a/libc/calls/pselect.c b/libc/calls/pselect.c index 9d3036a4cd2..9ecf8490fef 100644 --- a/libc/calls/pselect.c +++ b/libc/calls/pselect.c @@ -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) { diff --git a/libc/calls/select-nt.c b/libc/calls/select-nt.c index a669cae3721..cbb6797bf59 100644 --- a/libc/calls/select-nt.c +++ b/libc/calls/select-nt.c @@ -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__ +// +#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 +// + 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; } diff --git a/libc/calls/select.c b/libc/calls/select.c index 9c234f601d4..e90f69bdb50 100644 --- a/libc/calls/select.c +++ b/libc/calls/select.c @@ -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); } diff --git a/libc/calls/syscall-nt.internal.h b/libc/calls/syscall-nt.internal.h index 70fa3b41ddb..2c4e7dbbf2d 100644 --- a/libc/calls/syscall-nt.internal.h +++ b/libc/calls/syscall-nt.internal.h @@ -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); diff --git a/libc/intrin/fds.h b/libc/intrin/fds.h index a2b7e228b5f..59569ff28ac 100644 --- a/libc/intrin/fds.h +++ b/libc/intrin/fds.h @@ -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 diff --git a/libc/isystem/sys/poll.h b/libc/isystem/sys/poll.h index 98177f98ccb..df9126fb770 100644 --- a/libc/isystem/sys/poll.h +++ b/libc/isystem/sys/poll.h @@ -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_ */ diff --git a/libc/proc/posix_spawn.c b/libc/proc/posix_spawn.c index 310e1e73b85..434702d77e5 100644 --- a/libc/proc/posix_spawn.c +++ b/libc/proc/posix_spawn.c @@ -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]; diff --git a/libc/sock/epoll.c b/libc/sock/epoll.c deleted file mode 100644 index c5d809150cb..00000000000 --- a/libc/sock/epoll.c +++ /dev/null @@ -1,1655 +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 │ -╚──────────────────────────────────────────────────────────────────────────────╝ -│ │ -│ wepoll │ -│ https://github.com/piscisaureus/wepoll │ -│ │ -│ Copyright 2012-2020, Bert Belder │ -│ All rights reserved. │ -│ │ -│ Redistribution and use in source and binary forms, with or without │ -│ modification, are permitted provided that the following conditions are │ -│ met: │ -│ │ -│ * Redistributions of source code must retain the above copyright │ -│ notice, this list of conditions and the following disclaimer. │ -│ │ -│ * Redistributions in binary form must reproduce the above copyright │ -│ notice, this list of conditions and the following disclaimer in the │ -│ documentation and/or other materials provided with the distribution. │ -│ │ -│ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS │ -│ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT │ -│ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR │ -│ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT │ -│ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, │ -│ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT │ -│ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, │ -│ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY │ -│ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT │ -│ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE │ -│ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. │ -│ │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/sock/epoll.h" -#include "libc/assert.h" -#include "libc/calls/cp.internal.h" -#include "libc/calls/internal.h" -#include "libc/calls/state.internal.h" -#include "libc/calls/struct/sigset.internal.h" -#include "libc/calls/syscall_support-sysv.internal.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/strace.h" -#include "libc/limits.h" -#include "libc/macros.h" -#include "libc/mem/mem.h" -#include "libc/nt/enum/accessmask.h" -#include "libc/nt/enum/afd.h" -#include "libc/nt/enum/filesharemode.h" -#include "libc/nt/enum/ioctl.h" -#include "libc/nt/enum/keyedevent.h" -#include "libc/nt/enum/sio.h" -#include "libc/nt/enum/status.h" -#include "libc/nt/enum/wait.h" -#include "libc/nt/errors.h" -#include "libc/nt/files.h" -#include "libc/nt/iocp.h" -#include "libc/nt/nt/file.h" -#include "libc/nt/nt/key.h" -#include "libc/nt/ntdll.h" -#include "libc/nt/process.h" -#include "libc/nt/runtime.h" -#include "libc/nt/struct/afd.h" -#include "libc/nt/struct/criticalsection.h" -#include "libc/nt/struct/objectattributes.h" -#include "libc/nt/struct/overlappedentry.h" -#include "libc/nt/struct/unicodestring.h" -#include "libc/nt/synchronization.h" -#include "libc/nt/winsock.h" -#include "libc/runtime/runtime.h" -#include "libc/sock/internal.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/epoll.h" -#include "libc/sysv/consts/sig.h" -#include "libc/sysv/errfuns.h" - -/** - * @fileoverview epoll - * - * This is an alternative to poll() that's popular for event driven - * network servers that want >10,000 sockets per machine and don't do - * cpu bound computations that would otherwise block the event loop. - * - * This works on Linux and is polyfilled on Windows. It's worth noting - * that these polyfills depend on Microsoft's internal APIs. However - * these particular NTDLL APIs are also used by libuv, nodejs, etc. so - * we're reasonably certain Microsoft has compatibility policies in - * place where they've promised not to break them. - * - * TODO(jart): Polyfill kqueue for XNU/FreeBSD/OpenBSD. - */ - -__notice(wepoll_notice, "\ -wepoll (BSD-2)\n\ -Copyright 2012-2020 Bert Belder\n\ -https://github.com/piscisaureus/wepoll"); - -#define MAX_GROUP_SIZE 32 - -#define REFLOCK__REF 0x00000001 -#define REFLOCK__REF_MASK 0x0fffffff -#define REFLOCK__DESTROY 0x10000000 -#define REFLOCK__DESTROY_MASK 0xf0000000 -#define REFLOCK__POISON 0x300dead0 - -#define KNOWN_EVENTS \ - (EPOLLIN | EPOLLPRI | EPOLLOUT | EPOLLERR | EPOLLHUP | EPOLLRDNORM | \ - EPOLLRDBAND | EPOLLWRNORM | EPOLLWRBAND | EPOLLMSG | EPOLLRDHUP) - -#define RTL_CONSTANT_STRING(s) \ - { sizeof(s) - sizeof((s)[0]), sizeof(s), s } - -#define RTL_CONSTANT_OBJECT_ATTRIBUTES(ObjectName, Attributes) \ - { sizeof(struct NtObjectAttributes), 0, ObjectName, Attributes, NULL, NULL } - -#define RETURN_MAP_ERROR(value) \ - do { \ - err_map_win_error(); \ - return value; \ - } while (0) - -#define RETURN_SET_ERROR(value, error) \ - do { \ - err_set_win_error(error); \ - return value; \ - } while (0) - -#define CONTAINOF(ptr, type, member) \ - ((type *)((uintptr_t)(ptr) - offsetof(type, member))) - -#define TREE__ROTATE(cis, trans) \ - struct TreeNode *p = node; \ - struct TreeNode *q = node->trans; \ - struct TreeNode *parent = p->parent; \ - if (parent) { \ - if (parent->left == p) \ - parent->left = q; \ - else \ - parent->right = q; \ - } else { \ - tree->root = q; \ - } \ - q->parent = parent; \ - p->parent = q; \ - p->trans = q->cis; \ - if (p->trans) \ - p->trans->parent = p; \ - q->cis = p; - -#define TREE__INSERT_OR_DESCEND(side) \ - if (parent->side) { \ - parent = parent->side; \ - } else { \ - parent->side = node; \ - break; \ - } - -#define TREE__REBALANCE_AFTER_INSERT(cis, trans) \ - struct TreeNode *grandparent = parent->parent; \ - struct TreeNode *uncle = grandparent->trans; \ - if (uncle && uncle->red) { \ - parent->red = uncle->red = false; \ - grandparent->red = true; \ - node = grandparent; \ - } else { \ - if (node == parent->trans) { \ - tree__rotate_##cis(tree, parent); \ - node = parent; \ - parent = node->parent; \ - } \ - parent->red = false; \ - grandparent->red = true; \ - tree__rotate_##trans(tree, grandparent); \ - } - -#define TREE__REBALANCE_AFTER_REMOVE(cis, trans) \ - struct TreeNode *sibling = parent->trans; \ - if (sibling->red) { \ - sibling->red = false; \ - parent->red = true; \ - tree__rotate_##cis(tree, parent); \ - sibling = parent->trans; \ - } \ - if ((sibling->left && sibling->left->red) || \ - (sibling->right && sibling->right->red)) { \ - if (!sibling->trans || !sibling->trans->red) { \ - sibling->cis->red = false; \ - sibling->red = true; \ - tree__rotate_##trans(tree, sibling); \ - sibling = parent->trans; \ - } \ - sibling->red = parent->red; \ - parent->red = sibling->trans->red = false; \ - tree__rotate_##cis(tree, parent); \ - node = tree->root; \ - break; \ - } \ - sibling->red = true; - -#define tree_root(t) (t)->root -#define port_state_to_handle_tree_node(p) (&(p)->handle_tree_node) -#define sock_state_from_queue_node(q) CONTAINOF(q, struct SockState, queue_node) -#define sock_state_to_queue_node(s) (&(s)->queue_node) -#define sock_state_from_tree_node(t) CONTAINOF(t, struct SockState, tree_node) -#define sock_state_to_tree_node(s) (&(s)->tree_node) -#define poll_group_from_queue_node(q) CONTAINOF(q, struct PollGroup, queue_node) -#define poll_group_get_afd_device_handle(pg) (pg)->afd_device_handle - -enum PollStatus { - kPollIdle, - kPollPending, - kPollCancelled, -}; - -struct RefLock { - int state; -}; - -struct TreeNode { - struct TreeNode *left; - struct TreeNode *right; - struct TreeNode *parent; - uintptr_t key; - bool red; -}; - -struct Tree { - struct TreeNode *root; -}; - -struct TsTree { - struct Tree tree; - intptr_t lock; -}; - -struct TsTreeNode { - struct TreeNode tree_node; - struct RefLock reflock; -}; - -struct QueueNode { - struct QueueNode *prev; - struct QueueNode *next; -}; - -struct Queue { - struct QueueNode head; -}; - -struct PortState { - int64_t iocp_handle; - struct Tree sock_tree; - struct Queue sock_update_queue; - struct Queue sock_deleted_queue; - struct Queue poll_group_queue; - struct TsTreeNode handle_tree_node; - struct NtCriticalSection lock; - size_t active_poll_count; -}; - -struct PollGroup { - struct PortState *port_state; - struct QueueNode queue_node; - int64_t afd_device_handle; - size_t group_size; -}; - -struct SockState { - struct NtIoStatusBlock io_status_block; - struct NtAfdPollInfo poll_info; - struct QueueNode queue_node; - struct TreeNode tree_node; - struct PollGroup *poll_group; - int64_t base_socket; - epoll_data_t user_data; - uint32_t user_events; - uint32_t pending_events; - enum PollStatus poll_status; - bool delete_pending; -}; - -static const struct NtUnicodeString afd__device_name = - RTL_CONSTANT_STRING(u"\\Device\\Afd\\Wepoll"); - -static const struct NtObjectAttributes afd__device_attributes = - RTL_CONSTANT_OBJECT_ATTRIBUTES(&afd__device_name, 0); - -static int64_t reflock__keyed_event; -static struct TsTree epoll__handle_tree; - -static textwindows void err_map_win_error(void) { - errno = __dos2errno(GetLastError()); -} - -static textwindows void err_set_win_error(uint32_t error) { - SetLastError(error); - errno = __dos2errno(error); -} - -static textwindows int err_check_handle(int64_t handle) { - uint32_t flags; - /* GetHandleInformation() succeeds when passed INVALID_HANDLE_VALUE, - so check for this condition explicitly. */ - if (handle == kNtInvalidHandleValue) { - RETURN_SET_ERROR(-1, kNtErrorInvalidHandle); - } - if (!GetHandleInformation(handle, &flags)) { - RETURN_MAP_ERROR(-1); - } - return 0; -} - -static textwindows void tree_init(struct Tree *tree) { - bzero(tree, sizeof *tree); -} - -static textwindows void ts_tree_init(struct TsTree *ts_tree) { - tree_init(&ts_tree->tree); - InitializeSRWLock(&ts_tree->lock); -} - -static textwindows int reflock_global_init(void) { - NtStatus status; - if ((status = NtCreateKeyedEvent(&reflock__keyed_event, - kNtKeyedeventAllAccess, NULL, 0)) != - kNtStatusSuccess) { - RETURN_SET_ERROR(-1, RtlNtStatusToDosError(status)); - } - return 0; -} - -static textwindows int epoll_global_init(void) { - ts_tree_init(&epoll__handle_tree); - return 0; -} - -static textwindows int wepoll_init(void) { - static bool once; - static bool result; - if (!once) { - if (reflock_global_init() < 0 || epoll_global_init() < 0) { - result = false; - } else { - result = true; - } - once = true; - } - return result; -} - -static textwindows int afd_create_device_handle( - int64_t iocp_handle, int64_t *afd_device_handle_out) { - NtStatus status; - int64_t afd_device_handle; - struct NtIoStatusBlock iosb; - /* By opening \Device\Afd without specifying any extended attributes, - we'll get a handle that lets us talk to the AFD driver, but that - doesn't have an *associated endpoint (so it's not a socket). */ - status = NtCreateFile(&afd_device_handle, kNtSynchronize, - &afd__device_attributes, &iosb, NULL, 0, - kNtFileShareRead | kNtFileShareWrite, 1, 0, NULL, 0); - if (status != kNtStatusSuccess) { - RETURN_SET_ERROR(-1, RtlNtStatusToDosError(status)); - } - if (!CreateIoCompletionPort(afd_device_handle, iocp_handle, 0, 0)) { - goto error; - } - if (!SetFileCompletionNotificationModes(afd_device_handle, - kNtFileSkipSetEventOnHandle)) { - goto error; - } - *afd_device_handle_out = afd_device_handle; - return 0; -error: - CloseHandle(afd_device_handle); - RETURN_MAP_ERROR(-1); -} - -static textwindows int afd_poll(int64_t afd_device_handle, - struct NtAfdPollInfo *poll_info, - struct NtIoStatusBlock *io_status_block) { - NtStatus status; - /* Blocking operation is not supported.*/ - npassert(io_status_block); - io_status_block->Status = kNtStatusPending; - status = - NtDeviceIoControlFile(afd_device_handle, 0, NULL, io_status_block, - io_status_block, kNtIoctlAfdPoll, poll_info, - sizeof(*poll_info), poll_info, sizeof(*poll_info)); - if (status == kNtStatusSuccess) { - return 0; - } else if (status == kNtStatusPending) { - RETURN_SET_ERROR(-1, kNtErrorIoPending); - } else { - RETURN_SET_ERROR(-1, RtlNtStatusToDosError(status)); - } -} - -static textwindows int afd_cancel_poll( - int64_t afd_device_handle, struct NtIoStatusBlock *io_status_block) { - NtStatus cancel_status; - struct NtIoStatusBlock cancel_iosb; - /* If the poll operation has already completed or has been cancelled - earlier, there's nothing left for us to do. */ - if (io_status_block->Status != kNtStatusPending) - return 0; - cancel_status = - NtCancelIoFileEx(afd_device_handle, io_status_block, &cancel_iosb); - /* NtCancelIoFileEx() may return STATUS_NOT_FOUND if the operation completed - just before calling NtCancelIoFileEx(). This is not an error. */ - if (cancel_status == kNtStatusSuccess || cancel_status == kNtStatusNotFound) { - return 0; - } else { - RETURN_SET_ERROR(-1, RtlNtStatusToDosError(cancel_status)); - } -} - -static textwindows void queue_node_init(struct QueueNode *node) { - node->prev = node; - node->next = node; -} - -static textwindows void queue_init(struct Queue *queue) { - queue_node_init(&queue->head); -} - -static textwindows void queue__detach_node(struct QueueNode *node) { - node->prev->next = node->next; - node->next->prev = node->prev; -} - -forceinline bool queue_is_enqueued(const struct QueueNode *node) { - return node->prev != node; -} - -forceinline bool queue_is_empty(const struct Queue *queue) { - return !queue_is_enqueued(&queue->head); -} - -static textwindows struct QueueNode *queue_first(const struct Queue *queue) { - return !queue_is_empty(queue) ? queue->head.next : NULL; -} - -static textwindows struct QueueNode *queue_last(const struct Queue *queue) { - return !queue_is_empty(queue) ? queue->head.prev : NULL; -} - -static textwindows void queue_prepend(struct Queue *queue, - struct QueueNode *node) { - node->next = queue->head.next; - node->prev = &queue->head; - node->next->prev = node; - queue->head.next = node; -} - -static textwindows void queue_append(struct Queue *queue, - struct QueueNode *node) { - node->next = &queue->head; - node->prev = queue->head.prev; - node->prev->next = node; - queue->head.prev = node; -} - -static textwindows void queue_move_to_start(struct Queue *queue, - struct QueueNode *node) { - queue__detach_node(node); - queue_prepend(queue, node); -} - -static textwindows void queue_move_to_end(struct Queue *queue, - struct QueueNode *node) { - queue__detach_node(node); - queue_append(queue, node); -} - -static textwindows void queue_remove(struct QueueNode *node) { - queue__detach_node(node); - queue_node_init(node); -} - -static textwindows struct PortState *port__alloc(void) { - struct PortState *port_state = malloc(sizeof *port_state); - if (!port_state) - RETURN_SET_ERROR(NULL, kNtErrorNotEnoughMemory); - return port_state; -} - -static textwindows int64_t port__create_iocp(void) { - int64_t iocp_handle = CreateIoCompletionPort(kNtInvalidHandleValue, 0, 0, 0); - if (!iocp_handle) - RETURN_MAP_ERROR(0); - return iocp_handle; -} - -static textwindows int port__close_iocp(struct PortState *port_state) { - int64_t iocp_handle = port_state->iocp_handle; - port_state->iocp_handle = 0; - if (!CloseHandle(iocp_handle)) - RETURN_MAP_ERROR(-1); - return 0; -} - -static textwindows void tree_node_init(struct TreeNode *node) { - bzero(node, sizeof *node); -} - -static textwindows void reflock_init(struct RefLock *reflock) { - reflock->state = 0; -} - -static textwindows void ts_tree_node_init(struct TsTreeNode *node) { - tree_node_init(&node->tree_node); - reflock_init(&node->reflock); -} - -static textwindows void tree__rotate_left(struct Tree *tree, - struct TreeNode *node) { - TREE__ROTATE(left, right) -} - -static textwindows void tree__rotate_right(struct Tree *tree, - struct TreeNode *node) { - TREE__ROTATE(right, left) -} - -static textwindows int tree_add(struct Tree *tree, struct TreeNode *node, - uintptr_t key) { - struct TreeNode *parent; - parent = tree->root; - if (parent) { - for (;;) { - if (key < parent->key) { - TREE__INSERT_OR_DESCEND(left) - } else if (key > parent->key) { - TREE__INSERT_OR_DESCEND(right) - } else { - return -1; - } - } - } else { - tree->root = node; - } - node->key = key; - node->left = node->right = NULL; - node->parent = parent; - node->red = true; - for (; parent && parent->red; parent = node->parent) { - if (parent == parent->parent->left) { - TREE__REBALANCE_AFTER_INSERT(left, right) - } else { - TREE__REBALANCE_AFTER_INSERT(right, left) - } - } - tree->root->red = false; - return 0; -} - -static textwindows int ts_tree_add(struct TsTree *ts_tree, - struct TsTreeNode *node, uintptr_t key) { - int r; - AcquireSRWLockExclusive(&ts_tree->lock); - r = tree_add(&ts_tree->tree, &node->tree_node, key); - ReleaseSRWLockExclusive(&ts_tree->lock); - return r; -} - -static textwindows void port__free(struct PortState *port) { - npassert(port); - free(port); -} - -static textwindows struct PortState *port_new(int64_t *iocp_handle_out) { - struct PortState *port_state; - int64_t iocp_handle; - port_state = port__alloc(); - if (!port_state) - goto err1; - iocp_handle = port__create_iocp(); - if (!iocp_handle) - goto err2; - bzero(port_state, sizeof *port_state); - port_state->iocp_handle = iocp_handle; - tree_init(&port_state->sock_tree); - queue_init(&port_state->sock_update_queue); - queue_init(&port_state->sock_deleted_queue); - queue_init(&port_state->poll_group_queue); - ts_tree_node_init(&port_state->handle_tree_node); - InitializeCriticalSection(&port_state->lock); - *iocp_handle_out = iocp_handle; - return port_state; -err2: - port__free(port_state); -err1: - return NULL; -} - -static textwindows int sock__cancel_poll(struct SockState *sock_state) { - npassert(sock_state->poll_status == kPollPending); - if (afd_cancel_poll(poll_group_get_afd_device_handle(sock_state->poll_group), - &sock_state->io_status_block) < 0) { - return -1; - } - sock_state->poll_status = kPollCancelled; - sock_state->pending_events = 0; - return 0; -} - -static textwindows void port_cancel_socket_update( - struct PortState *port_state, struct SockState *sock_state) { - if (!queue_is_enqueued(sock_state_to_queue_node(sock_state))) - return; - queue_remove(sock_state_to_queue_node(sock_state)); -} - -static textwindows struct TreeNode *tree_find(const struct Tree *tree, - uintptr_t key) { - struct TreeNode *node = tree->root; - while (node) { - if (key < node->key) { - node = node->left; - } else if (key > node->key) { - node = node->right; - } else { - return node; - } - } - return NULL; -} - -static textwindows struct TsTreeNode *ts_tree__find_node(struct TsTree *ts_tree, - uintptr_t key) { - struct TreeNode *tree_node = tree_find(&ts_tree->tree, key); - if (!tree_node) - return NULL; - return CONTAINOF(tree_node, struct TsTreeNode, tree_node); -} - -static textwindows void tree_del(struct Tree *tree, struct TreeNode *node) { - bool red; - struct TreeNode *parent, *left, *right, *next; - parent = node->parent; - left = node->left; - right = node->right; - if (!left) { - next = right; - } else if (!right) { - next = left; - } else { - next = right; - while (next->left) - next = next->left; - } - if (parent) { - if (parent->left == node) { - parent->left = next; - } else { - parent->right = next; - } - } else { - tree->root = next; - } - if (left && right) { - red = next->red; - next->red = node->red; - next->left = left; - left->parent = next; - if (next != right) { - parent = next->parent; - next->parent = node->parent; - node = next->right; - parent->left = node; - next->right = right; - right->parent = next; - } else { - next->parent = parent; - parent = next; - node = next->right; - } - } else { - red = node->red; - node = next; - } - if (node) - node->parent = parent; - if (red) - return; - if (node && node->red) { - node->red = false; - return; - } - do { - if (node == tree->root) - break; - if (node == parent->left) { - TREE__REBALANCE_AFTER_REMOVE(left, right) - } else { - TREE__REBALANCE_AFTER_REMOVE(right, left) - } - node = parent; - parent = parent->parent; - } while (!node->red); - if (node) - node->red = false; -} - -static textwindows void reflock__signal_event(void *address) { - NtStatus status = - NtReleaseKeyedEvent(reflock__keyed_event, address, false, NULL); - if (status != kNtStatusSuccess) - abort(); -} - -static textwindows void reflock__await_event(void *address) { - NtStatus status = - NtWaitForKeyedEvent(reflock__keyed_event, address, false, NULL); - if (status != kNtStatusSuccess) - abort(); -} - -static textwindows void reflock_ref(struct RefLock *reflock) { - long state = InterlockedAdd(&reflock->state, REFLOCK__REF); - /* Verify that the counter didn 't overflow and the lock isn' t destroyed.*/ - npassert((state & REFLOCK__DESTROY_MASK) == 0); -} - -static textwindows void reflock_unref(struct RefLock *reflock) { - long state = InterlockedAdd(&reflock->state, -REFLOCK__REF); - /* Verify that the lock was referenced and not already destroyed.*/ - npassert((state & REFLOCK__DESTROY_MASK & ~REFLOCK__DESTROY) == 0); - if (state == REFLOCK__DESTROY) - reflock__signal_event(reflock); -} - -static textwindows struct TsTreeNode *ts_tree_del_and_ref( - struct TsTree *ts_tree, uintptr_t key) { - struct TsTreeNode *ts_tree_node; - AcquireSRWLockExclusive(&ts_tree->lock); - ts_tree_node = ts_tree__find_node(ts_tree, key); - if (ts_tree_node != NULL) { - tree_del(&ts_tree->tree, &ts_tree_node->tree_node); - reflock_ref(&ts_tree_node->reflock); - } - ReleaseSRWLockExclusive(&ts_tree->lock); - return ts_tree_node; -} - -static textwindows struct TsTreeNode *ts_tree_find_and_ref( - struct TsTree *ts_tree, uintptr_t key) { - struct TsTreeNode *ts_tree_node; - AcquireSRWLockShared(&ts_tree->lock); - ts_tree_node = ts_tree__find_node(ts_tree, key); - if (ts_tree_node != NULL) - reflock_ref(&ts_tree_node->reflock); - ReleaseSRWLockShared(&ts_tree->lock); - return ts_tree_node; -} - -static textwindows void ts_tree_node_unref(struct TsTreeNode *node) { - reflock_unref(&node->reflock); -} - -static textwindows void reflock_unref_and_destroy(struct RefLock *reflock) { - long state, ref_count; - state = InterlockedAdd(&reflock->state, REFLOCK__DESTROY - REFLOCK__REF); - ref_count = state & REFLOCK__REF_MASK; - /* Verify that the lock was referenced and not already destroyed. */ - npassert((state & REFLOCK__DESTROY_MASK) == REFLOCK__DESTROY); - if (ref_count != 0) - reflock__await_event(reflock); - state = InterlockedExchange(&reflock->state, REFLOCK__POISON); - npassert(state == REFLOCK__DESTROY); -} - -static textwindows void ts_tree_node_unref_and_destroy( - struct TsTreeNode *node) { - reflock_unref_and_destroy(&node->reflock); -} - -static textwindows void port_unregister_socket(struct PortState *port_state, - struct SockState *sock_state) { - tree_del(&port_state->sock_tree, sock_state_to_tree_node(sock_state)); -} - -static textwindows void port_remove_deleted_socket( - struct PortState *port_state, struct SockState *sock_state) { - if (!queue_is_enqueued(sock_state_to_queue_node(sock_state))) - return; - queue_remove(sock_state_to_queue_node(sock_state)); -} - -static textwindows struct Queue *port_get_poll_group_queue( - struct PortState *port_state) { - return &port_state->poll_group_queue; -} - -static textwindows void poll_group_release(struct PollGroup *poll_group) { - struct PortState *port_state = poll_group->port_state; - struct Queue *poll_group_queue = port_get_poll_group_queue(port_state); - poll_group->group_size--; - npassert(poll_group->group_size < MAX_GROUP_SIZE); - queue_move_to_end(poll_group_queue, &poll_group->queue_node); - /* Poll groups are currently only freed when the epoll port is closed. */ -} - -static textwindows void sock__free(struct SockState *sock_state) { - npassert(sock_state != NULL); - free(sock_state); -} - -static textwindows void port_add_deleted_socket(struct PortState *port_state, - struct SockState *sock_state) { - if (queue_is_enqueued(sock_state_to_queue_node(sock_state))) - return; - queue_append(&port_state->sock_deleted_queue, - sock_state_to_queue_node(sock_state)); -} - -static textwindows int sock__delete(struct PortState *port_state, - struct SockState *sock_state, bool force) { - if (!sock_state->delete_pending) { - if (sock_state->poll_status == kPollPending) { - sock__cancel_poll(sock_state); - } - port_cancel_socket_update(port_state, sock_state); - port_unregister_socket(port_state, sock_state); - sock_state->delete_pending = true; - } - /* If the poll request still needs to complete, the sock_state object - can't be free'd yet. `sock_feed_event()` or `port_close()` will - take care of this later. */ - if (force || sock_state->poll_status == kPollIdle) { - port_remove_deleted_socket(port_state, sock_state); - poll_group_release(sock_state->poll_group); - sock__free(sock_state); - } else { - /* Free the socket later.*/ - port_add_deleted_socket(port_state, sock_state); - } - return 0; -} - -static textwindows void sock_delete(struct PortState *port_state, - struct SockState *sock_state) { - sock__delete(port_state, sock_state, false); -} - -static textwindows void sock_force_delete(struct PortState *port_state, - struct SockState *sock_state) { - sock__delete(port_state, sock_state, true); -} - -static textwindows void poll_group_delete(struct PollGroup *poll_group) { - npassert(poll_group->group_size == 0); - CloseHandle(poll_group->afd_device_handle); - queue_remove(&poll_group->queue_node); - free(poll_group); -} - -static textwindows int port_delete(struct PortState *port_state) { - struct TreeNode *tree_node; - struct QueueNode *queue_node; - struct SockState *sock_state; - struct PollGroup *poll_group; - /* At this point the IOCP port should have been closed.*/ - npassert(!port_state->iocp_handle); - while ((tree_node = tree_root(&port_state->sock_tree)) != NULL) { - sock_state = sock_state_from_tree_node(tree_node); - sock_force_delete(port_state, sock_state); - } - while ((queue_node = queue_first(&port_state->sock_deleted_queue)) != NULL) { - sock_state = sock_state_from_queue_node(queue_node); - sock_force_delete(port_state, sock_state); - } - while ((queue_node = queue_first(&port_state->poll_group_queue)) != NULL) { - poll_group = poll_group_from_queue_node(queue_node); - poll_group_delete(poll_group); - } - npassert(queue_is_empty(&port_state->sock_update_queue)); - DeleteCriticalSection(&port_state->lock); - port__free(port_state); - return 0; -} - -static textwindows int64_t port_get_iocp_handle(struct PortState *port_state) { - npassert(port_state->iocp_handle); - return port_state->iocp_handle; -} - -static textwindows struct PollGroup *poll_group__new( - struct PortState *port_state) { - int64_t iocp_handle = port_get_iocp_handle(port_state); - struct Queue *poll_group_queue = port_get_poll_group_queue(port_state); - struct PollGroup *poll_group = malloc(sizeof *poll_group); - if (!poll_group) - RETURN_SET_ERROR(NULL, kNtErrorNotEnoughMemory); - bzero(poll_group, sizeof *poll_group); - queue_node_init(&poll_group->queue_node); - poll_group->port_state = port_state; - if (afd_create_device_handle(iocp_handle, &poll_group->afd_device_handle) < - 0) { - free(poll_group); - return NULL; - } - queue_append(poll_group_queue, &poll_group->queue_node); - return poll_group; -} - -static textwindows struct PollGroup *poll_group_acquire( - struct PortState *port_state) { - struct Queue *poll_group_queue = port_get_poll_group_queue(port_state); - struct PollGroup *poll_group = !queue_is_empty(poll_group_queue) - ? CONTAINOF(queue_last(poll_group_queue), - struct PollGroup, queue_node) - : NULL; - if (!poll_group || poll_group->group_size >= MAX_GROUP_SIZE) - poll_group = poll_group__new(port_state); - if (!poll_group) - return NULL; - if (++poll_group->group_size == MAX_GROUP_SIZE) - queue_move_to_start(poll_group_queue, &poll_group->queue_node); - return poll_group; -} - -static textwindows int port_close(struct PortState *port_state) { - int result; - EnterCriticalSection(&port_state->lock); - result = port__close_iocp(port_state); - LeaveCriticalSection(&port_state->lock); - return result; -} - -static textwindows uint32_t sock__epoll_events_to_afd_events(uint32_t e) { - /* Always monitor for kNtAfdPollLocalClose, which is triggered when - the socket is closed with closesocket() or CloseHandle(). */ - uint32_t a = kNtAfdPollLocalClose; - if (e & (EPOLLIN | EPOLLRDNORM)) - a |= kNtAfdPollReceive | kNtAfdPollAccept; - if (e & (EPOLLPRI | EPOLLRDBAND)) - a |= kNtAfdPollReceiveExpedited; - if (e & (EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND)) - a |= kNtAfdPollSend; - if (e & (EPOLLIN | EPOLLRDNORM | EPOLLRDHUP)) - a |= kNtAfdPollDisconnect; - if (e & EPOLLHUP) - a |= kNtAfdPollAbort; - if (e & EPOLLERR) - a |= kNtAfdPollConnectFail; - return a; -} - -static textwindows uint32_t sock__afd_events_to_epoll_events(uint32_t a) { - uint32_t e = 0; - if (a & (kNtAfdPollReceive | kNtAfdPollAccept)) - e |= EPOLLIN | EPOLLRDNORM; - if (a & kNtAfdPollReceiveExpedited) - e |= EPOLLPRI | EPOLLRDBAND; - if (a & kNtAfdPollSend) - e |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND; - if (a & kNtAfdPollDisconnect) - e |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP; - if (a & kNtAfdPollAbort) - e |= EPOLLHUP; - if (a & kNtAfdPollConnectFail) { - /* Linux reports all these events after connect() has failed. */ - e |= EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLRDNORM | EPOLLWRNORM | EPOLLRDHUP; - } - return e; -} - -static textwindows int sock_update(struct PortState *port_state, - struct SockState *sock_state) { - npassert(!sock_state->delete_pending); - if ((sock_state->poll_status == kPollPending) && - !(sock_state->user_events & KNOWN_EVENTS & ~sock_state->pending_events)) { - /* All the events the user is interested in are already being - monitored by the pending poll operation. It might spuriously - complete because of an event that we're no longer interested in; - when that happens we'll submit a new poll operation with the - updated event mask. */ - } else if (sock_state->poll_status == kPollPending) { - /* A poll operation is already pending, but it's not monitoring for - all the *events that the user is interested in. Therefore, cancel - the pending *poll operation; when we receive it's completion - package, a new poll *operation will be submitted with the correct - event mask. */ - if (sock__cancel_poll(sock_state) < 0) - return -1; - } else if (sock_state->poll_status == kPollCancelled) { - /* The poll operation has already been cancelled, we're still waiting for - it to return.For now, there' s nothing that needs to be done. */ - } else if (sock_state->poll_status == kPollIdle) { - /* No poll operation is pending; start one. */ - sock_state->poll_info.Exclusive = false; - sock_state->poll_info.NumberOfHandles = 1; - sock_state->poll_info.Timeout = INT64_MAX; - sock_state->poll_info.Handles[0].Handle = (int64_t)sock_state->base_socket; - sock_state->poll_info.Handles[0].Status = 0; - sock_state->poll_info.Handles[0].Events = - sock__epoll_events_to_afd_events(sock_state->user_events); - if (afd_poll(poll_group_get_afd_device_handle(sock_state->poll_group), - &sock_state->poll_info, &sock_state->io_status_block) < 0) { - switch (GetLastError()) { - case kNtErrorIoPending: - /* Overlapped poll operation in progress; this is expected. */ - break; - case kNtErrorInvalidHandle: - /* Socket closed; it'll be dropped from the epoll set. */ - return sock__delete(port_state, sock_state, false); - default: - /* Other errors are propagated to the caller. */ - RETURN_MAP_ERROR(-1); - } - } - /* The poll request was successfully submitted.*/ - sock_state->poll_status = kPollPending; - sock_state->pending_events = sock_state->user_events; - } else { - __builtin_unreachable(); - } - port_cancel_socket_update(port_state, sock_state); - return 0; -} - -static textwindows int port__update_events(struct PortState *port_state) { - struct QueueNode *queue_node; - struct SockState *sock_state; - struct Queue *sock_update_queue = &port_state->sock_update_queue; - /* Walk queue, submitting new poll requests for sockets needing it */ - while (!queue_is_empty(sock_update_queue)) { - queue_node = queue_first(sock_update_queue); - sock_state = sock_state_from_queue_node(queue_node); - if (sock_update(port_state, sock_state) < 0) - return -1; - /* sock_update() removes the socket from the update queue.*/ - } - return 0; -} - -static textwindows void port__update_events_if_polling( - struct PortState *port_state) { - if (port_state->active_poll_count > 0) - port__update_events(port_state); -} - -static textwindows void port_request_socket_update( - struct PortState *port_state, struct SockState *sock_state) { - if (queue_is_enqueued(sock_state_to_queue_node(sock_state))) - return; - queue_append(&port_state->sock_update_queue, - sock_state_to_queue_node(sock_state)); -} - -static textwindows int sock_feed_event(struct PortState *port_state, - struct NtIoStatusBlock *io_status_block, - struct epoll_event *ev) { - uint32_t epoll_events; - struct SockState *sock_state; - struct NtAfdPollInfo *poll_info; - epoll_events = 0; - sock_state = CONTAINOF(io_status_block, struct SockState, io_status_block); - poll_info = &sock_state->poll_info; - sock_state->poll_status = kPollIdle; - sock_state->pending_events = 0; - if (sock_state->delete_pending) { - /* Socket has been deleted earlier and can now be freed.*/ - return sock__delete(port_state, sock_state, false); - } else if (io_status_block->Status == kNtStatusCancelled) { - /* The poll request was cancelled by CancelIoEx.*/ - } else if (!NtSuccess(io_status_block->Status)) { - /* The overlapped request itself failed in an unexpected way.*/ - epoll_events = EPOLLERR; - } else if (poll_info->NumberOfHandles < 1) { - /* This poll operation succeeded but didn't report any socket events. */ - } else if (poll_info->Handles[0].Events & kNtAfdPollLocalClose) { - /* The poll operation reported that the socket was closed.*/ - return sock__delete(port_state, sock_state, false); - } else { - /* Events related to our socket were reported.*/ - epoll_events = - sock__afd_events_to_epoll_events(poll_info->Handles[0].Events); - } - /* Requeue the socket so a new poll request will be submitted.*/ - port_request_socket_update(port_state, sock_state); - /* Filter out events that the user didn't ask for. */ - epoll_events &= sock_state->user_events; - /* Return if there are no epoll events to report.*/ - if (epoll_events == 0) - return 0; - /* If the the socket has the EPOLLONESHOT flag set, unmonitor all - events, even EPOLLERR and EPOLLHUP. But always keep looking for - closed sockets. */ - if (sock_state->user_events & EPOLLONESHOT) { - sock_state->user_events = 0; - } - ev->data = sock_state->user_data; - ev->events = epoll_events; - return 1; -} - -static textwindows int port__feed_events(struct PortState *port_state, - struct epoll_event *epoll_events, - struct NtOverlappedEntry *iocp_events, - uint32_t iocp_event_count) { - uint32_t i; - int epoll_event_count; - struct epoll_event *ev; - struct NtIoStatusBlock *io_status_block; - epoll_event_count = 0; - for (i = 0; i < iocp_event_count; i++) { - io_status_block = (struct NtIoStatusBlock *)iocp_events[i].lpOverlapped; - ev = &epoll_events[epoll_event_count]; - epoll_event_count += sock_feed_event(port_state, io_status_block, ev); - } - return epoll_event_count; -} - -static textwindows int port__poll(struct PortState *port_state, - struct epoll_event *epoll_events, - struct NtOverlappedEntry *iocp_events, - uint32_t maxevents, uint32_t timeout) { - bool32 r; - uint32_t completion_count; - if (port__update_events(port_state) < 0) - return -1; - port_state->active_poll_count++; - LeaveCriticalSection(&port_state->lock); - r = GetQueuedCompletionStatusEx(port_state->iocp_handle, iocp_events, - maxevents, &completion_count, timeout, false); - EnterCriticalSection(&port_state->lock); - port_state->active_poll_count--; - if (!r) - RETURN_MAP_ERROR(-1); - return port__feed_events(port_state, epoll_events, iocp_events, - completion_count); -} - -static textwindows int port_wait(struct PortState *port_state, - struct epoll_event *events, int maxevents, - int timeout) { - int result; - uint64_t now, due = 0; - uint32_t gqcs_timeout; - struct NtOverlappedEntry *iocp_events; - struct NtOverlappedEntry stack_iocp_events[64]; - /* Check whether `maxevents` is in range.*/ - if (maxevents <= 0) - RETURN_SET_ERROR(-1, kNtErrorInvalidParameter); - /* Decide whether the IOCP completion list can live on the stack, or - allocate memory for it on the heap. */ - if ((size_t)maxevents <= ARRAYLEN(stack_iocp_events)) { - iocp_events = stack_iocp_events; - } else if ((iocp_events = malloc((size_t)maxevents * sizeof(*iocp_events))) == - NULL) { - iocp_events = stack_iocp_events; - maxevents = ARRAYLEN(stack_iocp_events); - } - /* Compute the timeout for GetQueuedCompletionStatus, and the wait end - time, if the user specified a timeout other than zero or infinite. */ - if (timeout > 0) { - due = GetTickCount64() + (uint64_t)timeout; - gqcs_timeout = (uint32_t)timeout; - } else if (timeout == 0) { - gqcs_timeout = 0; - } else { - gqcs_timeout = -1; - } - EnterCriticalSection(&port_state->lock); - /* Dequeue completion packets until either at least one interesting - event has been discovered, or the timeout is reached. */ - for (;;) { - result = port__poll(port_state, events, iocp_events, (uint32_t)maxevents, - gqcs_timeout); - if (result < 0 || result > 0) - break; - /* Result, error, or time - out. */ - if (timeout < 0) - continue; - /* When timeout is negative, never time out. */ - /* Update time. */ - now = GetTickCount64(); - /* Do not allow the due time to be in the past. */ - if (now >= due) { - SetLastError(kNtWaitTimeout); - break; - } - /* Recompute time-out argument for GetQueuedCompletionStatus. */ - gqcs_timeout = (uint32_t)(due - now); - } - port__update_events_if_polling(port_state); - LeaveCriticalSection(&port_state->lock); - if (iocp_events != stack_iocp_events) { - free(iocp_events); - } - if (result >= 0) { - return result; - } else if (GetLastError() == kNtWaitTimeout) { - return 0; - } else { - return -1; - } -} - -static textwindows int64_t ws__ioctl_get_bsp_socket(int64_t socket, - uint32_t ioctl) { - uint32_t bytes; - int64_t bsp_socket; - if (WSAIoctl(socket, ioctl, NULL, 0, &bsp_socket, sizeof(bsp_socket), &bytes, - NULL, NULL) != -1) { - return bsp_socket; - } else { - return -1; - } -} - -static textwindows int64_t ws_get_base_socket(int64_t socket) { - uint32_t error; - int64_t base_socket; - for (;;) { - base_socket = ws__ioctl_get_bsp_socket(socket, kNtSioBaseHandle); - if (base_socket != -1) { - return base_socket; - } - error = GetLastError(); - if (error == WSAENOTSOCK) { - RETURN_SET_ERROR(-1, error); - } - /* - * Even though Microsoft documentation clearly states that Layered - * Spyware Providers must never ever intercept the SIO_BASE_HANDLE - * ioctl, Komodia LSPs (that Lenovo got sued for preinstalling) do - * so anyway in order to redirect decrypted https requests through - * some foreign proxy and inject ads which breaks high-performance - * network event io. However it doesn't handle SIO_BSP_HANDLE_POLL - * which will at least let us obtain the socket associated with the - * next winsock protocol chain entry. If this succeeds, loop around - * and call SIO_BASE_HANDLE again with the returned BSP socket, to - * make sure we unwrap all layers and retrieve the real base socket. - */ - base_socket = ws__ioctl_get_bsp_socket(socket, kNtSioBspHandlePoll); - if (base_socket != -1 && base_socket != socket) { - socket = base_socket; - } else { - RETURN_SET_ERROR(-1, error); - } - } -} - -static textwindows struct SockState *sock__alloc(void) { - struct SockState *sock_state = malloc(sizeof *sock_state); - if (!sock_state) - RETURN_SET_ERROR(NULL, kNtErrorNotEnoughMemory); - return sock_state; -} - -static textwindows int port_register_socket(struct PortState *port_state, - struct SockState *sock_state, - int64_t socket) { - if (tree_add(&port_state->sock_tree, sock_state_to_tree_node(sock_state), - socket) < 0) { - RETURN_SET_ERROR(-1, kNtErrorAlreadyExists); - } - return 0; -} - -static textwindows struct SockState *sock_new(struct PortState *port_state, - int64_t socket) { - int64_t base_socket; - struct PollGroup *poll_group; - struct SockState *sock_state; - if (socket == 0 || socket == -1) - RETURN_SET_ERROR(0, kNtErrorInvalidHandle); - base_socket = ws_get_base_socket(socket); - if (base_socket == -1) - return NULL; - poll_group = poll_group_acquire(port_state); - if (!poll_group) - return NULL; - sock_state = sock__alloc(); - if (!sock_state) - goto err1; - bzero(sock_state, sizeof *sock_state); - sock_state->base_socket = base_socket; - sock_state->poll_group = poll_group; - tree_node_init(&sock_state->tree_node); - queue_node_init(&sock_state->queue_node); - if (port_register_socket(port_state, sock_state, socket) < 0) - goto err2; - return sock_state; -err2: - sock__free(sock_state); -err1: - poll_group_release(poll_group); - return NULL; -} - -static textwindows int sock_set_event(struct PortState *port_state, - struct SockState *sock_state, - const struct epoll_event *ev) { - /* EPOLLERR and EPOLLHUP are always reported, even when not requested - by the caller. However they are disabled after a event has been - reported for a socket for which the EPOLLONESHOT flag was set. */ - uint32_t events = ev->events | EPOLLERR | EPOLLHUP; - sock_state->user_events = events; - sock_state->user_data = ev->data; - if ((events & KNOWN_EVENTS & ~sock_state->pending_events) != 0) { - port_request_socket_update(port_state, sock_state); - } - return 0; -} - -static textwindows int port__ctl_add(struct PortState *port_state, int64_t sock, - struct epoll_event *ev) { - struct SockState *sock_state = sock_new(port_state, sock); - if (!sock_state) - return -1; - if (sock_set_event(port_state, sock_state, ev) < 0) { - sock_delete(port_state, sock_state); - return -1; - } - port__update_events_if_polling(port_state); - return 0; -} - -static textwindows struct SockState *port_find_socket( - struct PortState *port_state, int64_t socket) { - struct TreeNode *tree_node = tree_find(&port_state->sock_tree, socket); - if (!tree_node) - RETURN_SET_ERROR(NULL, kNtErrorNotFound); - return sock_state_from_tree_node(tree_node); -} - -static textwindows int port__ctl_mod(struct PortState *port_state, int64_t sock, - struct epoll_event *ev) { - struct SockState *sock_state = port_find_socket(port_state, sock); - if (!sock_state) - return -1; - if (sock_set_event(port_state, sock_state, ev) < 0) - return -1; - port__update_events_if_polling(port_state); - return 0; -} - -static textwindows int port__ctl_del(struct PortState *port_state, - int64_t sock) { - struct SockState *sock_state = port_find_socket(port_state, sock); - if (!sock_state) - return -1; - sock_delete(port_state, sock_state); - return 0; -} - -static textwindows int port__ctl_op(struct PortState *port_state, int op, - int64_t sock, struct epoll_event *ev) { - switch (op) { - case EPOLL_CTL_ADD: - return port__ctl_add(port_state, sock, ev); - case EPOLL_CTL_MOD: - return port__ctl_mod(port_state, sock, ev); - case EPOLL_CTL_DEL: - return port__ctl_del(port_state, sock); - default: - RETURN_SET_ERROR(-1, kNtErrorInvalidParameter); - } -} - -static textwindows int port_ctl(struct PortState *port_state, int op, - int64_t sock, struct epoll_event *ev) { - int result; - EnterCriticalSection(&port_state->lock); - result = port__ctl_op(port_state, op, sock, ev); - LeaveCriticalSection(&port_state->lock); - return result; -} - -static textwindows struct PortState *port_state_from_handle_tree_node( - struct TsTreeNode *tree_node) { - return CONTAINOF(tree_node, struct PortState, handle_tree_node); -} - -static textwindows dontinline int sys_epoll_create1_nt(uint32_t flags) { - int fd; - int64_t ephnd; - struct PortState *port_state; - struct TsTreeNode *tree_node; - if (wepoll_init() < 0) - return -1; - fd = __reservefd(-1); - if (fd == -1) - return -1; - port_state = port_new(&ephnd); - if (!port_state) { - __releasefd(fd); - return -1; - } - tree_node = port_state_to_handle_tree_node(port_state); - if (ts_tree_add(&epoll__handle_tree, tree_node, (uintptr_t)ephnd) < 0) { - /* This should never happen. */ - port_delete(port_state); - err_set_win_error(kNtErrorAlreadyExists); - __releasefd(fd); - return -1; - } - __fds_lock(); - g_fds.p[fd].kind = kFdEpoll; - g_fds.p[fd].handle = ephnd; - g_fds.p[fd].flags = flags; - g_fds.p[fd].mode = 0140666; - __fds_unlock(); - return fd; -} - -static textwindows dontinline int sys_epoll_ctl_nt(int epfd, int op, int fd, - struct epoll_event *ev) { - int r; - struct PortState *port_state; - struct TsTreeNode *tree_node; - if (!IsWindows()) { - return sys_epoll_ctl(epfd, op, fd, ev); - } else { - if (wepoll_init() < 0) - return -1; - if (!__isfdopen(fd)) - return ebadf(); - if (!__isfdkind(epfd, kFdEpoll)) - return ebadf(); - tree_node = ts_tree_find_and_ref(&epoll__handle_tree, g_fds.p[epfd].handle); - if (!tree_node) { - err_set_win_error(kNtErrorInvalidParameter); - goto err; - } - port_state = port_state_from_handle_tree_node(tree_node); - r = port_ctl(port_state, op, g_fds.p[fd].handle, ev); - ts_tree_node_unref(tree_node); - if (r < 0) - goto err; - return 0; - err: - /* On Linux, in the case of epoll_ctl(), EBADF takes priority over - other *errors. Wepoll mimics this behavior. */ - err_check_handle(g_fds.p[epfd].handle); - err_check_handle(g_fds.p[fd].handle); - return -1; - } -} - -static textwindows dontinline int sys_epoll_wait_nt(int epfd, - struct epoll_event *events, - int maxevents, - int timeoutms) { - int num_events; - struct PortState *port_state; - struct TsTreeNode *tree_node; - if (!__isfdkind(epfd, kFdEpoll)) - return ebadf(); - if (maxevents <= 0) - return einval(); - if (wepoll_init() < 0) - return -1; - tree_node = ts_tree_find_and_ref(&epoll__handle_tree, g_fds.p[epfd].handle); - if (!tree_node) { - err_set_win_error(kNtErrorInvalidParameter); - goto err; - } - port_state = port_state_from_handle_tree_node(tree_node); - num_events = port_wait(port_state, events, maxevents, timeoutms); - ts_tree_node_unref(tree_node); - if (num_events < 0) - goto err; - return num_events; -err: - err_check_handle(g_fds.p[epfd].handle); - return -1; -} - -#if SupportsWindows() -textwindows int sys_close_epoll_nt(int fd) { - struct PortState *port_state; - struct TsTreeNode *tree_node; - if (wepoll_init() < 0) - return -1; - tree_node = ts_tree_del_and_ref(&epoll__handle_tree, g_fds.p[fd].handle); - if (!tree_node) { - err_set_win_error(kNtErrorInvalidParameter); - goto err; - } - port_state = port_state_from_handle_tree_node(tree_node); - port_close(port_state); - ts_tree_node_unref_and_destroy(tree_node); - return port_delete(port_state); -err: - err_check_handle(g_fds.p[fd].handle); - return -1; -} -#endif - -/** - * Creates new epoll instance. - * - * @param size is ignored but must be greater than zero - * @param flags must be zero as there are no supported flags - * @return epoll file descriptor, or -1 on failure - */ -int epoll_create(int size) { - int rc; - if (size <= 0) { - rc = einval(); - } else { - BLOCK_SIGNALS; - rc = epoll_create1(0); - ALLOW_SIGNALS; - } - STRACE("epoll_create(%d) → %d% m", size, rc); - return rc; -} - -/** - * Creates new epoll instance. - * - * @param size is ignored but must be greater than zero - * @param flags must be zero or can have O_CLOEXEC - * @return epoll file descriptor, or -1 on failure - */ -int epoll_create1(int flags) { - int rc; - if (flags & ~O_CLOEXEC) { - rc = einval(); - } else if (!IsWindows()) { - rc = __fixupnewfd(sys_epoll_create(1337), flags); - } else { - BLOCK_SIGNALS; - rc = sys_epoll_create1_nt(flags); - ALLOW_SIGNALS; - } - STRACE("epoll_create1(%#x) → %d% m", flags, rc); - return rc; -} - -/** - * Controls which socket events are monitored. - * - * It is recommended to always explicitly remove a socket from its epoll - * set using EPOLL_CTL_DEL before closing it. As on Linux, your closed - * sockets are automatically removed from the epoll set, but wepoll may - * not be able to detect that a socket was closed until the next call to - * epoll_wait(). - * - * @param epfd is file descriptor created by epoll_create() - * @param op can be EPOLL_CTL_{ADD,MOD,DEL} - * @param fd is file descriptor to monitor - * @param ev is ignored if op is EPOLL_CTL_DEL - * @param ev->events can have these flags: - * - `EPOLLIN`: trigger on fd readable - * - `EPOLLOUT`: trigger on fd writeable - * - `EPOLLERR`: trigger on fd error (superfluous: always reported) - * - `EPOLLHUP`: trigger on fd remote hangup (superfluous: always reported) - * - `EPOLLPRI`: trigger on fd exceptional conditions, e.g. oob - * - `EPOLLONESHOT`: report event(s) only once - * - `EPOLLEXCLUSIVE`: not supported on windows - * - `EPOLLWAKEUP`: not supported on windows - * - `EPOLLET`: edge triggered mode (not supported on windows) - * - `EPOLLRDNORM` - * - `EPOLLRDBAND` - * - `EPOLLWRNORM` - * - `EPOLLWRBAND` - * - `EPOLLRDHUP` - * - `EPOLLMSG` - * @error ENOTSOCK on Windows if fd isn't a socket :( - * @return 0 on success, or -1 w/ errno - */ -int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev) { - int rc; - if (!IsWindows()) { - rc = sys_epoll_ctl(epfd, op, fd, ev); - } else { - BLOCK_SIGNALS; - rc = sys_epoll_ctl_nt(epfd, op, fd, ev); - ALLOW_SIGNALS; - } - STRACE("epoll_ctl(%d, %d, %d, %p) → %d% m", epfd, op, fd, ev, rc); - return rc; -} - -/** - * Receives socket events. - * - * @param events will receive information about what happened - * @param maxevents is array length of events - * @param timeoutms is milliseconds, 0 to not block, or -1 for forever - * @return number of events stored, 0 on timeout, or -1 w/ errno - * @cancelationpoint - * @norestart - */ -int epoll_wait(int epfd, struct epoll_event *events, int maxevents, - int timeoutms) { - int e, rc; - BEGIN_CANCELATION_POINT; - if (!IsWindows()) { - e = errno; - rc = sys_epoll_wait(epfd, events, maxevents, timeoutms); - if (rc == -1 && errno == ENOSYS) { - errno = e; - rc = sys_epoll_pwait(epfd, events, maxevents, timeoutms, 0, 0); - } - } else { - BLOCK_SIGNALS; - // eintr/ecanceled not implemented for epoll() on win32 yet - rc = sys_epoll_wait_nt(epfd, events, maxevents, timeoutms); - ALLOW_SIGNALS; - } - END_CANCELATION_POINT; - STRACE("epoll_wait(%d, %p, %d, %d) → %d% m", epfd, events, maxevents, - timeoutms, rc); - return rc; -} - -/** - * Receives socket events. - * - * @param events will receive information about what happened - * @param maxevents is array length of events - * @param timeoutms is milliseconds, 0 to not block, or -1 for forever - * @param sigmask is an optional sigprocmask() to use during call - * @return number of events stored, 0 on timeout, or -1 w/ errno - * @cancelationpoint - * @norestart - */ -int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, - int timeoutms, const sigset_t *sigmask) { - int e, rc; - sigset_t oldmask; - BEGIN_CANCELATION_POINT; - if (!IsWindows()) { - e = errno; - rc = sys_epoll_pwait(epfd, events, maxevents, timeoutms, sigmask, - sizeof(*sigmask)); - if (rc == -1 && errno == ENOSYS) { - errno = e; - if (sigmask) - sys_sigprocmask(SIG_SETMASK, sigmask, &oldmask); - rc = sys_epoll_wait(epfd, events, maxevents, timeoutms); - if (sigmask) - sys_sigprocmask(SIG_SETMASK, &oldmask, 0); - } - } else { - BLOCK_SIGNALS; - // eintr/ecanceled not implemented for epoll() on win32 yet - rc = sys_epoll_wait_nt(epfd, events, maxevents, timeoutms); - ALLOW_SIGNALS; - } - END_CANCELATION_POINT; - STRACE("epoll_pwait(%d, %p, %d, %d) → %d% m", epfd, events, maxevents, - timeoutms, DescribeSigset(0, sigmask), rc); - return rc; -} diff --git a/libc/sock/epoll.h b/libc/sock/epoll.h deleted file mode 100644 index ff858f09d05..00000000000 --- a/libc/sock/epoll.h +++ /dev/null @@ -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_ */ diff --git a/libc/sock/internal.h b/libc/sock/internal.h index 3cc13b06108..9dbd690dc34 100644 --- a/libc/sock/internal.h +++ b/libc/sock/internal.h @@ -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); diff --git a/libc/sysv/consts/epoll.h b/libc/sysv/consts/epoll.h deleted file mode 100644 index a2d11e6436c..00000000000 --- a/libc/sysv/consts/epoll.h +++ /dev/null @@ -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_ */ diff --git a/libc/thread/pthread_cancel.c b/libc/thread/pthread_cancel.c index 5ddbea0db80..2ac5cf3051f 100644 --- a/libc/thread/pthread_cancel.c +++ b/libc/thread/pthread_cancel.c @@ -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` diff --git a/test/libc/calls/poll_test.c b/test/libc/calls/poll_test.c index ee844762bdd..72e3f1e943a 100644 --- a/test/libc/calls/poll_test.c +++ b/test/libc/calls/poll_test.c @@ -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, "", p[1].fd, ""); -} - 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:\n" - "fd:3 revents:\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)); diff --git a/test/libc/sock/select_test.c b/test/libc/calls/select_test.c similarity index 100% rename from test/libc/sock/select_test.c rename to test/libc/calls/select_test.c diff --git a/test/libc/stdio/BUILD.mk b/test/libc/stdio/BUILD.mk index f40be539622..78bd1138c3e 100644 --- a/test/libc/stdio/BUILD.mk +++ b/test/libc/stdio/BUILD.mk @@ -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 \ diff --git a/third_party/python/BUILD.mk b/third_party/python/BUILD.mk index fe1d93def0a..8a636844cc0 100644 --- a/third_party/python/BUILD.mk +++ b/third_party/python/BUILD.mk @@ -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) diff --git a/third_party/python/Lib/_sysconfigdata_m_cosmo_x86_64_cosmo.py b/third_party/python/Lib/_sysconfigdata_m_cosmo_x86_64_cosmo.py index f371f423680..349ed740000 100644 --- a/third_party/python/Lib/_sysconfigdata_m_cosmo_x86_64_cosmo.py +++ b/third_party/python/Lib/_sysconfigdata_m_cosmo_x86_64_cosmo.py @@ -486,7 +486,7 @@ '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, diff --git a/third_party/python/Modules/selectmodule.c b/third_party/python/Modules/selectmodule.c index 37ba974fa5d..265e90b1386 100644 --- a/third_party/python/Modules/selectmodule.c +++ b/third_party/python/Modules/selectmodule.c @@ -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"); diff --git a/third_party/python/pyconfig.h b/third_party/python/pyconfig.h index e5fb19abd81..fe9749bd4c5 100644 --- a/third_party/python/pyconfig.h +++ b/third_party/python/pyconfig.h @@ -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