mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-03 15:38:22 +00:00
345 lines
10 KiB
C
345 lines
10 KiB
C
/*-*- 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 2022 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 "ape/sections.internal.h"
|
|
#include "libc/assert.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/internal.h"
|
|
#include "libc/calls/sig.internal.h"
|
|
#include "libc/calls/state.internal.h"
|
|
#include "libc/calls/struct/sigaction.h"
|
|
#include "libc/calls/struct/siginfo.h"
|
|
#include "libc/calls/struct/sigset.h"
|
|
#include "libc/calls/struct/ucontext.internal.h"
|
|
#include "libc/calls/ucontext.h"
|
|
#include "libc/intrin/describebacktrace.internal.h"
|
|
#include "libc/intrin/strace.internal.h"
|
|
#include "libc/intrin/weaken.h"
|
|
#include "libc/log/libfatal.internal.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/nexgen32e/stackframe.h"
|
|
#include "libc/nt/console.h"
|
|
#include "libc/nt/enum/context.h"
|
|
#include "libc/nt/runtime.h"
|
|
#include "libc/nt/struct/context.h"
|
|
#include "libc/nt/thread.h"
|
|
#include "libc/runtime/internal.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/sa.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
#include "libc/thread/tls.h"
|
|
|
|
#ifdef __x86_64__
|
|
|
|
/**
|
|
* Returns true if signal default action is to end process.
|
|
*/
|
|
textwindows bool __sig_is_fatal(int sig) {
|
|
return !(sig == SIGURG || //
|
|
sig == SIGCHLD || //
|
|
sig == SIGWINCH);
|
|
}
|
|
|
|
/**
|
|
* Returns true if signal is so fatal it should dump core.
|
|
*/
|
|
textwindows bool __sig_is_core(int sig) {
|
|
return sig == SIGSYS || //
|
|
sig == SIGBUS || //
|
|
sig == SIGSEGV || //
|
|
sig == SIGQUIT || //
|
|
sig == SIGTRAP || //
|
|
sig == SIGXCPU || //
|
|
sig == SIGXFSZ;
|
|
}
|
|
|
|
/**
|
|
* Allocates piece of memory for storing pending signal.
|
|
* @assume lock is held
|
|
*/
|
|
static textwindows struct Signal *__sig_alloc(void) {
|
|
int i;
|
|
struct Signal *res = 0;
|
|
for (i = 0; i < ARRAYLEN(__sig.mem); ++i) {
|
|
if (!__sig.mem[i].used) {
|
|
__sig.mem[i].used = true;
|
|
res = __sig.mem + i;
|
|
break;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Returns signal memory to static pool.
|
|
*/
|
|
static textwindows void __sig_free(struct Signal *mem) {
|
|
mem->used = false;
|
|
}
|
|
|
|
static inline textwindows int __sig_is_masked(int sig) {
|
|
if (__tls_enabled) {
|
|
return __get_tls()->tib_sigmask & (1ull << (sig - 1));
|
|
} else {
|
|
return __sig.sigmask & (1ull << (sig - 1));
|
|
}
|
|
}
|
|
|
|
textwindows int __sig_is_applicable(struct Signal *s) {
|
|
return s->tid <= 0 || s->tid == gettid();
|
|
}
|
|
|
|
/**
|
|
* Dequeues signal that isn't masked.
|
|
* @return signal or null if empty or none unmasked
|
|
*/
|
|
static textwindows struct Signal *__sig_remove(int sigops) {
|
|
struct Signal *prev, *res;
|
|
if (__sig.queue) {
|
|
__sig_lock();
|
|
for (prev = 0, res = __sig.queue; res; prev = res, res = res->next) {
|
|
if (__sig_is_applicable(res) && //
|
|
!__sig_is_masked(res->sig) && //
|
|
!((sigops & kSigOpNochld) && res->sig == SIGCHLD)) {
|
|
if (res == __sig.queue) {
|
|
__sig.queue = res->next;
|
|
} else if (prev) {
|
|
prev->next = res->next;
|
|
}
|
|
res->next = 0;
|
|
break;
|
|
}
|
|
}
|
|
__sig_unlock();
|
|
} else {
|
|
res = 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Delivers signal to callback.
|
|
*
|
|
* @return true if `EINTR` should be raised
|
|
*/
|
|
bool __sig_deliver(int sigops, int sig, int sic, ucontext_t *ctx) {
|
|
unsigned rva = __sighandrvas[sig];
|
|
unsigned flags = __sighandflags[sig];
|
|
|
|
// generate expensive data if needed
|
|
ucontext_t uc;
|
|
siginfo_t info;
|
|
siginfo_t *infop;
|
|
if (flags & SA_SIGINFO) {
|
|
__repstosb(&info, 0, sizeof(info));
|
|
info.si_signo = sig;
|
|
info.si_code = sic;
|
|
infop = &info;
|
|
if (!ctx) {
|
|
struct NtContext nc = {.ContextFlags = kNtContextAll};
|
|
__repstosb(&uc, 0, sizeof(uc));
|
|
GetThreadContext(GetCurrentThread(), &nc);
|
|
_ntcontext2linux(&uc, &nc);
|
|
ctx = &uc;
|
|
}
|
|
} else {
|
|
infop = 0;
|
|
}
|
|
|
|
// save the thread's signal mask
|
|
uint64_t oldmask;
|
|
if (__tls_enabled) {
|
|
oldmask = __get_tls()->tib_sigmask;
|
|
} else {
|
|
oldmask = __sig.sigmask;
|
|
}
|
|
if (ctx) {
|
|
ctx->uc_sigmask = (sigset_t){{oldmask}};
|
|
}
|
|
|
|
// mask the signal that's being handled whilst handling
|
|
if (!(flags & SA_NODEFER)) {
|
|
if (__tls_enabled) {
|
|
__get_tls()->tib_sigmask |= 1ull << (sig - 1);
|
|
} else {
|
|
__sig.sigmask |= 1ull << (sig - 1);
|
|
}
|
|
}
|
|
|
|
STRACE("delivering %G", sig);
|
|
((sigaction_f)(__executable_start + rva))(sig, infop, ctx);
|
|
|
|
if (ctx) {
|
|
oldmask = ctx->uc_sigmask.__bits[0];
|
|
}
|
|
if (__tls_enabled) {
|
|
__get_tls()->tib_sigmask = oldmask;
|
|
} else {
|
|
__sig.sigmask = oldmask;
|
|
}
|
|
if (flags & SA_RESETHAND) {
|
|
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
|
|
}
|
|
|
|
if (!(sigops & kSigOpRestartable)) {
|
|
return true; // always send EINTR for wait4(), poll(), etc.
|
|
} else if (flags & SA_RESTART) {
|
|
STRACE("restarting syscall on %G", sig);
|
|
return false; // resume syscall for read(), write(), etc.
|
|
} else {
|
|
return true; // default course is to raise EINTR
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles signal.
|
|
* @return true if `EINTR` should be raised
|
|
*/
|
|
textwindows bool __sig_handle(int sigops, int sig, int sic, ucontext_t *ctx) {
|
|
if (__sig_is_masked(sig)) {
|
|
if (sigops & kSigOpUnmaskable) {
|
|
goto DefaultAction;
|
|
}
|
|
__sig_add(0, sig, sic);
|
|
return false;
|
|
}
|
|
switch (__sighandrvas[sig]) {
|
|
case (intptr_t)SIG_DFL:
|
|
DefaultAction:
|
|
if (__sig_is_fatal(sig)) {
|
|
uint32_t cmode;
|
|
intptr_t hStderr;
|
|
const char *signame;
|
|
char *end, sigbuf[21], output[123];
|
|
signame = strsignal_r(sig, sigbuf);
|
|
STRACE("terminating due to uncaught %s", signame);
|
|
if (__sig_is_core(sig)) {
|
|
hStderr = GetStdHandle(kNtStdErrorHandle);
|
|
if (GetConsoleMode(hStderr, &cmode)) {
|
|
end = stpcpy(output, signame);
|
|
end = stpcpy(end, " ");
|
|
end = stpcpy(
|
|
end,
|
|
DescribeBacktrace(
|
|
ctx ? (struct StackFrame *)ctx->uc_mcontext.BP
|
|
: (struct StackFrame *)__builtin_frame_address(0)));
|
|
end = stpcpy(end, "\n");
|
|
WriteFile(hStderr, output, end - output, 0, 0);
|
|
}
|
|
}
|
|
ExitProcess(sig);
|
|
}
|
|
// fallthrough
|
|
case (intptr_t)SIG_IGN:
|
|
return false;
|
|
default:
|
|
return __sig_deliver(sigops, sig, sic, ctx);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enqueues generic signal for delivery on New Technology.
|
|
* @return 0 on success, otherwise -1 w/ errno
|
|
* @threadsafe
|
|
*/
|
|
textwindows int __sig_add(int tid, int sig, int si_code) {
|
|
int rc;
|
|
struct Signal *mem;
|
|
if (1 <= sig && sig <= 64) {
|
|
if (__sighandrvas[sig] == (unsigned)(uintptr_t)SIG_IGN ||
|
|
(__sighandrvas[sig] == (unsigned)(uintptr_t)SIG_DFL &&
|
|
!__sig_is_fatal(sig))) {
|
|
STRACE("ignoring %G", sig);
|
|
rc = 0;
|
|
} else {
|
|
STRACE("enqueuing %G", sig);
|
|
__sig_lock();
|
|
++__sig_count;
|
|
if ((mem = __sig_alloc())) {
|
|
mem->tid = tid;
|
|
mem->sig = sig;
|
|
mem->si_code = si_code;
|
|
mem->next = __sig.queue;
|
|
__sig.queue = mem;
|
|
rc = 0;
|
|
} else {
|
|
rc = enomem();
|
|
}
|
|
__sig_unlock();
|
|
}
|
|
} else {
|
|
rc = einval();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Checks for unblocked signals and delivers them on New Technology.
|
|
* @return true if EINTR should be returned by caller
|
|
* @note called from main thread
|
|
* @threadsafe
|
|
*/
|
|
textwindows bool __sig_check(int sigops) {
|
|
bool delivered;
|
|
struct Signal *sig;
|
|
delivered = false;
|
|
while ((sig = __sig_remove(sigops))) {
|
|
delivered |= __sig_handle(sigops, sig->sig, sig->si_code, 0);
|
|
__sig_free(sig);
|
|
}
|
|
return delivered;
|
|
}
|
|
|
|
/**
|
|
* Determines if a signal should be ignored and if so discards existing
|
|
* instances of said signal on New Technology.
|
|
*
|
|
* Even blocked signals are discarded.
|
|
*
|
|
* @param sig the signal number to remove
|
|
* @threadsafe
|
|
*/
|
|
textwindows void __sig_check_ignore(const int sig, const unsigned rva) {
|
|
struct Signal *cur, *prev, *next;
|
|
if (rva != (unsigned)(intptr_t)SIG_IGN &&
|
|
(rva != (unsigned)(intptr_t)SIG_DFL || __sig_is_fatal(sig))) {
|
|
return;
|
|
}
|
|
if (__sig.queue) {
|
|
__sig_lock();
|
|
for (prev = 0, cur = __sig.queue; cur; cur = next) {
|
|
next = cur->next;
|
|
if (sig == cur->sig) {
|
|
if (cur == __sig.queue) {
|
|
__sig.queue = cur->next;
|
|
} else if (prev) {
|
|
prev->next = cur->next;
|
|
}
|
|
__sig_handle(0, cur->sig, cur->si_code, 0);
|
|
__sig_free(cur);
|
|
} else {
|
|
prev = cur;
|
|
}
|
|
}
|
|
__sig_unlock();
|
|
}
|
|
}
|
|
|
|
#endif /* __x86_64__ */
|