Put more thought into i/o polyfills

wait4() is now solid enough to run `make -j100` on Windows. You can now
use MSG_DONTWAIT on Windows. There was a handle leak in accept() that's
been fixed. Our WIN32 overlapped i/o code has been simplified. Priority
class now inherits into subprocesses, so the verynice command will work
and the signal mask will now be inherited by execve() and posix_spawn()
This commit is contained in:
Justine Tunney 2023-11-06 16:38:44 -08:00
parent 736fdb757a
commit e961385e55
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
52 changed files with 679 additions and 487 deletions

View file

@ -164,6 +164,6 @@ int __getcwd(char *buf, size_t size) {
} else {
rc = sys_getcwd_metal(buf, size);
}
STRACE("__getcwd([%#hhs], %'zu) → %d% m", rc != -1 ? buf : "n/a", size, rc);
STRACE("getcwd([%#hhs], %'zu) → %d% m", rc != -1 ? buf : "n/a", size, rc);
return rc;
}

View file

@ -126,7 +126,8 @@ textwindows int ntspawn(
if (CreateProcess(sb->path, sb->cmdline, 0, 0, true,
dwCreationFlags | kNtCreateUnicodeEnvironment |
kNtExtendedStartupinfoPresent |
kNtInheritParentAffinity,
kNtInheritParentAffinity |
GetPriorityClass(GetCurrentProcess()),
sb->envblock, opt_lpCurrentDirectory,
&info.StartupInfo, opt_out_lpProcessInformation)) {
rc = 0;

View file

@ -32,11 +32,7 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
bool restartable) {
int sig;
if (_check_cancel() == -1) return -1;
if ((sig = __sig_get(waitmask))) {
int handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
if (!restartable || handler_was_called == 1) return eintr();
}
if ((sig = __sig_get(waitmask))) goto HandleSignal;
int expect = 0;
atomic_int futex = 0;
struct PosixThread *pt = _pthread_self();
@ -45,9 +41,12 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
if (ok && (sig = __sig_get(waitmask))) {
HandleSignal:
int handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
if (!restartable || handler_was_called == 1) return eintr();
if (!restartable || (handler_was_called & SIG_HANDLED_NO_RESTART)) {
return eintr();
}
}
return 0;
}

View file

@ -55,6 +55,8 @@
#include "libc/thread/tls.h"
#ifdef __x86_64__
#define POLL_INTERVAL_MS 10
// Polls on the New Technology.
//
// This function is used to implement poll() and select(). You may poll
@ -184,7 +186,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
remain = timespec_sub(deadline, now);
millis = timespec_tomillis(remain);
waitfor = MIN(millis, 0xffffffffu);
waitfor = MIN(waitfor, __SIG_POLL_INTERVAL_MS);
waitfor = MIN(waitfor, POLL_INTERVAL_MS);
if (waitfor) {
POLLTRACE("poll() sleeping for %'d out of %'lu ms", waitfor,
timespec_tomillis(remain));

View file

@ -723,8 +723,6 @@ static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) {
int sig;
int64_t sem;
uint32_t wi, ms = -1;
int handler_was_called;
struct PosixThread *pt;
if (!__ttyconf.vmin) {
if (!__ttyconf.vtime) {
return 0; // non-blocking w/o raising eagain
@ -732,31 +730,24 @@ static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) {
ms = __ttyconf.vtime * 100;
}
}
if (f->flags & _O_NONBLOCK) {
return eagain(); // standard unix non-blocking
}
if (_check_cancel() == -1) return -1;
if ((sig = __sig_get(waitmask))) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
if (handler_was_called != 1) return -2;
return eintr();
}
pt = _pthread_self();
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
pthread_cleanup_push((void *)CloseHandle, (void *)sem);
if (f->flags & _O_NONBLOCK) return eagain();
if ((sig = __sig_get(waitmask))) goto DeliverSignal;
struct PosixThread *pt = _pthread_self();
pt->pt_blkmask = waitmask;
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release);
wi = WaitForMultipleObjects(2, (int64_t[2]){__keystroke.cin, sem}, 0, ms);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
pthread_cleanup_pop(true);
CloseHandle(sem);
if (wi == kNtWaitTimeout) return 0; // vtime elapsed
if (wi == 0) return -2; // console data
if (wi != 1) return __winerr(); // wait failed
if (!(sig = __sig_get(waitmask))) return eintr();
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
DeliverSignal:
int handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
if (handler_was_called != 1) return -2;
if (!(handler_was_called & SIG_HANDLED_NO_RESTART)) return -2;
return eintr();
}

View file

@ -16,15 +16,12 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/createfileflags.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/events.h"
@ -34,28 +31,10 @@
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/stdio/sysparam.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
struct ReadwriteResources {
int64_t handle;
struct NtOverlapped *overlap;
};
static void UnwindReadwrite(void *arg) {
uint32_t got;
struct ReadwriteResources *rwc = arg;
CancelIoEx(rwc->handle, rwc->overlap);
GetOverlappedResult(rwc->handle, rwc->overlap, &got, true);
CloseHandle(rwc->overlap->hEvent);
}
/**
* Runs code that's common to read/write/pread/pwrite/etc on Windows.
*
@ -64,17 +43,11 @@ static void UnwindReadwrite(void *arg) {
*/
textwindows ssize_t
sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
int64_t handle, uint64_t waitmask,
int64_t handle, sigset_t waitmask,
bool32 ReadOrWriteFile(int64_t, void *, uint32_t, uint32_t *,
struct NtOverlapped *)) {
bool32 ok;
int sig = 0;
int sig;
uint32_t exchanged;
int olderror = errno;
bool eagained = false;
bool canceled = false;
int handler_was_called;
struct PosixThread *pt;
struct Fd *f = g_fds.p + fd;
// win32 i/o apis generally take 32-bit values thus we implicitly
@ -106,33 +79,26 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
}
RestartOperation:
bool eagained = false;
// check for signals and cancelation
if (_check_cancel() == -1) return -1; // ECANCELED
if ((sig = __sig_get(waitmask))) goto HandleInterrupt;
// 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};
struct ReadwriteResources rwc = {handle, &overlap};
pthread_cleanup_push(UnwindReadwrite, &rwc);
ok = ReadOrWriteFile(handle, data, size, 0, &overlap);
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 if (_check_cancel()) {
// _check_cancel() can go three ways:
// 1. it'll return 0 if we're fine and no thread cancelation happened
// 2. it'll pthread_exit() and cleanup, when cancelation was deferred
// 3. it'll return -1 and raise ECANCELED if a cancelation was masked
CancelIoEx(handle, &overlap);
canceled = true;
} else if ((sig = __sig_get(waitmask))) {
// we've dequeued a signal that was pending per caller's old sigmask
// we can't call the signal handler until we release win32 resources
CancelIoEx(handle, &overlap);
} 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;
@ -147,30 +113,13 @@ RestartOperation:
if (ok) {
ok = GetOverlappedResult(handle, &overlap, &exchanged, true);
}
pthread_cleanup_pop(false);
CloseHandle(overlap.hEvent);
// if we acknowledged a pending masked mode cancelation request then
// we must pass it to the caller immediately now that cleanup's done
if (canceled) {
return ecanceled();
}
// if we removed a pending signal then we must raise it
// it's now safe to call a signal handler that longjmps
if (sig) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
} else {
handler_was_called = 0;
}
// if i/o succeeded then return its result
if (ok) {
if (!pwriting && seekable) {
f->pointer = offset + exchanged;
}
errno = olderror;
return exchanged;
}
@ -180,17 +129,15 @@ RestartOperation:
if (eagained) {
return eagain();
}
// at this point the i/o must have been canceled due to a signal.
// this could be because we found the signal earlier and canceled
// ourself. otherwise it's due to a kill from another thread that
// added something to our mask and canceled our i/o, so we check.
if (!handler_was_called && (sig = __sig_get(waitmask))) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
}
// read() is @restartable unless non-SA_RESTART hands were called
if (handler_was_called != 1) {
goto RestartOperation;
// otherwise it must be due to a kill() via __sig_cancel()
if ((sig = __sig_get(waitmask))) {
HandleInterrupt:
int handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1; // possible if we SIGTHR'd
// read() is @restartable unless non-SA_RESTART hands were called
if (!(handler_was_called & SIG_HANDLED_NO_RESTART)) {
goto RestartOperation;
}
}
return eintr();
}

View file

