diff --git a/libc/calls/checkcancel.c b/libc/calls/checkcancel.c index b13f0446e..51e1bfee7 100644 --- a/libc/calls/checkcancel.c +++ b/libc/calls/checkcancel.c @@ -21,12 +21,16 @@ #include "libc/intrin/weaken.h" #include "libc/thread/posixthread.internal.h" +textwindows bool _is_canceled(void) { + struct PosixThread *pt; + return _weaken(_pthread_cancel_ack) && (pt = _pthread_self()) && + atomic_load_explicit(&pt->pt_canceled, memory_order_acquire) && + !(pt->pt_flags & PT_NOCANCEL); +} + textwindows int _check_cancel(void) { - if (_weaken(_pthread_cancel_ack) && // - _pthread_self() && !(_pthread_self()->pt_flags & PT_NOCANCEL) && - atomic_load_explicit(&_pthread_self()->pt_canceled, - memory_order_acquire)) { + if (_is_canceled()) + // once acknowledged _is_canceled() will return false return _weaken(_pthread_cancel_ack)(); - } return 0; } diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 7c2774380..010215788 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -43,6 +43,7 @@ forceinline bool __isfdkind(int fd, int kind) { int _check_signal(bool); int _check_cancel(void); +bool _is_canceled(void); int sys_close_nt(int, int); int _park_norestart(uint32_t, uint64_t); int _park_restartable(uint32_t, uint64_t); diff --git a/libc/calls/islinuxmodern.c b/libc/calls/islinuxmodern.c new file mode 100644 index 000000000..3b8a748d7 --- /dev/null +++ b/libc/calls/islinuxmodern.c @@ -0,0 +1,27 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-sysv.internal.h" +#include "libc/dce.h" +#include "libc/errno.h" + +bool IsLinuxModern(void) { + return IsLinux() && sys_close_range(-1, -2, 0) == -1 && errno == EINVAL; +} diff --git a/libc/calls/readwrite-nt.c b/libc/calls/readwrite-nt.c index 30516b4fb..2f9a7d8d5 100644 --- a/libc/calls/readwrite-nt.c +++ b/libc/calls/readwrite-nt.c @@ -16,21 +16,25 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/createfileflags.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/syscall_support-nt.internal.h" +#include "libc/errno.h" #include "libc/intrin/fds.h" #include "libc/intrin/weaken.h" #include "libc/nt/enum/filetype.h" #include "libc/nt/errors.h" #include "libc/nt/events.h" #include "libc/nt/files.h" +#include "libc/nt/process.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/overlapped.h" #include "libc/nt/synchronization.h" #include "libc/nt/thread.h" +#include "libc/sock/internal.h" #include "libc/stdio/sysparam.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/errfuns.h" @@ -47,8 +51,6 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset, int64_t handle, sigset_t waitmask, bool32 ReadOrWriteFile(int64_t, void *, uint32_t, uint32_t *, struct NtOverlapped *)) { - int sig; - uint32_t exchanged; struct Fd *f = g_fds.p + fd; // pread() and pwrite() perform an implicit lseek() operation, so @@ -60,105 +62,153 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset, if (pwriting && !seekable) return espipe(); - // determine if we need to lock a file descriptor across processes + // determine if we need to lock file descriptor bool locked = isdisk && !pwriting && f->cursor; - if (locked) - __cursor_lock(f->cursor); -RestartOperation: - // when a file is opened in overlapped mode win32 requires that we - // take over full responsibility for managing our own file pointer - // which is fine, because the one win32 has was never very good in - // the sense that it behaves so differently from linux, that using - // win32 i/o required more compatibilty toil than doing it by hand - if (!pwriting) { - if (seekable && f->cursor) { - offset = f->cursor->shared->pointer; - } else { - offset = 0; - } - } + for (;;) { + int got_sig = 0; + bool got_eagain = false; + uint32_t other_error = 0; - bool eagained = false; - // check for signals and cancelation - if (_check_cancel() == -1) { + // create event handle for overlapped i/o + intptr_t event; + if (!(event = CreateEvent(0, 1, 0, 0))) + return __winerr(); + + // ensure iops are ordered across threads and processes if seeking if (locked) - __cursor_unlock(f->cursor); - return -1; // ECANCELED - } - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) - goto HandleInterrupt; + __cursor_lock(f->cursor); - // signals have already been fully blocked by caller - // perform i/o operation with atomic signal/cancel checking - struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 1, 0, 0), - .Pointer = offset}; - bool32 ok = ReadOrWriteFile(handle, data, size, 0, &overlap); - if (!ok && GetLastError() == kNtErrorIoPending) { - // win32 says this i/o operation needs to block - if (f->flags & _O_NONBLOCK) { - // abort the i/o operation if file descriptor is in non-blocking mode - CancelIoEx(handle, &overlap); - eagained = true; - } else { - // wait until i/o either completes or is canceled by another thread - // we avoid a race condition by having a second mask for unblocking - struct PosixThread *pt; - pt = _pthread_self(); - pt->pt_blkmask = waitmask; - pt->pt_iohandle = handle; - pt->pt_ioverlap = &overlap; - atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_IO, - memory_order_release); - WaitForSingleObject(overlap.hEvent, -1u); - atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); + // when a file is opened in overlapped mode win32 requires that we + // take over full responsibility for managing our own file pointer + // which is fine, because the one win32 has was never very good in + // the sense that it behaves so differently from linux, that using + // win32 i/o required more compatibilty toil than doing it by hand + if (!pwriting) { + if (seekable && f->cursor) { + offset = f->cursor->shared->pointer; + } else { + offset = 0; + } } - ok = true; - } - if (ok) - ok = GetOverlappedResult(handle, &overlap, &exchanged, true); - CloseHandle(overlap.hEvent); - // if i/o succeeded then return its result - if (ok) { - if (!pwriting && seekable && f->cursor) - f->cursor->shared->pointer = offset + exchanged; - if (locked) - __cursor_unlock(f->cursor); - return exchanged; - } + // initiate asynchronous i/o operation with win32 + struct NtOverlapped overlap = {.hEvent = event, .Pointer = offset}; + bool32 ok = ReadOrWriteFile(handle, data, size, 0, &overlap); + if (!ok && GetLastError() == kNtErrorIoPending) { + if (f->flags & _O_NONBLOCK) { + // immediately back out of blocking i/o if non-blocking + CancelIoEx(handle, &overlap); + got_eagain = true; + } else { + // atomic block on i/o completion, signal, or cancel + // it's not safe to acknowledge cancelation from here + // it's not safe to call any signal handlers from here + intptr_t sem; + if ((sem = CreateSemaphore(0, 0, 1, 0))) { + // installing semaphore before sig get makes wait atomic + struct PosixThread *pt = _pthread_self(); + pt->pt_semaphore = sem; + pt->pt_blkmask = waitmask; + atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, + memory_order_release); + if (_is_canceled()) { + CancelIoEx(handle, &overlap); + } else if (_weaken(__sig_get) && + (got_sig = _weaken(__sig_get)(waitmask))) { + CancelIoEx(handle, &overlap); + } else { + intptr_t hands[] = {event, sem}; + uint32_t wi = WaitForMultipleObjects(2, hands, 0, -1u); + if (wi == 1) { // semaphore was signaled by signal enqueue + CancelIoEx(handle, &overlap); + if (_weaken(__sig_get)) + got_sig = _weaken(__sig_get)(waitmask); + } else if (wi == -1u) { + other_error = GetLastError(); + CancelIoEx(handle, &overlap); + } + } + atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); + CloseHandle(sem); + } else { + other_error = GetLastError(); + CancelIoEx(handle, &overlap); + } + } + ok = true; + } + uint32_t exchanged = 0; + if (ok) + ok = GetOverlappedResult(handle, &overlap, &exchanged, true); + uint32_t io_error = GetLastError(); + CloseHandle(event); - // only raise EINTR or EAGAIN if I/O got canceled - if (GetLastError() == kNtErrorOperationAborted) { - // raise EAGAIN if it's due to O_NONBLOCK mmode - if (eagained) { + // check if i/o completed + // this could forseeably happen even if CancelIoEx was called + if (ok) { + if (!pwriting && seekable && f->cursor) + f->cursor->shared->pointer = offset + exchanged; if (locked) __cursor_unlock(f->cursor); + if (got_sig) // swallow dequeued signal + _weaken(__sig_relay)(got_sig, SI_KERNEL, waitmask); + return exchanged; + } + + // it's now safe to unlock cursor + if (locked) + __cursor_unlock(f->cursor); + + // check if i/o failed + if (io_error != kNtErrorOperationAborted) { + if (got_sig) // swallow dequeued signal + _weaken(__sig_relay)(got_sig, SI_KERNEL, waitmask); + // read() and write() have different error paths + SetLastError(io_error); + return -2; + } + + // the i/o operation was successfully canceled + if (got_eagain) { + unassert(!got_sig); return eagain(); } - // otherwise it must be due to a kill() via __sig_cancel() - if (_weaken(__sig_relay) && (sig = _weaken(__sig_get)(waitmask))) { - HandleInterrupt: - if (locked) - __cursor_unlock(f->cursor); - int handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); - if (_check_cancel() == -1) - return -1; // possible if we SIGTHR'd - if (locked) - __cursor_lock(f->cursor); - // read() is @restartable unless non-SA_RESTART hands were called - if (!(handler_was_called & SIG_HANDLED_NO_RESTART)) - goto RestartOperation; - } - if (locked) - __cursor_unlock(f->cursor); - return eintr(); - } - // read() and write() have generally different error-handling paths - if (locked) - __cursor_unlock(f->cursor); - return -2; + // it's now reasonable to report semaphore creation error + if (other_error) { + unassert(!got_sig); + errno = __dos2errno(other_error); + return -1; + } + + // check for thread cancelation and acknowledge + if (_check_cancel() == -1) + return -1; + + // if signal module has been linked, then + if (_weaken(__sig_get)) { + + // gobble up all unmasked pending signals + // it's now safe to recurse into signal handlers + int handler_was_called = 0; + do { + if (got_sig) + handler_was_called |= + _weaken(__sig_relay)(got_sig, SI_KERNEL, waitmask); + } while ((got_sig = _weaken(__sig_get)(waitmask))); + + // check if SIGTHR handler was called + if (_check_cancel() == -1) + return -1; + + // check if signal handler without SA_RESTART was called + if (handler_was_called & SIG_HANDLED_NO_RESTART) + return eintr(); + } + + // otherwise try the i/o operation again + } } #endif /* __x86_64__ */ diff --git a/libc/calls/sigtimedwait-nt.c b/libc/calls/sigtimedwait-nt.c index 5afb3d438..fdf652cf8 100644 --- a/libc/calls/sigtimedwait-nt.c +++ b/libc/calls/sigtimedwait-nt.c @@ -32,6 +32,7 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" +#ifdef __x86_64__ textwindows static int sys_sigtimedwait_nt_check(sigset_t syncsigs, siginfo_t *opt_info, @@ -111,3 +112,5 @@ textwindows int sys_sigtimedwait_nt(const sigset_t *set, siginfo_t *opt_info, ALLOW_SIGNALS; return rc; } + +#endif /* __x86_64__ */ diff --git a/libc/calls/write-nt.c b/libc/calls/write-nt.c index 528c28332..cbd721483 100644 --- a/libc/calls/write-nt.c +++ b/libc/calls/write-nt.c @@ -53,20 +53,17 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size, bool isconsole = f->kind == kFdConsole; // not implemented, XNU returns eperm(); - if (f->kind == kFdDevRandom) { + if (f->kind == kFdDevRandom) return eperm(); - } // determine win32 handle for writing int64_t handle = f->handle; - if (isconsole && _weaken(GetConsoleOutputHandle)) { + if (isconsole && _weaken(GetConsoleOutputHandle)) handle = _weaken(GetConsoleOutputHandle)(); - } // intercept ansi tty configuration sequences - if (isconsole && _weaken(GetConsoleOutputHandle)) { + if (isconsole && _weaken(GetConsoleOutputHandle)) _weaken(InterceptTerminalCommands)(data, size); - } // perform heavy lifting ssize_t rc; diff --git a/libc/cosmo.h b/libc/cosmo.h index ce7f3a5dc..fdfd8c39f 100644 --- a/libc/cosmo.h +++ b/libc/cosmo.h @@ -14,6 +14,7 @@ char *GetProgramExecutableName(void) libcesque; void unleaf(void) libcesque; int __demangle(char *, const char *, size_t) libcesque; int __is_mangled(const char *) libcesque; +bool IsLinuxModern(void) libcesque; int LoadZipArgs(int *, char ***) libcesque; COSMOPOLITAN_C_END_ diff --git a/libc/sock/connect-nt.c b/libc/sock/connect-nt.c index a79196d7a..473066d0e 100644 --- a/libc/sock/connect-nt.c +++ b/libc/sock/connect-nt.c @@ -146,7 +146,7 @@ textwindows static int sys_connect_nt_impl(struct Fd *f, const void *addr, // check if we still need more time if (!ready) { if (f->flags & O_NONBLOCK) { - return ealready(); + return etimedout(); } else { continue; } diff --git a/libc/sock/winsockblock.c b/libc/sock/winsockblock.c index e0d0b2848..170c9e106 100644 --- a/libc/sock/winsockblock.c +++ b/libc/sock/winsockblock.c @@ -16,14 +16,20 @@ │ 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/sig.internal.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/timespec.internal.h" +#include "libc/calls/syscall_support-nt.internal.h" #include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/intrin/weaken.h" #include "libc/nt/enum/wait.h" #include "libc/nt/errors.h" +#include "libc/nt/runtime.h" #include "libc/nt/struct/overlapped.h" +#include "libc/nt/synchronization.h" #include "libc/nt/thread.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" @@ -39,69 +45,138 @@ __winsock_block(int64_t handle, uint32_t flags, bool nonblock, uint32_t *flags, void *arg), void *arg) { -RestartOperation: - int rc, sig, reason = 0; - uint32_t status, exchanged; - if (_check_cancel() == -1) - return -1; // ECANCELED - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { - goto HandleInterrupt; + // convert relative to absolute timeout + struct timespec deadline; + if (srwtimeout) { + deadline = timespec_add(sys_clock_gettime_monotonic_nt(), + timespec_frommillis(srwtimeout)); + } else { + deadline = timespec_max; } - struct NtOverlapped overlap = {.hEvent = WSACreateEvent()}; - rc = StartSocketOp(handle, &overlap, &flags, arg); - if (rc && WSAGetLastError() == kNtErrorIoPending) { - if (nonblock) { - CancelIoEx(handle, &overlap); - reason = EAGAIN; - } else { - struct PosixThread *pt; - pt = _pthread_self(); - pt->pt_blkmask = waitmask; - pt->pt_iohandle = handle; - pt->pt_ioverlap = &overlap; - atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_IO, - memory_order_release); - status = WSAWaitForMultipleEvents(1, &overlap.hEvent, 0, - srwtimeout ? srwtimeout : -1u, 0); - atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); - if (status) { - if (status == kNtWaitTimeout) { - reason = EAGAIN; // SO_RCVTIMEO or SO_SNDTIMEO elapsed - } else { - reason = WSAGetLastError(); // ENETDOWN or ENOBUFS - } + for (;;) { + int got_sig = 0; + bool got_cancel = false; + bool got_eagain = false; + uint32_t other_error = 0; + + // create event handle for overlapped i/o + intptr_t event; + if (!(event = WSACreateEvent())) + return __winsockerr(); + + struct NtOverlapped overlap = {.hEvent = event}; + bool32 ok = !StartSocketOp(handle, &overlap, &flags, arg); + if (!ok && WSAGetLastError() == kNtErrorIoPending) { + if (nonblock) { CancelIoEx(handle, &overlap); + got_eagain = true; + } else { + // atomic block on i/o completion, signal, or cancel + // it's not safe to acknowledge cancelation from here + // it's not safe to call any signal handlers from here + intptr_t sem; + if ((sem = CreateSemaphore(0, 0, 1, 0))) { + // installing semaphore before sig get makes wait atomic + struct PosixThread *pt = _pthread_self(); + pt->pt_semaphore = sem; + pt->pt_blkmask = waitmask; + atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, + memory_order_release); + if (_is_canceled()) { + got_cancel = true; + CancelIoEx(handle, &overlap); + } else if (_weaken(__sig_get) && + (got_sig = _weaken(__sig_get)(waitmask))) { + CancelIoEx(handle, &overlap); + } else { + struct timespec now = sys_clock_gettime_monotonic_nt(); + struct timespec remain = timespec_subz(deadline, now); + int64_t millis = timespec_tomillis(remain); + uint32_t waitms = MIN(millis, 0xffffffffu); + intptr_t hands[] = {event, sem}; + uint32_t wi = WSAWaitForMultipleEvents(2, hands, 0, waitms, 0); + if (wi == 1) { // semaphore was signaled by signal enqueue + CancelIoEx(handle, &overlap); + if (_weaken(__sig_get)) + got_sig = _weaken(__sig_get)(waitmask); + } else if (wi == kNtWaitTimeout) { + CancelIoEx(handle, &overlap); + got_eagain = true; + } else if (wi == -1u) { + other_error = WSAGetLastError(); + CancelIoEx(handle, &overlap); + } + } + atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release); + CloseHandle(sem); + } else { + other_error = GetLastError(); + CancelIoEx(handle, &overlap); + } } + ok = true; } - rc = 0; - } - if (!rc) { - rc = WSAGetOverlappedResult(handle, &overlap, &exchanged, true, &flags) - ? 0 - : -1; - } - WSACloseEvent(overlap.hEvent); + uint32_t exchanged = 0; + if (ok) + ok = WSAGetOverlappedResult(handle, &overlap, &exchanged, true, &flags); + uint32_t io_error = WSAGetLastError(); + WSACloseEvent(event); - if (!rc) { - return exchanged; - } - if (WSAGetLastError() == kNtErrorOperationAborted) { - if (reason) { - errno = reason; + // check if i/o completed + // this could forseeably happen even if CancelIoEx was called + if (ok) { + if (got_sig) // swallow dequeued signal + _weaken(__sig_relay)(got_sig, SI_KERNEL, waitmask); + return exchanged; + } + + // check if i/o failed + if (io_error != kNtErrorOperationAborted) { + if (got_sig) // swallow dequeued signal + _weaken(__sig_relay)(got_sig, SI_KERNEL, waitmask); + errno = __dos2errno(io_error); return -1; } - if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { - HandleInterrupt: - int handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); + + // it's now reasonable to report semaphore creation error + if (other_error) { + unassert(!got_sig); + errno = __dos2errno(other_error); + return -1; + } + + // check for non-block cancel or timeout + if (got_eagain && !got_sig && !got_cancel) + return eagain(); + + // check for thread cancelation and acknowledge + if (_check_cancel() == -1) + return -1; + + // if signal module has been linked, then + if (_weaken(__sig_get)) { + + // gobble up all unmasked pending signals + // it's now safe to recurse into signal handlers + int handler_was_called = 0; + do { + if (got_sig) + handler_was_called |= + _weaken(__sig_relay)(got_sig, SI_KERNEL, waitmask); + } while ((got_sig = _weaken(__sig_get)(waitmask))); + + // check if SIGTHR handler was called if (_check_cancel() == -1) return -1; - if (handler_was_called != 1) - goto RestartOperation; + + // check if signal handler without SA_RESTART was called + if (handler_was_called & SIG_HANDLED_NO_RESTART) + return eintr(); } - return eintr(); + + // otherwise try the i/o operation again } - return __winsockerr(); } #endif /* __x86_64__ */ diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 156699952..7e1537123 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -465,7 +465,7 @@ syscon rlimit RLIMIT_RSS 5 5 5 5 5 5 5 127 # max physical mem syscon rlimit RLIMIT_NPROC 6 6 7 7 7 7 7 127 # max number of processes; see fork()→EAGAIN; bsd consensus syscon rlimit RLIMIT_NOFILE 7 7 8 8 8 8 8 127 # max number of open files; see accept()→EMFILE/ENFILE; bsd consensus syscon rlimit RLIMIT_MEMLOCK 8 8 6 6 6 6 6 127 # max locked-in-memory address space; bsd consensus -syscon rlimit RLIMIT_AS 9\ 9 5 5 10 2 10 0 # max virtual memory size in bytes; this one actually works; fudged as RLIMIT_DATA on OpenBSD +syscon rlimit RLIMIT_AS 9 9 5 5 10 2 10 0 # max virtual memory size in bytes; this one actually works; fudged as RLIMIT_DATA on OpenBSD syscon rlimit RLIMIT_LOCKS 10 10 127 127 127 127 127 127 # max flock() / fcntl() locks; bsd consensus syscon rlimit RLIMIT_SIGPENDING 11 11 127 127 127 127 127 127 # max sigqueue() can enqueue; bsd consensus syscon rlimit RLIMIT_MSGQUEUE 12 12 127 127 127 127 127 127 # meh posix message queues; bsd consensus diff --git a/libc/sysv/consts/RLIMIT_AS.S b/libc/sysv/consts/RLIMIT_AS.S index 03c20c065..7c5fc850c 100644 --- a/libc/sysv/consts/RLIMIT_AS.S +++ b/libc/sysv/consts/RLIMIT_AS.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon rlimit,RLIMIT_AS,9 ,9,5,5,10,2,10,0 +.syscon rlimit,RLIMIT_AS,9,9,5,5,10,2,10,0 diff --git a/test/posix/signal_torture_read_test.c b/test/posix/signal_torture_read_test.c new file mode 100644 index 000000000..51af76521 --- /dev/null +++ b/test/posix/signal_torture_read_test.c @@ -0,0 +1,209 @@ +// Copyright 2024 Justine Alexandra Roberts Tunney +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted, provided that the +// above copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @fileoverview i/o signal handling torture test + * + * This test tries to trigger race conditions in the kernel's read() + * implementation, by sending a massive amount of SA_RESTART signals + * which cause churn in its internal code, and finally an interrupt. + * This should reveal if the kernel code that checks for any pending + * signals before blocking on i/o happens non-atomically. Expect the + * test to hang indefinitely in such cases. + * + * "This flag affects the behavior of interruptible functions; that is, + * those specified to fail with errno set to EINTR. If set, and a + * function specified as interruptible is interrupted by this signal, + * the function shall restart and shall not fail with EINTR unless + * otherwise specified. If an interruptible function which uses a + * timeout is restarted, the duration of the timeout following the + * restart is set to an unspecified value that does not exceed the + * original timeout value. If the flag is not set, interruptible + * functions interrupted by this signal shall fail with errno set to + * EINTR." —Quoth IEEE Std 1003.1-2017 (POSIX.1) on SA_RESTART + * + * Every OS except Windows fails this test. + * + * @see sys_readwrite_nt() + */ + +#define COUNT 1000 + +volatile sig_atomic_t got_sigusr1; +volatile sig_atomic_t got_sigusr2; +volatile sig_atomic_t thread_ready; +volatile sig_atomic_t read_interrupted; + +void sigusr1_handler(int signo) { + ++got_sigusr1; + // printf("got %d sigusr1\n", got_sigusr1); +} + +void sigusr2_handler(int signo) { + ++got_sigusr2; + // printf("got %d sigusr2\n", got_sigusr2); +} + +void setup_signal_handlers() { + struct sigaction sa; + + // Set up SIGUSR1 handler with SA_RESTART + sa.sa_handler = sigusr1_handler; + sa.sa_flags = SA_RESTART; // Signal handler with SA_RESTART + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, NULL) == -1) + exit(97); + + // Set up SIGUSR2 handler without SA_RESTART + sa.sa_handler = sigusr2_handler; + sa.sa_flags = 0; // Signal handler without SA_RESTART + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR2, &sa, NULL) == -1) + exit(98); +} + +void block_signals() { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigaddset(&set, SIGUSR2); + if (pthread_sigmask(SIG_BLOCK, &set, 0)) + exit(99); +} + +void *thread_func(void *arg) { + int *pipefd = (int *)arg; + char buf[1]; + ssize_t ret; + + // Unblock SIGUSR1 and SIGUSR2 in this thread + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigaddset(&set, SIGUSR2); + if (pthread_sigmask(SIG_UNBLOCK, &set, 0)) + exit(100); + + // Indicate that the thread is ready + thread_ready = 1; + + // Call read() on the pipe + ret = read(pipefd[0], buf, 1); + if (ret == -1) { + if (errno == EINTR) { + read_interrupted = 1; + // printf("read interrupted\n"); + } else { + perror("read"); + exit(78); + } + } else { + exit(77); + } + + return NULL; +} + +int main() { + int pipefd[2]; + pthread_t thread; + + // this test exposes bugs in macos + if (IsXnu()) + return 0; + + // this test exposes bugs in linux + if (IsLinux()) + return 0; + + // this test exposes bugs in netbsd + if (IsNetbsd()) + return 0; + + // this test exposes bugs in freebsd + if (IsFreebsd()) + return 0; + + // this test exposes bugs in openbsd + if (IsOpenbsd()) + return 0; + + ShowCrashReports(); + + // Block SIGUSR1 and SIGUSR2 in the main thread + block_signals(); + + // Set up signal handlers + setup_signal_handlers(); + + // Create a pipe + if (pipe(pipefd) == -1) + exit(95); + + // Create a thread + if (pthread_create(&thread, NULL, thread_func, pipefd) != 0) + exit(90); + + // Wait until the thread is ready + while (!thread_ready) + if (pthread_yield_np()) + exit(101); + + // Send SIGUSR1 signals + // This will cause read() to restart internally + for (int i = 0; i < COUNT; i++) { + if (pthread_kill(thread, SIGUSR1) != 0) + exit(91); + if (i % (COUNT / 10) == 0) + usleep(1); + } + + // Send SIGUSR2 to the thread + // This will trigger an EINTR + fflush(stdout); + if (pthread_kill(thread, SIGUSR2)) + exit(92); + + // Join the thread + if (pthread_join(thread, NULL)) + exit(93); + + // Close the pipe + close(pipefd[0]); + close(pipefd[1]); + + // Check if read() was interrupted by EINTR + if (!read_interrupted) + exit(94); + + if (!got_sigusr1) + exit(60); + if (!got_sigusr2) + exit(61); + + // printf("got %d got_sigusr1\n", got_sigusr1); + + CheckForMemoryLeaks(); + return 0; +}