@ -18,8 +18,6 @@
*/
#include "libc/sysv/consts/sig.h"
#include "ape/sections.internal.h"
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
@ -30,34 +28,26 @@
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsf.h"
#include "libc/intrin/describebacktrace.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/popcnt.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/context.h"
#include "libc/nt/enum/exceptionhandleractions.h"
#include "libc/nt/enum/signal.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/errors.h"
#include "libc/nt/runtime.h"
#include "libc/nt/signals.h"
#include "libc/nt/struct/context.h"
#include "libc/nt/struct/ntexceptionpointers.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/ss.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
/**
@ -206,16 +196,19 @@ textwindows int __sig_raise(volatile int sig, int sic) {
memory_order_acquire);
// call the user's signal handler
char ssbuf[2][128];
char ssbuf[128];
siginfo_t si = {.si_signo = sig, .si_code = sic};
STRACE("__sig_raise(%G, %t) mask %s → %s", sig, __sig_handler(rva),
(DescribeSigset)(ssbuf[0], 0, &ctx.uc_sigmask),
(DescribeSigset)(ssbuf[1], 0, (sigset_t *)&pt->tib->tib_sigmask));
STRACE("__sig_raise(%G, %t) mask %s", sig, __sig_handler(rva),
(DescribeSigset)(ssbuf, 0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
(void)ssbuf;
// record this handler
handler_was_called |= (flags & SA_RESTART) ? 2 : 1;
if (flags & SA_RESTART) {
handler_was_called |= SIG_HANDLED_SA_RESTART;
} else {
handler_was_called |= SIG_HANDLED_NO_RESTART;
}
}
// restore sigmask
@ -320,12 +313,18 @@ static int __sig_killer(struct PosixThread *pt, int sig, int sic) {
__sig_terminate(sig);
}
// ignore signals already pending
uintptr_t th = _pthread_syshand(pt);
if (atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire) &
(1ull << (sig - 1))) {
return 0;
}
// take control of thread
// suspending the thread happens asynchronously
// however getting the context blocks until it's frozen
static pthread_spinlock_t killer_lock;
pthread_spin_lock(&killer_lock);
uintptr_t th = _pthread_syshand(pt);
if (SuspendThread(th) == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError());
pthread_spin_unlock(&killer_lock);
@ -414,6 +413,10 @@ textwindows void __sig_generate(int sig, int sic) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
if (atomic_load_explicit(&__sig.pending, memory_order_acquire) &
(1ull << (sig - 1))) {
return;
}
BLOCK_SIGNALS;
_pthread_lock();
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
@ -428,7 +431,6 @@ textwindows void __sig_generate(int sig, int sic) {
// choose this thread if it isn't masking sig
if (!(atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))) {
STRACE("generating %G by killing %d", sig, _pthread_tid(pt));
_pthread_ref(pt);
mark = pt;
break;
@ -439,7 +441,6 @@ textwindows void __sig_generate(int sig, int sic) {
if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) &&
!(atomic_load_explicit(&pt->pt_blkmask, memory_order_relaxed) &
(1ull << (sig - 1)))) {
STRACE("generating %G by unblocking %d", sig, _pthread_tid(pt));
_pthread_ref(pt);
mark = pt;
break;
@ -450,7 +451,6 @@ textwindows void __sig_generate(int sig, int sic) {
__sig_killer(mark, sig, sic);
_pthread_unref(mark);
} else {
STRACE("all threads block %G so adding to pending signals of process", sig);
atomic_fetch_or_explicit(&__sig.pending, 1ull << (sig - 1),
memory_order_relaxed);
}

View file

@ -3,13 +3,8 @@
#include "libc/calls/struct/sigset.h"
#include "libc/thread/posixthread.internal.h"
#define __SIG_LOCK_INTERVAL_MS 1000 /* semaphore synchronization: solid */
#define __SIG_SIG_INTERVAL_MS 1000 /* posix signal polyfill also solid */
#define __SIG_PROC_INTERVAL_MS 1000 /* process waiting also pretty good */
#define __SIG_IO_INTERVAL_MS 1000 /* read/write cancel/notify is good */
#define __SIG_POLL_INTERVAL_MS 20 /* poll on windows is dumpster fire */
#define __SIG_LOGGING_INTERVAL_MS 1700
#define __SIG_QUEUE_LENGTH 32
#define SIG_HANDLED_NO_RESTART 1
#define SIG_HANDLED_SA_RESTART 2
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_

View file

@ -41,10 +41,10 @@ int sys_futex(int *, int, int, const struct timespec *, int *);
static inline struct timespec timespec_fromseconds(int64_t __x) {
return (struct timespec){__x};
}
static inline bool timespec_iszero(struct timespec __ts) {
static inline int timespec_iszero(struct timespec __ts) {
return !(__ts.tv_sec | __ts.tv_nsec);
}
static inline bool timespec_isvalid(struct timespec __ts) {
static inline int timespec_isvalid(struct timespec __ts) {
return __ts.tv_sec >= 0 && __ts.tv_nsec + 0ull < 1000000000ull;
}
#endif /* _COSMO_SOURCE */

View file

@ -39,10 +39,10 @@ static inline struct timeval timeval_fromseconds(int64_t __x) {
static inline struct timespec timeval_totimespec(struct timeval __tv) {
return (struct timespec){__tv.tv_sec, __tv.tv_usec * 1000};
}
static inline bool timeval_iszero(struct timeval __tv) {
static inline int timeval_iszero(struct timeval __tv) {
return !(__tv.tv_sec | __tv.tv_usec);
}
static inline bool timeval_isvalid(struct timeval __tv) {
static inline int timeval_isvalid(struct timeval __tv) {
return __tv.tv_sec >= 0 && __tv.tv_usec + 0ull < 1000000ull;
}
#endif /* _COSMO_SOURCE */

View file

@ -17,17 +17,14 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/ucontext.h"
#include "libc/calls/struct/sigset.h"
#include "libc/runtime/runtime.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/dce.h"
#include "libc/intrin/atomic.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/tls.h"
int __tailcontext(const ucontext_t *);
static int __contextmask(const sigset_t *opt_set, sigset_t *opt_out_oldset) {
// now context switching needs to go 14x slower
return sigprocmask(SIG_SETMASK, opt_set, opt_out_oldset);
}
/**
* Sets machine context.
*
@ -37,14 +34,31 @@ static int __contextmask(const sigset_t *opt_set, sigset_t *opt_out_oldset) {
* @see getcontext()
*/
int setcontext(const ucontext_t *uc) {
if (__contextmask(&uc->uc_sigmask, 0)) return -1;
if (IsWindows()) {
atomic_store_explicit(&__get_tls()->tib_sigmask, uc->uc_sigmask,
memory_order_release);
} else {
sys_sigprocmask(SIG_SETMASK, &uc->uc_sigmask, 0);
}
return __tailcontext(uc);
}
int __getcontextsig(ucontext_t *uc) {
return __contextmask(0, &uc->uc_sigmask);
if (IsWindows()) {
uc->uc_sigmask =
atomic_load_explicit(&__get_tls()->tib_sigmask, memory_order_acquire);
return 0;
} else {
return sys_sigprocmask(SIG_SETMASK, 0, &uc->uc_sigmask);
}
}
int __swapcontextsig(ucontext_t *x, const ucontext_t *y) {
return __contextmask(&y->uc_sigmask, &x->uc_sigmask);
if (IsWindows()) {
x->uc_sigmask = atomic_exchange_explicit(
&__get_tls()->tib_sigmask, y->uc_sigmask, memory_order_acquire);
return 0;
} else {
return sys_sigprocmask(SIG_SETMASK, &y->uc_sigmask, &x->uc_sigmask);
}
}

View file

@ -73,7 +73,8 @@ imaxdiv_t imaxdiv(intmax_t, intmax_t) pureconst;
#endif
#if (__GNUC__ * 100 + __GNUC_MINOR__ >= 406 || defined(__llvm__)) && \
!defined(__STRICT_ANSI__) && defined(_COSMO_SOURCE)
!defined(__STRICT_ANSI__) && defined(_COSMO_SOURCE) && \
!defined(__COSMOCC__)
int128_t i128abs(int128_t)
libcesque pureconst;
int128_t strtoi128(const char *, char **, int) paramsnonnull((1));

View file

@ -71,7 +71,7 @@ TryAgain:
switch (__imp_GetLastError()) {
case kNtErrorPipeBusy:
if (micros >= 1024) __imp_Sleep(micros / 1024);
if (micros / 1024 < __SIG_IO_INTERVAL_MS) micros <<= 1;
if (micros < 1024 * 1024) micros <<= 1;
goto TryAgain;
case kNtErrorAccessDenied:
// GetNtOpenFlags() always greedily requests execute permissions

View file

@ -50,7 +50,7 @@ TryAgain:
nDefaultTimeOutMs, opt_lpSecurityAttributes);
if (hServer == -1 && __imp_GetLastError() == kNtErrorPipeBusy) {
if (micros >= 1024) __imp_Sleep(micros / 1024);
if (micros / 1024 < __SIG_IO_INTERVAL_MS) micros <<= 1;
if (micros < 1024 * 1024) micros <<= 1;
goto TryAgain;
}
if (hServer == -1) __winerr();

View file

@ -23,9 +23,7 @@
static bool _pthread_deref(struct PosixThread *pt) {
int refs = atomic_load_explicit(&pt->pt_refs, memory_order_acquire);
if (!refs) return true;
unassert(refs > 0);
return !atomic_fetch_sub(&pt->pt_refs, 1);
return !refs || !atomic_fetch_sub(&pt->pt_refs, 1);
}
void _pthread_unref(struct PosixThread *pt) {

View file

@ -42,7 +42,7 @@ textwindows int __sig_mask(int how, const sigset_t *neu, sigset_t *old) {
if (how == SIG_BLOCK) {
oldmask = atomic_fetch_or_explicit(mask, *neu, memory_order_acq_rel);
} else if (how == SIG_UNBLOCK) {
oldmask = atomic_fetch_and_explicit(mask, *neu, memory_order_acq_rel);
oldmask = atomic_fetch_and_explicit(mask, ~*neu, memory_order_acq_rel);
} else { // SIG_SETMASK
oldmask = atomic_exchange_explicit(mask, *neu, memory_order_acq_rel);
}

View file

@ -30,9 +30,7 @@ __msabi extern typeof(WSARecv) *const __imp_WSARecv;
/**
* Receives data from Windows socket.
*
* @return 0 on success, or -1 on failure
* @note this wrapper takes care of ABI, STRACE(), and __winerr()
*/
textwindows int WSARecv(
uint64_t s, const struct NtIovec *inout_lpBuffers, uint32_t dwBufferCount,
@ -57,9 +55,6 @@ textwindows int WSARecv(
if (opt_out_lpNumberOfBytesRecvd) {
*opt_out_lpNumberOfBytesRecvd = NumberOfBytesRecvd;
}
if (rc == -1) {
__winsockerr();
}
if (UNLIKELY(__strace > 0) && strace_enabled(0) > 0) {
kprintf(STRACE_PROLOGUE "WSARecv(%lu, [", s);
DescribeIovNt(inout_lpBuffers, dwBufferCount,
@ -73,9 +68,6 @@ textwindows int WSARecv(
rc = __imp_WSARecv(s, inout_lpBuffers, dwBufferCount,
opt_out_lpNumberOfBytesRecvd, inout_lpFlags,
opt_inout_lpOverlapped, opt_lpCompletionRoutine);
if (rc == -1) {
__winsockerr();
}
#endif
return rc;
}

View file

@ -29,9 +29,7 @@ __msabi extern typeof(WSARecvFrom) *const __imp_WSARecvFrom;
/**
* Receives data from Windows socket.
*
* @return 0 on success, or -1 on failure
* @note this wrapper takes care of ABI, STRACE(), and __winerr()
*/
textwindows int WSARecvFrom(
uint64_t s, const struct NtIovec *inout_lpBuffers, uint32_t dwBufferCount,
@ -52,9 +50,6 @@ textwindows int WSARecvFrom(
if (opt_out_lpNumberOfBytesRecvd) {
*opt_out_lpNumberOfBytesRecvd = NumberOfBytesRecvd;
}
if (rc == -1) {
__winerr();
}
if (UNLIKELY(__strace > 0) && strace_enabled(0) > 0) {
kprintf(STRACE_PROLOGUE "WSARecvFrom(%lu, [", s);
DescribeIovNt(inout_lpBuffers, dwBufferCount,
@ -69,9 +64,6 @@ textwindows int WSARecvFrom(
opt_out_lpNumberOfBytesRecvd, inout_lpFlags,
opt_out_fromsockaddr, opt_inout_fromsockaddrlen,
opt_inout_lpOverlapped, opt_lpCompletionRoutine);
if (rc == -1) {
__winerr();
}
#endif
return rc;
}

View file

@ -22,6 +22,7 @@
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/kprintf.h"
#include "libc/mem/mem.h"
#include "libc/nt/enum/processaccess.h"
@ -46,7 +47,7 @@ textwindows int sys_execve_nt(const char *program, char *const argv[],
char *const envp[]) {
// execve() needs to be @asyncsignalsafe
sigset_t m = __sig_block();
sigset_t sigmask = __sig_block();
_pthread_lock();
// new process should be a child of our parent
@ -55,10 +56,14 @@ textwindows int sys_execve_nt(const char *program, char *const argv[],
if (!(hParentProcess = OpenProcess(
kNtProcessDupHandle | kNtProcessCreateProcess, false, ppid))) {
_pthread_unlock();
__sig_unblock(m);
__sig_unblock(sigmask);
return -1;
}
// inherit signal mask
char maskvar[6 + 21];
FormatUint64(stpcpy(maskvar, "_MASK="), sigmask);
// define stdio handles for the spawned subprocess
struct NtStartupInfo si = {
.cb = sizeof(struct NtStartupInfo),
@ -80,21 +85,21 @@ textwindows int sys_execve_nt(const char *program, char *const argv[],
&lpExplicitHandles, &dwExplicitHandleCount))) {
CloseHandle(hParentProcess);
_pthread_unlock();
__sig_unblock(m);
__sig_unblock(sigmask);
return -1;
}
// launch the process
struct NtProcessInformation pi;
int rc = ntspawn(AT_FDCWD, program, argv, envp, (char *[]){fdspec, 0}, 0, 0,
hParentProcess, lpExplicitHandles, dwExplicitHandleCount,
&si, &pi);
int rc = ntspawn(AT_FDCWD, program, argv, envp,
(char *[]){fdspec, maskvar, 0}, 0, 0, hParentProcess,
lpExplicitHandles, dwExplicitHandleCount, &si, &pi);
__undescribe_fds(hParentProcess, lpExplicitHandles, dwExplicitHandleCount);
if (rc == -1) {
free(fdspec);
CloseHandle(hParentProcess);
_pthread_unlock();
__sig_unblock(m);
__sig_unblock(sigmask);
if (GetLastError() == kNtErrorSharingViolation) {
return etxtbsy();
} else {

View file

@ -401,7 +401,7 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
}
}
if (rc == -1) {
__proc_free(proc);
dll_make_first(&__proc.free, &proc->elem);
}
ftrace_enabled(+1);
strace_enabled(+1);

View file

@ -248,9 +248,9 @@ static textwindows errno_t posix_spawn_nt_impl(
struct SpawnFds fds = {0};
int64_t dirhand = AT_FDCWD;
int64_t *lpExplicitHandles = 0;
sigset_t sigmask = __sig_block();
uint32_t dwExplicitHandleCount = 0;
int64_t hCreatorProcess = GetCurrentProcess();
sigset_t m = __sig_block();
// reserve process tracking object
__proc_lock();
@ -266,11 +266,11 @@ static textwindows errno_t posix_spawn_nt_impl(
free(fdspec);
if (proc) {
__proc_lock();
__proc_free(proc);
dll_make_first(&__proc.free, &proc->elem);
__proc_unlock();
}
spawnfds_destroy(&fds);
__sig_unblock(m);
__sig_unblock(sigmask);
errno = e;
return err;
}
@ -360,13 +360,17 @@ static textwindows errno_t posix_spawn_nt_impl(
}
}
// inherit signal mask
char maskvar[6 + 21];
FormatUint64(stpcpy(maskvar, "_MASK="), sigmask);
// launch process
int rc = -1;
struct NtProcessInformation procinfo;
if (!envp) envp = environ;
if ((fdspec = __describe_fds(fds.p, fds.n, &startinfo, hCreatorProcess,
&lpExplicitHandles, &dwExplicitHandleCount))) {
rc = ntspawn(dirhand, path, argv, envp, (char *[]){fdspec, 0},
rc = ntspawn(dirhand, path, argv, envp, (char *[]){fdspec, maskvar, 0},
dwCreationFlags, lpCurrentDirectory, 0, lpExplicitHandles,
dwExplicitHandleCount, &startinfo, &procinfo);
}

View file

@ -32,15 +32,20 @@
* - `POSIX_SPAWN_SETSCHEDPARAM`
* - `POSIX_SPAWN_SETSCHEDULER`
* - `POSIX_SPAWN_SETSID`
* - `POSIX_SPAWN_SETRLIMIT`
* @return 0 on success, or errno on error
* @raise EINVAL if `flags` has invalid bits
*/
int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
if (flags &
~(POSIX_SPAWN_USEVFORK | POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP |
POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK |
POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER |
POSIX_SPAWN_SETSID | POSIX_SPAWN_SETRLIMIT)) {
if (flags & ~(POSIX_SPAWN_USEVFORK | //
POSIX_SPAWN_RESETIDS | //
POSIX_SPAWN_SETPGROUP | //
POSIX_SPAWN_SETSIGDEF | //
POSIX_SPAWN_SETSIGMASK | //
POSIX_SPAWN_SETSCHEDPARAM | //
POSIX_SPAWN_SETSCHEDULER | //
POSIX_SPAWN_SETSID | //
POSIX_SPAWN_SETRLIMIT)) {
return EINVAL;
}
(*attr)->flags = flags;

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/proc/posix_spawn.h"
#include "libc/proc/posix_spawn.internal.h"
/**
@ -25,14 +24,14 @@
* Setting `pgroup` to zero will ensure newly created processes are
* placed within their own brand new process group.
*
* This setter also sets the `POSIX_SPAWN_SETPGROUP` flag.
* You also need to pass `POSIX_SPAWN_SETPGROUP` to
* posix_spawnattr_setflags() for it to take effect.
*
* @param attr was initialized by posix_spawnattr_init()
* @param pgroup is the process group id, or 0 for self
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, int pgroup) {
(*attr)->flags |= POSIX_SPAWN_SETPGROUP;
(*attr)->pgroup = pgroup;
return 0;
}

View file

@ -26,7 +26,8 @@
/**
* Sets resource limit on spawned process.
*
* Using this setter automatically sets `POSIX_SPAWN_SETRLIMIT`.
* You also need to pass `POSIX_SPAWN_SETRLIMIT` to
* posix_spawnattr_setflags() for it to take effect.
*
* @return 0 on success, or errno on error
* @raise EINVAL if resource is invalid
@ -34,7 +35,6 @@
int posix_spawnattr_setrlimit(posix_spawnattr_t *attr, int resource,
const struct rlimit *rlim) {
if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) {
(*attr)->flags |= POSIX_SPAWN_SETRLIMIT;
(*attr)->rlimset |= 1u << resource;
(*attr)->rlim[resource] = *rlim;
return 0;

View file

@ -16,14 +16,13 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/sched_param.h"
#include "libc/proc/posix_spawn.h"
#include "libc/proc/posix_spawn.internal.h"
/**
* Specifies scheduler parameter override for spawned process.
*
* Using this setter automatically sets `POSIX_SPAWN_SETSCHEDPARAM`.
* You also need to pass `POSIX_SPAWN_SETSCHEDPARAM` to
* posix_spawnattr_setflags() for it to take effect.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedparam receives the result
@ -31,7 +30,6 @@
*/
int posix_spawnattr_setschedparam(posix_spawnattr_t *attr,
const struct sched_param *schedparam) {
(*attr)->flags |= POSIX_SPAWN_SETSCHEDPARAM;
(*attr)->schedparam = *schedparam;
return 0;
}

View file

@ -16,19 +16,18 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/proc/posix_spawn.h"
#include "libc/proc/posix_spawn.internal.h"
/**
* Specifies scheduler policy override for spawned process.
*
* Using this setter automatically sets `POSIX_SPAWN_SETSCHEDULER`.
* You also need to pass `POSIX_SPAWN_SETSCHEDULER` to
* posix_spawnattr_setflags() for it to take effect.
*
* @param attr was initialized by posix_spawnattr_init()
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *attr, int schedpolicy) {
(*attr)->flags |= POSIX_SPAWN_SETSCHEDULER;
(*attr)->schedpolicy = schedpolicy;
return 0;
}

View file

@ -16,8 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/sigset.h"
#include "libc/proc/posix_spawn.h"
#include "libc/proc/posix_spawn.internal.h"
/**
@ -32,13 +30,13 @@
* set the signals to `SIG_IGN` earlier (since posix_spawn() will not
* issue O(128) system calls just to be totally pedantic about that).
*
* Using this setter automatically sets `POSIX_SPAWN_SETSIGDEF`.
* You also need to pass `POSIX_SPAWN_SETSIGDEF` to
* posix_spawnattr_setflags() for it to take effect.
*
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setsigdefault(posix_spawnattr_t *attr,
const sigset_t *sigdefault) {
(*attr)->flags |= POSIX_SPAWN_SETSIGDEF;
(*attr)->sigdefault = *sigdefault;
return 0;
}

View file

@ -16,20 +16,18 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/sigset.h"
#include "libc/proc/posix_spawn.h"
#include "libc/proc/posix_spawn.internal.h"
/**
* Specifies signal mask for sigprocmask() in child process.
*
* This setter also sets the `POSIX_SPAWN_SETSIGMASK` flag.
* You also need to pass `POSIX_SPAWN_SETSIGMASK` to
* posix_spawnattr_setflags() for it to take effect.
*
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setsigmask(posix_spawnattr_t *attr,
const sigset_t *sigmask) {
(*attr)->flags |= POSIX_SPAWN_SETSIGMASK;
(*attr)->sigmask = *sigmask;
return 0;
}

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
@ -37,6 +36,7 @@
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/events.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/filetime.h"
@ -59,95 +59,167 @@
struct Procs __proc;
static textwindows void GetProcessStats(int64_t h, struct rusage *ru) {
static textwindows void __proc_stats(int64_t h, struct rusage *ru) {
bzero(ru, sizeof(*ru));
struct NtProcessMemoryCountersEx memcount = {sizeof(memcount)};
unassert(GetProcessMemoryInfo(h, &memcount, sizeof(memcount)));
GetProcessMemoryInfo(h, &memcount, sizeof(memcount));
ru->ru_maxrss = memcount.PeakWorkingSetSize / 1024;
ru->ru_majflt = memcount.PageFaultCount;
struct NtFileTime createtime, exittime;
struct NtFileTime kerneltime, usertime;
unassert(GetProcessTimes(h, &createtime, &exittime, &kerneltime, &usertime));
GetProcessTimes(h, &createtime, &exittime, &kerneltime, &usertime);
ru->ru_utime = WindowsDurationToTimeVal(ReadFileTime(usertime));
ru->ru_stime = WindowsDurationToTimeVal(ReadFileTime(kerneltime));
struct NtIoCounters iocount;
unassert(GetProcessIoCounters(h, &iocount));
GetProcessIoCounters(h, &iocount);
ru->ru_inblock = iocount.ReadOperationCount;
ru->ru_oublock = iocount.WriteOperationCount;
}
// performs accounting on exited process
// multiple threads can wait on a process
// it's important that only one calls this
textwindows int __proc_harvest(struct Proc *pr, bool iswait4) {
int sic = 0;
uint32_t status;
struct rusage ru;
GetExitCodeProcess(pr->handle, &status);
if (status == kNtStillActive) return 0;
__proc_stats(pr->handle, &ru);
rusage_add(&pr->ru, &ru);
rusage_add(&__proc.ruchlds, &ru);
if ((status & 0xFF000000u) == 0x23000000u) {
// handle child execve()
CloseHandle(pr->handle);
pr->handle = status & 0x00FFFFFF;
} else {
// handle child _Exit()
if (status == 0xc9af3d51u) {
status = kNtStillActive;
}
pr->wstatus = status;
if (!iswait4 && !pr->waiters && !__proc.waiters &&
(__sighandrvas[SIGCHLD] == (uintptr_t)SIG_IGN ||
(__sighandflags[SIGCHLD] & SA_NOCLDWAIT))) {
// perform automatic zombie reaping
dll_remove(&__proc.list, &pr->elem);
dll_make_first(&__proc.free, &pr->elem);
CloseHandle(pr->handle);
} else {
// transitions process to zombie state
// wait4 is responsible for reaping it
pr->status = PROC_ZOMBIE;
dll_remove(&__proc.list, &pr->elem);
dll_make_first(&__proc.zombies, &pr->elem);
SetEvent(__proc.haszombies);
if (!pr->waiters && !__proc.waiters) {
if (WIFSIGNALED(status)) {
sic = CLD_KILLED;
} else {
sic = CLD_EXITED;
}
}
}
}
return sic;
}
static textwindows dontinstrument uint32_t __proc_worker(void *arg) {
__bootstrap_tls(&__proc.tls, __builtin_frame_address(0));
struct CosmoTib tls;
__bootstrap_tls(&tls, __builtin_frame_address(0));
for (;;) {
// assemble a group of processes to wait on. if more than 64
// children exist, then we'll use a small timeout and select
// processes with a shifting window via a double linked list
struct rusage ru;
// if fewer than 64 processes exist then we'll also wait for
// process birth notifications, and wait on them immediately
int64_t handles[64];
int sic, dosignal = 0;
struct Proc *pr, *objects[64];
struct Proc *objects[64];
uint32_t millis, i, n = 0;
struct Dll *e, *e2, *samples = 0;
uint32_t millis, status, i, n = 1;
__proc_lock();
handles[0] = __proc.onstart;
for (e = dll_first(__proc.list); e && n < 64; ++n, e = e2) {
pr = PROC_CONTAINER(e);
for (e = dll_first(__proc.list); e && n < 64; e = e2) {
struct Proc *pr = PROC_CONTAINER(e);
e2 = dll_next(__proc.list, e);
// cycle process to end of list
dll_remove(&__proc.list, e);
dll_make_last(&samples, e);
handles[n] = pr->handle;
objects[n] = pr;
// don't bother waiting if it's already awaited
if (!pr->waiters) {
handles[n] = pr->handle;
objects[n] = pr;
++pr->waiters;
++n;
}
}
dll_make_last(&__proc.list, samples);
__proc_unlock();
// wait for win32 to report any status change
millis = n == 64 ? 20 : -1u;
unassert((i = WaitForMultipleObjects(n, handles, false, millis)) != -1u);
i &= ~kNtWaitAbandoned;
if (!i || i == kNtWaitTimeout) continue;
GetExitCodeProcess(handles[i], &status);
if (status == kNtStillActive) continue;
GetProcessStats(handles[i], &ru);
// update data structures and notify folks
__proc_lock();
pr = objects[i];
rusage_add(&pr->ru, &ru);
rusage_add(&__proc.ruchlds, &ru);
if ((status & 0xFF000000u) == 0x23000000u) {
// handle child execve()
CloseHandle(pr->handle);
pr->handle = status & 0x00FFFFFF;
// wait for something to happen
if (n == 64) {
millis = 5;
} else {
// handle child _exit()
CloseHandle(pr->handle);
if (status == 0xc9af3d51u) {
status = kNtStillActive;
}
pr->wstatus = status;
if ((__sighandrvas[SIGCHLD] == (uintptr_t)SIG_IGN ||
(__sighandflags[SIGCHLD] & SA_NOCLDWAIT)) &&
(!pr->waiters && !__proc.waiters)) {
dll_remove(&__proc.list, &pr->elem);
dll_make_first(&__proc.free, &pr->elem);
} else {
pr->iszombie = 1;
dll_remove(&__proc.list, &pr->elem);
dll_make_first(&__proc.zombies, &pr->elem);
if (pr->waiters) {
nsync_cv_broadcast(&pr->onexit);
} else if (__proc.waiters) {
nsync_cv_signal(&__proc.onexit);
} else {
dosignal = 1;
sic = WIFSIGNALED(status) ? CLD_KILLED : CLD_EXITED;
}
millis = -1u;
handles[n++] = __proc.onbirth;
}
i = WaitForMultipleObjects(n, handles, false, millis);
if (i == -1u) {
STRACE("proc wait panic %d", GetLastError());
_Exit(157);
}
if (i & kNtWaitAbandoned) {
i &= ~kNtWaitAbandoned;
STRACE("proc %u handle %ld abandoned", i, handles[i]);
}
__proc_lock();
// release our waiter status
for (int j = 0; j < n; ++j) {
if (handles[j] == __proc.onbirth) continue;
if (j == i) continue;
if (!--objects[j]->waiters && objects[j]->status == PROC_UNDEAD) {
__proc_free(objects[j]);
}
}
// check if we need to churn due to >64 processes
if (i == kNtWaitTimeout) {
__proc_unlock();
continue;
}
// churn on new process birth
if (handles[i] == __proc.onbirth) {
__proc_unlock();
continue;
}
// handle process status change
int sic = 0;
--objects[i]->waiters;
switch (objects[i]->status) {
case PROC_ALIVE:
sic = __proc_harvest(objects[i], false);
break;
case PROC_ZOMBIE:
break;
case PROC_UNDEAD:
if (!objects[i]->waiters) {
__proc_free(objects[i]);
}
break;
default:
__builtin_unreachable();
}
__proc_unlock();
if (dosignal) {
// don't raise SIGCHLD if
// 1. wait4() is being used
// 2. SIGCHLD has SIG_IGN handler
// 3. SIGCHLD has SA_NOCLDWAIT flag
if (sic) {
__sig_generate(SIGCHLD, sic);
}
}
@ -158,7 +230,8 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) {
* Lazy initializes process tracker data structures and worker.
*/
static textwindows void __proc_setup(void) {
__proc.onstart = CreateSemaphore(0, 0, 1, 0);
__proc.onbirth = CreateEvent(0, 0, 0, 0); // auto reset
__proc.haszombies = CreateEvent(0, 1, 0, 0); // manual reset
__proc.thread = CreateThread(0, 65536, __proc_worker, 0,
kNtStackSizeParamIsAReservation, 0);
}
@ -191,7 +264,7 @@ textwindows void __proc_wipe(void) {
* The returned memory is not tracked by any list. It must be filled in
* with system process information and then added back to the system by
* calling __proc_add(). If process creation fails, then it needs to be
* released using __proc_free().
* added back to the __proc.free list by caller.
*/
textwindows struct Proc *__proc_new(void) {
struct Dll *e;
@ -230,16 +303,13 @@ IGNORE_LEAKS(__proc_new)
*/
textwindows void __proc_add(struct Proc *proc) {
dll_make_first(&__proc.list, &proc->elem);
ReleaseSemaphore(__proc.onstart, 1, 0);
SetEvent(__proc.onbirth);
}
/**
* Frees process allocation.
*
* Process must not be currently tracked in the active or zombies list.
*/
textwindows void __proc_free(struct Proc *proc) {
dll_make_first(&__proc.free, &proc->elem);
textwindows void __proc_free(struct Proc *pr) {
dll_remove(&__proc.undead, &pr->elem);
dll_make_first(&__proc.free, &pr->elem);
CloseHandle(pr->handle);
}
// returns owned handle of direct child process

View file

@ -2,25 +2,27 @@
#define COSMOPOLITAN_LIBC_PROC_H_
#include "libc/atomic.h"
#include "libc/calls/struct/rusage.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/dll.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "third_party/nsync/cv.h"
#include "third_party/nsync/mu.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define PROC_ALIVE 0
#define PROC_ZOMBIE 1
#define PROC_UNDEAD 2
#define PROC_CONTAINER(e) DLL_CONTAINER(struct Proc, elem, e)
struct Proc {
int pid;
int status;
int waiters;
bool iszombie;
bool wasforked;
uint32_t wstatus;
int64_t handle;
struct Dll elem;
nsync_cv onexit;
struct rusage ru;
};
@ -28,15 +30,15 @@ struct Procs {
int waiters;
atomic_uint once;
nsync_mu lock;
nsync_cv onexit;
intptr_t thread;
intptr_t onstart;
intptr_t onbirth;
intptr_t haszombies;
struct Dll *list;
struct Dll *free;
struct Dll *undead;
struct Dll *zombies;
struct Proc pool[8];
unsigned allocated;
struct CosmoTib tls;
struct rusage ruchlds;
};
@ -50,6 +52,7 @@ int64_t __proc_search(int);
struct Proc *__proc_new(void);
void __proc_add(struct Proc *);
void __proc_free(struct Proc *);
int __proc_harvest(struct Proc *, bool);
int sys_wait4_nt(int, int *, int, struct rusage *);
COSMOPOLITAN_C_END_

View file

@ -35,8 +35,7 @@ LIBC_PROC_A_DIRECTDEPS = \
LIBC_STR \
LIBC_SYSV \
LIBC_SYSV_CALLS \
THIRD_PARTY_NSYNC \
THIRD_PARTY_NSYNC_MEM
THIRD_PARTY_NSYNC
LIBC_PROC_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_PROC_A_DIRECTDEPS),$($(x))))

View file

@ -19,6 +19,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/struct/sched_param.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/sysv/consts/ioprio.h"
#include "libc/sysv/consts/prio.h"
#include "libc/sysv/consts/sched.h"
@ -31,7 +32,7 @@
*/
int verynice(void) {
int e = errno;
setpriority(PRIO_PROCESS, 0, 10);
setpriority(PRIO_PROCESS, 0, NZERO);
sys_ioprio_set(IOPRIO_WHO_PROCESS, 0,
IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0));
struct sched_param param = {sched_get_priority_min(SCHED_IDLE)};

View file

@ -16,139 +16,191 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/cosmo.h"
#include "libc/errno.h"
#include "libc/fmt/wintime.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/nt/accounting.h"
#include "libc/nt/process.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/events.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/filetime.h"
#include "libc/nt/struct/processmemorycounters.h"
#include "libc/nt/synchronization.h"
#include "libc/proc/proc.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/w.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
static textwindows struct timespec GetNextDeadline(struct timespec deadline) {
if (timespec_iszero(deadline)) deadline = timespec_real();
struct timespec delay = timespec_frommillis(__SIG_PROC_INTERVAL_MS);
return timespec_add(deadline, delay);
}
static textwindows int ReapZombie(struct Proc *pr, int *wstatus,
struct rusage *opt_out_rusage) {
static textwindows int __proc_reap(struct Proc *pr, int *wstatus,
struct rusage *opt_out_rusage) {
if (wstatus) {
*wstatus = pr->wstatus;
}
if (opt_out_rusage) {
*opt_out_rusage = pr->ru;
}
if (!pr->waiters) {
dll_remove(&__proc.zombies, &pr->elem);
dll_remove(&__proc.zombies, &pr->elem);
if (dll_is_empty(__proc.zombies)) {
ResetEvent(__proc.haszombies);
}
if (pr->waiters) {
pr->status = PROC_UNDEAD;
dll_make_first(&__proc.undead, &pr->elem);
} else {
dll_make_first(&__proc.free, &pr->elem);
CloseHandle(pr->handle);
}
return pr->pid;
}
static textwindows int CheckZombies(int pid, int *wstatus,
static textwindows int __proc_check(int pid, int *wstatus,
struct rusage *opt_out_rusage) {
struct Dll *e;
for (e = dll_first(__proc.zombies); e; e = dll_next(__proc.zombies, e)) {
struct Proc *pr = PROC_CONTAINER(e);
if (pid == -1 && pr->waiters) {
continue; // this zombie has been claimed
}
if (pid == -1 || pid == pr->pid) {
return ReapZombie(pr, wstatus, opt_out_rusage);
return __proc_reap(pr, wstatus, opt_out_rusage);
}
}
return 0;
}
static textwindows void UnwindWaiterCount(void *arg) {
int *waiters = arg;
--*waiters;
}
static textwindows int __proc_wait(int pid, int *wstatus, int options,
struct rusage *rusage, sigset_t waitmask) {
for (;;) {
static textwindows int WaitForProcess(int pid, int *wstatus, int options,
struct rusage *rusage,
uint64_t waitmask) {
uint64_t m;
int rc, *wv;
nsync_cv *cv;
struct Dll *e;
struct Proc *pr;
struct timespec deadline = timespec_zero;
// check list of processes that've already exited
if ((rc = CheckZombies(pid, wstatus, rusage))) {
return rc;
}
// find the mark
pr = 0;
if (pid == -1) {
if (dll_is_empty(__proc.list)) {
return echild();
// check for signals and cancelation
int sig, handler_was_called;
if (_check_cancel() == -1) {
return -1;
}
cv = &__proc.onexit;
wv = &__proc.waiters;
} else {
for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) {
if (pid == PROC_CONTAINER(e)->pid) {
pr = PROC_CONTAINER(e);
if ((sig = __sig_get(waitmask))) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) {
return -1; // ECANCELED because SIGTHR was just handled
}
if (handler_was_called & SIG_HANDLED_NO_RESTART) {
return eintr(); // a non-SA_RESTART handler was called
}
}
if (pr) {
unassert(!pr->iszombie);
cv = &pr->onexit;
wv = &pr->waiters;
} else {
// check for zombie to harvest
__proc_lock();
CheckForZombies:
int rc = __proc_check(pid, wstatus, rusage);
if (rc || (options & WNOHANG)) {
__proc_unlock();
return rc;
}
// there's no zombies left
// check if there's any living processes
if (dll_is_empty(__proc.list)) {
__proc_unlock();
return echild();
}
}
// wait for status change
if (options & WNOHANG) return 0;
WaitMore:
deadline = GetNextDeadline(deadline);
SpuriousWakeup:
++*wv;
pthread_cleanup_push(UnwindWaiterCount, wv);
m = __sig_begin(waitmask);
if ((rc = _check_signal(true)) != -1) {
rc = nsync_cv_wait_with_deadline(cv, &__proc.lock, deadline, 0);
// get appropriate wait object
// register ourself as waiting
struct Proc *pr = 0;
uintptr_t hWaitObject;
if (pid == -1) {
// wait for any status change
hWaitObject = __proc.haszombies;
++__proc.waiters;
} else {
// wait on specific child
for (struct Dll *e = dll_first(__proc.list); e;
e = dll_next(__proc.list, e)) {
pr = PROC_CONTAINER(e);
if (pid == pr->pid) break;
}
if (pr) {
// by making the waiter count non-zero, the proc daemon stops
// being obligated to monitor this process. this means we may
// need to assume responsibility later on for zombifying this
++pr->waiters;
hWaitObject = pr->handle;
} else {
__proc_unlock();
return echild();
}
}
__proc_unlock();
// perform blocking operation
uint32_t wi;
uintptr_t sem;
struct PosixThread *pt = _pthread_self();
pt->pt_blkmask = waitmask;
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM,
memory_order_release);
wi = WaitForMultipleObjects(2, (intptr_t[2]){hWaitObject, sem}, 0, -1u);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
CloseHandle(sem);
// log warning if handle unexpectedly closed
if (wi & kNtWaitAbandoned) {
wi &= ~kNtWaitAbandoned;
STRACE("wait4 abandoned %u", wi);
}
// check for wait() style wakeup
__proc_lock();
if (!wi && !pr) {
--__proc.waiters;
goto CheckForZombies;
}
// check if killed or win32 error
if (wi) {
if (pr && --pr->waiters && pr->status == PROC_UNDEAD) {
__proc_free(pr);
}
__proc_unlock();
if (wi == 1) {
// __sig_cancel() woke our semaphore
continue;
} else {
// neither posix or win32 define i/o error conditions for
// generic wait. failure should only be due to api misuse
return einval();
}
}
// handle process exit notification
--pr->waiters;
if (pr->status == PROC_ALIVE) {
__proc_harvest(pr, true);
}
switch (pr->status) {
case PROC_ALIVE:
// exit caused by execve() reparenting
__proc_unlock();
break;
case PROC_ZOMBIE:
// exit happened and we're the first to know
rc = __proc_reap(pr, wstatus, rusage);
__proc_unlock();
return rc;
case PROC_UNDEAD:
// exit happened but another thread waited first
if (!pr->waiters) {
__proc_free(pr);
}
__proc_unlock();
return echild();
default:
__builtin_unreachable();
}
}
__sig_finish(m);
pthread_cleanup_pop(true);
if (rc == -1) return -1;
if (rc == ETIMEDOUT) goto WaitMore;
if (rc == ECANCELED) return ecanceled();
if (pr && pr->iszombie) return ReapZombie(pr, wstatus, rusage);
unassert(!rc); // i have to follow my dreams however crazy they seem
if (!pr && (rc = CheckZombies(pid, wstatus, rusage))) return rc;
goto SpuriousWakeup;
}
textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
struct rusage *opt_out_rusage) {
int rc;
uint64_t m;
// no support for WCONTINUED and WUNTRACED yet
if (options & ~WNOHANG) return einval();
// XXX: NT doesn't really have process groups. For instance the
@ -156,12 +208,9 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
// just does an "ignore ctrl-c" internally.
if (pid == 0) pid = -1;
if (pid < -1) pid = -pid;
m = __sig_block();
__proc_lock();
pthread_cleanup_push((void *)__proc_unlock, 0);
rc = WaitForProcess(pid, opt_out_wstatus, options, opt_out_rusage,
m | 1ull << (SIGCHLD - 1));
pthread_cleanup_pop(true);
sigset_t m = __sig_block();
int rc = __proc_wait(pid, opt_out_wstatus, options, opt_out_rusage,
m | 1ull << (SIGCHLD - 1));
__sig_unblock(m);
return rc;
}

View file

@ -24,6 +24,7 @@
#include "libc/intrin/asancodes.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/getenv.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/nt/files.h"
@ -48,6 +49,18 @@ extern unsigned char __tls_add_nt_rax[];
_Alignas(TLS_ALIGNMENT) static char __static_tls[6016];
static unsigned long ParseMask(const char *str) {
int c;
unsigned long x = 0;
if (str) {
while ((c = *str++)) {
x *= 10;
x += c - '0';
}
}
return x;
}
/**
* Enables thread local storage for main process.
*
@ -210,6 +223,13 @@ textstartup void __enable_tls(void) {
atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed);
// TODO(jart): set_tid_address?
// inherit signal mask
if (IsWindows()) {
atomic_store_explicit(&tib->tib_sigmask,
ParseMask(__getenv(environ, "_MASK").s),
memory_order_relaxed);
}
// initialize posix threads
_pthread_static.tib = tib;
_pthread_static.pt_flags = PT_STATIC;

View file

@ -109,7 +109,7 @@ textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr,
if ((resources.handle = WSASocket(f->family, f->type, f->protocol, 0, 0,
kNtWsaFlagOverlapped)) == -1) {
client = __winsockerr();
goto WeFailed;
goto Finish;
}
// accept network connection
@ -118,7 +118,10 @@ textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr,
ssize_t bytes_received = __winsock_block(
resources.handle, 0, !!(f->flags & O_NONBLOCK), f->rcvtimeo, m,
sys_accept_nt_start, &(struct AcceptArgs){f->handle, &buffer});
if (bytes_received == -1) goto WeFailed;
if (bytes_received == -1) {
__imp_closesocket(resources.handle);
goto Finish;
}
// create file descriptor for new socket
// don't inherit the file open mode bits
@ -138,7 +141,7 @@ textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr,
memcpy(addr, &buffer.remote.addr, sizeof(*addr));
g_fds.p[client].kind = kFdSocket;
WeFailed:
Finish:
pthread_cleanup_pop(false);
__sig_unblock(m);
if (client == -1 && errno == ECONNRESET) {

View file

@ -25,8 +25,14 @@
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#define _MSG_OOB 1
#define _MSG_PEEK 2
#define _MSG_WAITALL 8
#define _MSG_DONTWAIT 64
struct RecvArgs {
const struct iovec *iov;
size_t iovlen;
@ -44,11 +50,17 @@ static textwindows int sys_recv_nt_start(int64_t handle,
textwindows ssize_t sys_recv_nt(int fd, const struct iovec *iov, size_t iovlen,
uint32_t flags) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_PEEK | _MSG_WAITALL)) {
return einval();
}
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
rc = __winsock_block(f->handle, flags, !!(f->flags & O_NONBLOCK), f->rcvtimeo,
m, sys_recv_nt_start, &(struct RecvArgs){iov, iovlen});
bool nonblock = !(flags & _MSG_WAITALL) &&
((f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT));
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags, nonblock, f->rcvtimeo, m,
sys_recv_nt_start, &(struct RecvArgs){iov, iovlen});
__sig_unblock(m);
return rc;
}

View file

@ -33,7 +33,7 @@
* @param fd is the file descriptor returned by socket()
* @param buf is where received network data gets copied
* @param size is the byte capacity of buf
* @param flags can have MSG_{WAITALL,PEEK,OOB}, etc.
* @param flags can have `MSG_OOB`, `MSG_PEEK`, `MSG_DONTWAIT`, `MSG_WAITALL`
* @return number of bytes received, 0 on remote close, or -1 w/ errno
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.

View file

@ -24,9 +24,15 @@
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/msg.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#define _MSG_OOB 1
#define _MSG_PEEK 2
#define _MSG_DONTWAIT 64
struct RecvFromArgs {
const struct iovec *iov;
size_t iovlen;
@ -48,11 +54,14 @@ textwindows ssize_t sys_recvfrom_nt(int fd, const struct iovec *iov,
size_t iovlen, uint32_t flags,
void *opt_out_srcaddr,
uint32_t *opt_inout_srcaddrsize) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_PEEK)) return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
rc = __winsock_block(f->handle, flags, !!(f->flags & O_NONBLOCK), f->rcvtimeo,
m, sys_recvfrom_nt_start,
bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT);
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags, nonblock, f->rcvtimeo, m,
sys_recvfrom_nt_start,
&(struct RecvFromArgs){iov, iovlen, opt_out_srcaddr,
opt_inout_srcaddrsize});
__sig_unblock(m);

View file

@ -34,15 +34,15 @@
/**
* Receives data from network.
*
* This function blocks unless MSG_DONTWAIT is passed. In that case, the
* non-error EWOULDBLOCK might be returned. It basically means we didn't
* wait around to learn an amount of bytes were written that we know in
* advance are guaranteed to be atomic.
*
* @param fd is the file descriptor returned by socket()
* @param buf is where received network data gets copied
* @param size is the byte capacity of buf
* @param flags is a bitmask which may contain any of the following:
* - `MSG_DONTWAIT` to force `O_NONBLOCK` behavior for this call
* - `MSG_OOB` is broadly supported (untested by cosmo)
* - `MSG_PEEK` is broadly supported (untested by cosmo)
* - `MSG_WAITALL` is broadly supported (untested by cosmo)
* - `MSG_DONTROUTE` is broadly supported (untested by cosmo)
* @param flags can have `MSG_OOB`, `MSG_PEEK`, and `MSG_DONTWAIT`
* @param opt_out_srcaddr receives the binary ip:port of the data's origin
* @param opt_inout_srcaddrsize is srcaddr capacity which gets updated
* @return number of bytes received, 0 on remote close, or -1 w/ errno

View file

@ -25,8 +25,13 @@
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#define _MSG_OOB 1
#define _MSG_DONTROUTE 4
#define _MSG_DONTWAIT 64
struct SendArgs {
const struct iovec *iov;
size_t iovlen;
@ -44,11 +49,14 @@ static textwindows int sys_send_nt_start(int64_t handle,
textwindows ssize_t sys_send_nt(int fd, const struct iovec *iov, size_t iovlen,
uint32_t flags) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE)) return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
rc = __winsock_block(f->handle, flags, !!(f->flags & O_NONBLOCK), f->sndtimeo,
m, sys_send_nt_start, &(struct SendArgs){iov, iovlen});
bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT);
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(f->handle, flags, nonblock, f->sndtimeo, m,
sys_send_nt_start, &(struct SendArgs){iov, iovlen});
__sig_unblock(m);
return rc;
}

View file

@ -34,7 +34,7 @@
* @param fd is the file descriptor returned by socket()
* @param buf is the data to send, which we'll copy if necessary
* @param size is the byte-length of buf
* @param flags MSG_OOB, MSG_DONTROUTE, MSG_PARTIAL, MSG_NOSIGNAL, etc.
* @param flags can have `MSG_OOB`, `MSG_DONTROUTE`, and `MSG_DONTWAIT`
* @return number of bytes transmitted, or -1 w/ errno
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.

View file

@ -25,8 +25,13 @@
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#define _MSG_OOB 1
#define _MSG_DONTROUTE 4
#define _MSG_DONTWAIT 64
struct SendToArgs {
const struct iovec *iov;
size_t iovlen;
@ -47,12 +52,14 @@ static textwindows int sys_sendto_nt_start(int64_t handle,
textwindows ssize_t sys_sendto_nt(int fd, const struct iovec *iov,
size_t iovlen, uint32_t flags,
void *opt_in_addr, uint32_t in_addrsize) {
if (flags & ~(_MSG_DONTWAIT | _MSG_OOB | _MSG_DONTROUTE)) return einval();
ssize_t rc;
struct Fd *f = g_fds.p + fd;
sigset_t m = __sig_block();
bool nonblock = (f->flags & O_NONBLOCK) || (flags & _MSG_DONTWAIT);
flags &= ~_MSG_DONTWAIT;
rc = __winsock_block(
f->handle, flags, !!(f->flags & O_NONBLOCK), f->sndtimeo, m,
sys_sendto_nt_start,
f->handle, flags, nonblock, f->sndtimeo, m, sys_sendto_nt_start,
&(struct SendToArgs){iov, iovlen, opt_in_addr, in_addrsize});
__sig_unblock(m);
return rc;

View file

@ -43,7 +43,7 @@
* @param fd is the file descriptor returned by socket()
* @param buf is the data to send, which we'll copy if necessary
* @param size is the byte-length of buf
* @param flags MSG_OOB, MSG_DONTROUTE, MSG_PARTIAL, MSG_NOSIGNAL, etc.
* @param flags can have `MSG_OOB`, `MSG_DONTROUTE`, and `MSG_DONTWAIT`
* @param opt_addr is a binary ip:port destination override, which is
* mandatory for UDP if connect() wasn't called
* @param addrsize is the byte-length of addr's true polymorphic form

View file

@ -16,50 +16,21 @@
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/fd.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/thread.h"
#include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__
struct WinsockBlockResources {
int64_t handle;
struct NtOverlapped *overlap;
};
static void UnwindWinsockBlock(void *arg) {
struct WinsockBlockResources *wbr = arg;
uint32_t got, flags;
CancelIoEx(wbr->handle, wbr->overlap);
WSAGetOverlappedResult(wbr->handle, wbr->overlap, &got, true, &flags);
WSACloseEvent(wbr->overlap->hEvent);
}
static void CancelWinsockBlock(int64_t handle, struct NtOverlapped *overlap) {
if (!CancelIoEx(handle, overlap)) {
unassert(WSAGetLastError() == kNtErrorNotFound);
}
}
textwindows ssize_t
__winsock_block(int64_t handle, uint32_t flags, bool nonblock,
uint32_t srwtimeout, sigset_t waitmask,
@ -67,31 +38,20 @@ __winsock_block(int64_t handle, uint32_t flags, bool nonblock,
uint32_t *flags, void *arg),
void *arg) {
int rc;
int sig = 0;
uint32_t status;
uint32_t exchanged;
int olderror = errno;
bool eagained = false;
bool canceled = false;
int handler_was_called;
struct PosixThread *pt;
RestartOperation:
int rc, sig, reason = 0;
uint32_t status, exchanged;
if (_check_cancel() == -1) return -1; // ECANCELED
if ((sig = __sig_get(waitmask))) goto HandleInterrupt;
struct NtOverlapped overlap = {.hEvent = WSACreateEvent()};
struct WinsockBlockResources wbr = {handle, &overlap};
pthread_cleanup_push(UnwindWinsockBlock, &wbr);
rc = StartSocketOp(handle, &overlap, &flags, arg);
if (rc && WSAGetLastError() == kNtErrorIoPending) {
if (nonblock) {
CancelWinsockBlock(handle, &overlap);
eagained = true;
} else if (_check_cancel()) {
CancelWinsockBlock(handle, &overlap);
canceled = true;
} else if ((sig = __sig_get(waitmask))) {
CancelWinsockBlock(handle, &overlap);
CancelIoEx(handle, &overlap);
reason = EAGAIN;
} else {
struct PosixThread *pt;
pt = _pthread_self();
pt->pt_blkmask = waitmask;
pt->pt_iohandle = handle;
@ -101,10 +61,13 @@ RestartOperation:
status = WSAWaitForMultipleEvents(1, &overlap.hEvent, 0,
srwtimeout ? srwtimeout : -1u, 0);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
if (status == kNtWaitTimeout) {
// SO_RCVTIMEO or SO_SNDTIMEO elapsed
CancelWinsockBlock(handle, &overlap);
eagained = true;
if (status) {
if (status == kNtWaitTimeout) {
reason = EAGAIN; // SO_RCVTIMEO or SO_SNDTIMEO elapsed
} else {
reason = WSAGetLastError(); // ENETDOWN or ENOBUFS
}
CancelIoEx(handle, &overlap);
}
}
rc = 0;
@ -114,30 +77,21 @@ RestartOperation:
? 0
: -1;
}
pthread_cleanup_pop(false);
WSACloseEvent(overlap.hEvent);
if (canceled) {
return ecanceled();
}
if (sig) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
} else {
handler_was_called = 0;
}
if (!rc) {
errno = olderror;
return exchanged;
}
if (WSAGetLastError() == kNtErrorOperationAborted) {
if (eagained) return eagain();
if (!handler_was_called && (sig = __sig_get(waitmask))) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
if (reason) {
errno = reason;
return -1;
}
if (handler_was_called != 1) {
goto RestartOperation;
if ((sig = __sig_get(waitmask))) {
HandleInterrupt:
int handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) return -1;
if (handler_was_called != 1) goto RestartOperation;
}
return eintr();
}

View file

@ -309,9 +309,9 @@ textstartup void __printargs(const char *prologue) {
PRINT("");
PRINT("SIGNAL MASK %#lx", ss);
if (ss) {
for (i = 0; i < 32; ++i) {
if (ss & (1u << i)) {
PRINT(" ☼ %G (%d) is masked", i + 1, i + 1);
for (i = 1; i <= NSIG; ++i) {
if (ss & (1ull << (i - 1))) {
PRINT(" ☼ %G (%d) is masked", i, i);
}
}
} else {

View file

@ -56,17 +56,14 @@ int iswseparator(wint_t);
wint_t towlower(wint_t);
wint_t towupper(wint_t);
void bzero(void *, size_t) memcpyesque;
void *memset(void *, int, size_t) memcpyesque;
void *memmove(void *, const void *, size_t) memcpyesque;
void *memcpy(void *restrict, const void *restrict, size_t) memcpyesque;
void *mempcpy(void *restrict, const void *restrict, size_t) memcpyesque;
char *hexpcpy(char *restrict, const void *restrict, size_t) memcpyesque;
void *memccpy(void *restrict, const void *restrict, int, size_t) memcpyesque;
void bcopy(const void *, void *, size_t) memcpyesque;
void explicit_bzero(void *, size_t);
int bcmp(const void *, const void *, size_t) strlenesque;
int memcmp(const void *, const void *, size_t) strlenesque;
int timingsafe_bcmp(const void *, const void *, size_t);
int timingsafe_memcmp(const void *, const void *, size_t);
@ -75,7 +72,6 @@ size_t strlen(const char *) strlenesque;
size_t strnlen(const char *, size_t) strlenesque;
size_t strnlen_s(const char *, size_t);
char *strchr(const char *, int) strlenesque;
char *index(const char *, int) strlenesque;
void *memchr(const void *, int, size_t) strlenesque;
char *strchrnul(const char *, int) strlenesque returnsnonnull;
void *rawmemchr(const void *, int) strlenesque returnsnonnull;
@ -220,6 +216,17 @@ bool wcsendswith(const wchar_t *, const wchar_t *) strlenesque;
char *__join_paths(char *, size_t, const char *, const char *) __wur;
#endif /* _COSMO_SOURCE */
#if defined(_COSMO_SOURCE) || defined(_GNU_SOURCE) || defined(_BSD_SOURCE) || \
defined(_POSIX_SOURCE) || \
(defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE + 0 < 200809L) || \
(defined(_XOPEN_SOURCE) && _XOPEN_SOURCE + 0 < 700)
int bcmp(const void *, const void *, size_t) strlenesque;
void bcopy(const void *, void *, size_t) memcpyesque;
void bzero(void *, size_t) memcpyesque;
char *index(const char *, int) strlenesque;
char *rindex(const char *, int) strlenesque;
#endif
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STR_STR_H_ */

View file

@ -115,7 +115,12 @@ static int PosixThread(void *arg, int tid) {
}
// set long jump handler so pthread_exit can bring control back here
if (!setjmp(pt->pt_exiter)) {
pthread_sigmask(SIG_SETMASK, &pt->pt_attr.__sigmask, 0);
if (IsWindows()) {
atomic_store_explicit(&__get_tls()->tib_sigmask, pt->pt_attr.__sigmask,
memory_order_release);
} else {
sys_sigprocmask(SIG_SETMASK, &pt->pt_attr.__sigmask, 0);
}
rc = pt->pt_start(pt->pt_arg);
// ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup
unassert(!pt->pt_cleanup);

View file

@ -27,6 +27,7 @@
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/sysconf.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/consts/rlimit.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
@ -58,8 +59,10 @@ void SetUp(void) {
// tune down the main process's stack size to a reasonable amount
// some operating systems, e.g. freebsd, will do things like have
// 500mb RLIMIT_STACK by default, even on machines with 400mb RAM
struct rlimit rl = {2 * 1024 * 1024, 2 * 1024 * 1024};
if (!IsWindows() && !IsXnu()) {
struct rlimit rl;
getrlimit(RLIMIT_STACK, &rl);
rl.rlim_cur = MIN(rl.rlim_cur, 2 * 1024 * 1024);
ASSERT_SYS(0, 0, setrlimit(RLIMIT_STACK, &rl));
}

View file

@ -1,12 +1,21 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 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/struct/sigaction.h"
#include "libc/calls/struct/sigaltstack.h"
#include "libc/calls/struct/siginfo.h"
@ -29,10 +38,6 @@
* rewrite thread cpu state to call pthread_exit
* this method returns gracefully from signal handlers
* unfortunately it relies on cpu architecture knowledge
*
* @see test/libc/thread/stackoverflow1_test.c
* @see test/libc/thread/stackoverflow2_test.c
* @see test/libc/thread/stackoverflow3_test.c
*/
volatile bool smashed_stack;
@ -47,11 +52,12 @@ void Exiter(void *rc) {
void CrashHandler(int sig, siginfo_t *si, void *arg) {
ucontext_t *ctx = arg;
struct sigaltstack ss;
ASSERT_FALSE(smashed_stack);
ASSERT_SYS(0, 0, sigaltstack(0, &ss));
ASSERT_EQ(SS_ONSTACK, ss.ss_flags);
kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr);
smashed_stack = true;
ASSERT_TRUE(__is_stack_overflow(si, ctx));
// EXPECT_TRUE(__is_stack_overflow(si, ctx));
//
// the backtrace will look like this
//
@ -67,7 +73,15 @@ void CrashHandler(int sig, siginfo_t *si, void *arg) {
ctx->uc_mcontext.PC = (long)Exiter;
ctx->uc_mcontext.SP += 32768;
ctx->uc_mcontext.SP &= -16;
ctx->uc_mcontext.SP -= 8;
#ifdef __x86_64__
*(long *)(ctx->uc_mcontext.SP -= 8) = ctx->uc_mcontext.PC;
#elif defined(__aarch64__)
*(long *)(ctx->uc_mcontext.SP -= 8) = ctx->uc_mcontext.regs[30];
*(long *)(ctx->uc_mcontext.SP -= 8) = ctx->uc_mcontext.regs[29];
ctx->uc_mcontext.BP = ctx->uc_mcontext.SP;
#else
#error "unsupported architecture"
#endif
}
int StackOverflow(int f(), int n) {
@ -96,7 +110,7 @@ void *MyPosixThread(void *arg) {
return 0;
}
int main(int argc, char *argv[]) {
TEST(stackoverflow, standardStack_altStack_thread_teleport) {
void *res;
pthread_t th;
struct sigaltstack ss;
@ -105,6 +119,7 @@ int main(int argc, char *argv[]) {
pthread_join(th, &res);
ASSERT_EQ((void *)123L, res);
ASSERT_TRUE(smashed_stack);
// this should be SS_DISABLE but ShowCrashReports() creates an alt stack
ASSERT_SYS(0, 0, sigaltstack(0, &ss));
ASSERT_EQ(SS_DISABLE, ss.ss_flags);
ASSERT_EQ(0, ss.ss_flags);
}

View file

@ -0,0 +1,86 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 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/struct/sigaction.h"
#include "libc/calls/struct/sigaltstack.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/intrin/kprintf.h"
#include "libc/limits.h"
#include "libc/mem/gc.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/sysconf.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/ss.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
/**
* stack overflow recovery technique #4
* just call pthread_exit() and let the thread die
*/
volatile bool smashed_stack;
void CrashHandler(int sig) {
smashed_stack = true;
pthread_exit((void *)123L);
}
int StackOverflow(int f(), int n) {
if (n < INT_MAX) {
return f(f, n + 1) - 1;
} else {
return INT_MAX;
}
}
int (*pStackOverflow)(int (*)(), int) = StackOverflow;
void *MyPosixThread(void *arg) {
struct sigaction sa;
struct sigaltstack ss;
ss.ss_flags = 0;
ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096;
ss.ss_sp = gc(malloc(ss.ss_size));
ASSERT_SYS(0, 0, sigaltstack(&ss, 0));
sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important
sigemptyset(&sa.sa_mask);
sa.sa_handler = CrashHandler;
sigaction(SIGBUS, &sa, 0);
sigaction(SIGSEGV, &sa, 0);
exit(pStackOverflow(pStackOverflow, 0));
return 0;
}
TEST(stackoverflow, standardStack_altStack_thread_teleport) {
void *res;
pthread_t th;
struct sigaltstack ss;
smashed_stack = false;
pthread_create(&th, 0, MyPosixThread, 0);
pthread_join(th, &res);
ASSERT_EQ((void *)123L, res);
ASSERT_TRUE(smashed_stack);
// this should be SS_DISABLE but ShowCrashReports() creates an alt stack
ASSERT_SYS(0, 0, sigaltstack(0, &ss));
ASSERT_EQ(0, ss.ss_flags);
}

View file

@ -637,6 +637,8 @@ int Launch(void) {
posix_spawnattr_init(&spawnattr);
posix_spawnattr_setsigmask(&spawnattr, &savemask);
posix_spawnattr_setflags(&spawnattr,
POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETRLIMIT);
SetCpuLimit(cpuquota);
SetFszLimit(fszquota);
SetMemLimit(memquota);

View file

@ -552,7 +552,8 @@ RetryOnEtxtbsyRaceCondition:
started = timespec_real();
pipe2(client->pipe, O_CLOEXEC);
posix_spawnattr_init(&spawnattr);
posix_spawnattr_setflags(&spawnattr, POSIX_SPAWN_SETPGROUP);
posix_spawnattr_setflags(&spawnattr,
POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETPGROUP);
posix_spawnattr_setsigmask(&spawnattr, &sigmask);
posix_spawn_file_actions_init(&spawnfila);
posix_spawn_file_actions_adddup2(&spawnfila, g_bogusfd, 0);