cosmopolitan/libc/calls/sigaction.c
Justine Tunney ec480f5aa0
Make improvements
- Every unit test now passes on Apple Silicon. The final piece of this
  puzzle was porting our POSIX threads cancelation support, since that
  works differently on ARM64 XNU vs. AMD64. Our semaphore support on
  Apple Silicon is also superior now compared to AMD64, thanks to the
  grand central dispatch library which lets *NSYNC locks go faster.

- The Cosmopolitan runtime is now more stable, particularly on Windows.
  To do this, thread local storage is mandatory at all runtime levels,
  and the innermost packages of the C library is no longer being built
  using ASAN. TLS is being bootstrapped with a 128-byte TIB during the
  process startup phase, and then later on the runtime re-allocates it
  either statically or dynamically to support code using _Thread_local.
  fork() and execve() now do a better job cooperating with threads. We
  can now check how much stack memory is left in the process or thread
  when functions like kprintf() / execve() etc. call alloca(), so that
  ENOMEM can be raised, reduce a buffer size, or just print a warning.

- POSIX signal emulation is now implemented the same way kernels do it
  with pthread_kill() and raise(). Any thread can interrupt any other
  thread, regardless of what it's doing. If it's blocked on read/write
  then the killer thread will cancel its i/o operation so that EINTR can
  be returned in the mark thread immediately. If it's doing a tight CPU
  bound operation, then that's also interrupted by the signal delivery.
  Signal delivery works now by suspending a thread and pushing context
  data structures onto its stack, and redirecting its execution to a
  trampoline function, which calls SetThreadContext(GetCurrentThread())
  when it's done.

- We're now doing a better job managing locks and handles. On NetBSD we
  now close semaphore file descriptors in forked children. Semaphores on
  Windows can now be canceled immediately, which means mutexes/condition
  variables will now go faster. Apple Silicon semaphores can be canceled
  too. We're now using Apple's pthread_yield() funciton. Apple _nocancel
  syscalls are now used on XNU when appropriate to ensure pthread_cancel
  requests aren't lost. The MbedTLS library has been updated to support
  POSIX thread cancelations. See tool/build/runitd.c for an example of
  how it can be used for production multi-threaded tls servers. Handles
  on Windows now leak less often across processes. All i/o operations on
  Windows are now overlapped, which means file pointers can no longer be
  inherited across dup() and fork() for the time being.

- We now spawn a thread on Windows to deliver SIGCHLD and wakeup wait4()
  which means, for example, that posix_spawn() now goes 3x faster. POSIX
  spawn is also now more correct. Like Musl, it's now able to report the
  failure code of execve() via a pipe although our approach favors using
  shared memory to do that on systems that have a true vfork() function.

- We now spawn a thread to deliver SIGALRM to threads when setitimer()
  is used. This enables the most precise wakeups the OS makes possible.

- The Cosmopolitan runtime now uses less memory. On NetBSD for example,
  it turned out the kernel would actually commit the PT_GNU_STACK size
  which caused RSS to be 6mb for every process. Now it's down to ~4kb.
  On Apple Silicon, we reduce the mandatory upstream thread size to the
  smallest possible size to reduce the memory overhead of Cosmo threads.
  The examples directory has a program called greenbean which can spawn
  a web server on Linux with 10,000 worker threads and have the memory
  usage of the process be ~77mb. The 1024 byte overhead of POSIX-style
  thread-local storage is now optional; it won't be allocated until the
  pthread_setspecific/getspecific functions are called. On Windows, the
  threads that get spawned which are internal to the libc implementation
  use reserve rather than commit memory, which shaves a few hundred kb.

- sigaltstack() is now supported on Windows, however it's currently not
  able to be used to handle stack overflows, since crash signals are
  still generated by WIN32. However the crash handler will still switch
  to the alt stack, which is helpful in environments with tiny threads.

- Test binaries are now smaller. Many of the mandatory dependencies of
  the test runner have been removed. This ensures many programs can do a
  better job only linking the the thing they're testing. This caused the
  test binaries for LIBC_FMT for example, to decrease from 200kb to 50kb

- long double is no longer used in the implementation details of libc,
  except in the APIs that define it. The old code that used long double
  for time (instead of struct timespec) has now been thoroughly removed.

- ShowCrashReports() is now much tinier in MODE=tiny. Instead of doing
  backtraces itself, it'll just print a command you can run on the shell
  using our new `cosmoaddr2line` program to view the backtrace.

- Crash report signal handling now works in a much better way. Instead
  of terminating the process, it now relies on SA_RESETHAND so that the
  default SIG_IGN behavior can terminate the process if necessary.

- Our pledge() functionality has now been fully ported to AARCH64 Linux.
2023-09-18 21:04:47 -07:00

517 lines
20 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 2020 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 "ape/sections.internal.h"
#include "libc/assert.h"
#include "libc/calls/blocksigs.internal.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/sigaction.internal.h"
#include "libc/calls/struct/siginfo.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/limits.h"
#include "libc/log/backtrace.internal.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/limits.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/tls.h"
#if SupportsWindows()
__static_yoink("__sig_ctor");
#endif
#define SA_RESTORER 0x04000000
static void sigaction_cosmo2native(union metasigaction *sa) {
void *handler;
uint64_t flags;
void *restorer;
uint32_t mask[4];
if (!sa) return;
flags = sa->cosmo.sa_flags;
handler = sa->cosmo.sa_handler;
restorer = sa->cosmo.sa_restorer;
mask[0] = sa->cosmo.sa_mask.__bits[0];
mask[1] = sa->cosmo.sa_mask.__bits[0] >> 32;
mask[2] = sa->cosmo.sa_mask.__bits[1];
mask[3] = sa->cosmo.sa_mask.__bits[1] >> 32;
if (IsLinux()) {
sa->linux.sa_flags = flags;
sa->linux.sa_handler = handler;
sa->linux.sa_restorer = restorer;
sa->linux.sa_mask[0] = mask[0];
sa->linux.sa_mask[1] = mask[1];
} else if (IsXnu()) {
sa->xnu_in.sa_flags = flags;
sa->xnu_in.sa_handler = handler;
sa->xnu_in.sa_restorer = restorer;
sa->xnu_in.sa_mask[0] = mask[0];
} else if (IsFreebsd()) {
sa->freebsd.sa_flags = flags;
sa->freebsd.sa_handler = handler;
sa->freebsd.sa_mask[0] = mask[0];
sa->freebsd.sa_mask[1] = mask[1];
sa->freebsd.sa_mask[2] = mask[2];
sa->freebsd.sa_mask[3] = mask[3];
} else if (IsOpenbsd()) {
sa->openbsd.sa_flags = flags;
sa->openbsd.sa_handler = handler;
sa->openbsd.sa_mask[0] = mask[0];
} else if (IsNetbsd()) {
sa->netbsd.sa_flags = flags;
sa->netbsd.sa_handler = handler;
sa->netbsd.sa_mask[0] = mask[0];
sa->netbsd.sa_mask[1] = mask[1];
sa->netbsd.sa_mask[2] = mask[2];
sa->netbsd.sa_mask[3] = mask[3];
}
}
static void sigaction_native2cosmo(union metasigaction *sa) {
void *handler;
uint64_t flags;
void *restorer = 0;
uint32_t mask[4] = {0};
if (!sa) return;
if (IsLinux()) {
flags = sa->linux.sa_flags;
handler = sa->linux.sa_handler;
restorer = sa->linux.sa_restorer;
mask[0] = sa->linux.sa_mask[0];
mask[1] = sa->linux.sa_mask[1];
} else if (IsXnu()) {
flags = sa->xnu_out.sa_flags;
handler = sa->xnu_out.sa_handler;
mask[0] = sa->xnu_out.sa_mask[0];
} else if (IsFreebsd()) {
flags = sa->freebsd.sa_flags;
handler = sa->freebsd.sa_handler;
mask[0] = sa->freebsd.sa_mask[0];
mask[1] = sa->freebsd.sa_mask[1];
mask[2] = sa->freebsd.sa_mask[2];
mask[3] = sa->freebsd.sa_mask[3];
} else if (IsOpenbsd()) {
flags = sa->openbsd.sa_flags;
handler = sa->openbsd.sa_handler;
mask[0] = sa->openbsd.sa_mask[0];
} else if (IsNetbsd()) {
flags = sa->netbsd.sa_flags;
handler = sa->netbsd.sa_handler;
mask[0] = sa->netbsd.sa_mask[0];
mask[1] = sa->netbsd.sa_mask[1];
mask[2] = sa->netbsd.sa_mask[2];
mask[3] = sa->netbsd.sa_mask[3];
} else {
return;
}
sa->cosmo.sa_flags = flags;
sa->cosmo.sa_handler = handler;
sa->cosmo.sa_restorer = restorer;
sa->cosmo.sa_mask.__bits[0] = mask[0] | (uint64_t)mask[1] << 32;
sa->cosmo.sa_mask.__bits[1] = mask[2] | (uint64_t)mask[3] << 32;
}
static int __sigaction(int sig, const struct sigaction *act,
struct sigaction *oldact) {
_Static_assert(
(sizeof(struct sigaction) >= sizeof(struct sigaction_linux) &&
sizeof(struct sigaction) >= sizeof(struct sigaction_xnu_in) &&
sizeof(struct sigaction) >= sizeof(struct sigaction_xnu_out) &&
sizeof(struct sigaction) >= sizeof(struct sigaction_freebsd) &&
sizeof(struct sigaction) >= sizeof(struct sigaction_openbsd) &&
sizeof(struct sigaction) >= sizeof(struct sigaction_netbsd)),
"sigaction cosmo abi needs tuning");
int64_t arg4, arg5;
int rc, rva, oldrva;
sigaction_f sigenter;
struct sigaction *ap, copy;
if (IsMetal()) return enosys(); /* TODO: Signals on Metal */
if (!(1 <= sig && sig <= _NSIG)) return einval();
if (sig == SIGKILL || sig == SIGSTOP) return einval();
if (IsAsan() && ((act && !__asan_is_valid(act, sizeof(*act))) ||
(oldact && !__asan_is_valid(oldact, sizeof(*oldact))))) {
return efault();
}
if (!act) {
rva = (int32_t)(intptr_t)SIG_DFL;
} else if ((intptr_t)act->sa_handler < kSigactionMinRva) {
rva = (int)(intptr_t)act->sa_handler;
} else if ((intptr_t)act->sa_handler >=
(intptr_t)&__executable_start + kSigactionMinRva &&
(intptr_t)act->sa_handler <
(intptr_t)&__executable_start + INT_MAX) {
rva = (int)((uintptr_t)act->sa_handler - (uintptr_t)&__executable_start);
} else {
return efault();
}
if (__vforked && rva != (intptr_t)SIG_DFL && rva != (intptr_t)SIG_IGN) {
return einval();
}
if (!IsWindows()) {
if (act) {
memcpy(&copy, act, sizeof(copy));
ap = &copy;
if (IsLinux()) {
if (!(ap->sa_flags & SA_RESTORER)) {
ap->sa_flags |= SA_RESTORER;
ap->sa_restorer = &__restore_rt;
}
if (__iswsl1()) {
sigenter = __sigenter_wsl;
} else {
sigenter = ap->sa_sigaction;
}
} else if (IsXnu()) {
ap->sa_restorer = (void *)&__sigenter_xnu;
sigenter = __sigenter_xnu;
// mitigate Rosetta signal handling strangeness
// https://github.com/jart/cosmopolitan/issues/455
ap->sa_flags |= SA_SIGINFO;
} else if (IsNetbsd()) {
sigenter = __sigenter_netbsd;
} else if (IsFreebsd()) {
sigenter = __sigenter_freebsd;
} else if (IsOpenbsd()) {
sigenter = __sigenter_openbsd;
} else {
return enosys();
}
if (rva < kSigactionMinRva) {
ap->sa_sigaction = (void *)(intptr_t)rva;
} else {
ap->sa_sigaction = sigenter;
}
sigaction_cosmo2native((union metasigaction *)ap);
} else {
ap = NULL;
}
if (IsXnu()) {
arg4 = (int64_t)(intptr_t)oldact; /* from go code */
arg5 = 0;
} else if (IsNetbsd()) {
/* int __sigaction_sigtramp(int signum,
const struct sigaction *nsa,
struct sigaction *osa,
const void *tramp,
int vers); */
if (ap) {
arg4 = (int64_t)(intptr_t)&__restore_rt_netbsd;
arg5 = 2; /* netbsd/lib/libc/arch/x86_64/sys/__sigtramp2.S */
} else {
arg4 = 0;
arg5 = 0; /* netbsd/lib/libc/arch/x86_64/sys/__sigtramp2.S */
}
} else {
arg4 = 8; /* or linux whines */
arg5 = 0;
}
if ((rc = sys_sigaction(sig, ap, oldact, arg4, arg5)) != -1) {
sigaction_native2cosmo((union metasigaction *)oldact);
}
} else {
if (oldact) {
bzero(oldact, sizeof(*oldact));
oldrva = __sighandrvas[sig];
oldact->sa_flags = __sighandflags[sig];
oldact->sa_sigaction =
(sigaction_f)(oldrva < kSigactionMinRva
? oldrva
: (intptr_t)&__executable_start + oldrva);
}
rc = 0;
}
if (rc != -1 && !__vforked) {
if (act) {
__sighandrvas[sig] = rva;
__sighandflags[sig] = act->sa_flags;
if (IsWindows()) {
if (__sig_ignored(sig)) {
__sig.pending &= ~(1ull << (sig - 1));
if (__tls_enabled) {
__get_tls()->tib_sigpending &= ~(1ull << (sig - 1));
}
}
}
}
}
return rc;
}
/**
* Installs handler for kernel interrupt to thread, e.g.:
*
* void GotCtrlC(int sig, siginfo_t *si, void *arg) {
* ucontext_t *ctx = arg;
* }
* struct sigaction sa = {.sa_sigaction = GotCtrlC,
* .sa_flags = SA_RESETHAND|SA_RESTART|SA_SIGINFO};
* CHECK_NE(-1, sigaction(SIGINT, &sa, NULL));
*
* The following flags are supported across platforms:
*
* - `SA_SIGINFO`: Causes the `siginfo_t` and `ucontext_t` parameters to
* be passed. `void *ctx` actually refers to `struct ucontext *`.
* This not only gives you more information about the signal, but also
* allows your signal handler to change the CPU registers. That's
* useful for recovering from crashes. If you don't use this attribute,
* then signal delivery will go a little faster.
*
* - `SA_RESTART`: Enables BSD signal handling semantics. Normally i/o
* entrypoints check for pending signals to deliver. If one gets
* delivered during an i/o call, the normal behavior is to cancel the
* i/o operation and return -1 with EINTR in errno. If you use the
* `SA_RESTART` flag then that behavior changes, so that any function
* that's been annotated with @restartable will not return `EINTR` and
* will instead resume the i/o operation. This makes coding easier but
* it can be an anti-pattern if not used carefully, since poor usage
* can easily result in latency issues. It also requires one to do
* more work in signal handlers, so special care needs to be given to
* which C library functions are @asyncsignalsafe.
*
* - `SA_RESETHAND`: Causes signal handler to be single-shot. This means
* that, upon entry of delivery to a signal handler, it's reset to the
* `SIG_DFL` handler automatically. You may use the alias `SA_ONESHOT`
* for this flag, which means the same thing.
*
* - `SA_NODEFER`: Disables the reentrancy safety check on your signal
* handler. Normally that's a good thing, since for instance if your
* `SIGSEGV` signal handler happens to segfault, you're going to want
* your process to just crash rather than looping endlessly. But in
* some cases it's desirable to use `SA_NODEFER` instead, such as at
* times when you wish to `longjmp()` out of your signal handler and
* back into your program. This is only safe to do across platforms
* for non-crashing signals such as `SIGCHLD` and `SIGINT`. Crash
* handlers should use Xed instead to recover execution, because on
* Windows a `SIGSEGV` or `SIGTRAP` crash handler might happen on a
* separate stack and/or a separate thread. You may use the alias
* `SA_NOMASK` for this flag, which means the same thing.
*
* - `SA_NOCLDWAIT`: Changes `SIGCHLD` so the zombie is gone and you
* can't call `wait()` anymore; similar but may
* still deliver the SIGCHLD.
*
* - `SA_NOCLDSTOP`: Lets you set `SIGCHLD` handler that's only notified
* on exit/termination and not notified on `SIGSTOP`, `SIGTSTP`,
* `SIGTTIN`, `SIGTTOU`, or `SIGCONT`.
*
* Here's an example of the most professional way to handle signals in
* an i/o event loop. It's generally a best practice to have signal
* handlers do the fewest number of things possible. The trick is to
* have your signals work hand-in-glove with the EINTR errno. This
* obfuscates the need for having to worry about @asyncsignalsafe.
*
* static volatile bool gotctrlc;
*
* void OnCtrlC(int sig) {
* gotctrlc = true;
* }
*
* int main() {
* size_t got;
* ssize_t rc;
* char buf[1];
* struct sigaction oldint;
* struct sigaction saint = {.sa_handler = GotCtrlC};
* if (sigaction(SIGINT, &saint, &oldint) == -1) {
* perror("sigaction");
* exit(1);
* }
* for (;;) {
* rc = read(0, buf, sizeof(buf));
* if (rc == -1) {
* if (errno == EINTR) {
* if (gotctrlc) {
* break;
* }
* } else {
* perror("read");
* exit(2);
* }
* }
* if (!(got = rc)) {
* break;
* }
* for (;;) {
* rc = write(1, buf, got);
* if (rc != -1) {
* assert(rc == 1);
* break;
* } else if (errno != EINTR) {
* perror("write");
* exit(3);
* }
* }
* }
* sigaction(SIGINT, &oldint, 0);
* }
*
* Please note that you can't do the above if you use SA_RESTART. Since
* the purpose of SA_RESTART is to restart i/o operations whose docs say
* that they're @restartable and read() is one such function. Here's
* some even better news: if you don't install any signal handlers at
* all, then your i/o calls will never be interrupted!
*
* Here's an example of the most professional way to recover from
* `SIGSEGV`, `SIGFPE`, and `SIGILL`.
*
* void ContinueOnCrash(void);
*
* void SkipOverFaultingInstruction(struct ucontext *ctx) {
* struct XedDecodedInst xedd;
* xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64);
* xed_instruction_length_decode(&xedd, (void *)ctx->uc_mcontext.rip, 15);
* ctx->uc_mcontext.rip += xedd.length;
* }
*
* void OnCrash(int sig, struct siginfo *si, void *vctx) {
* struct ucontext *ctx = vctx;
* SkipOverFaultingInstruction(ctx);
* ContinueOnCrash(); // reinstall here in case *rip faults
* }
*
* void ContinueOnCrash(void) {
* struct sigaction sa = {.sa_handler = OnSigSegv,
* .sa_flags = SA_SIGINFO | SA_RESETHAND};
* sigaction(SIGSEGV, &sa, 0);
* sigaction(SIGFPE, &sa, 0);
* sigaction(SIGILL, &sa, 0);
* }
*
* int main() {
* ContinueOnCrash();
* // ...
* }
*
* You may also edit any other CPU registers during the handler. For
* example, you can use the above technique so that division by zero
* becomes defined to a specific value of your choosing!
*
* Please note that Xed isn't needed to recover from `SIGTRAP` which can
* be raised at any time by embedding `DebugBreak()` or `asm("int3")` in
* your program code. Your signal handler will automatically skip over
* the interrupt instruction, assuming your signal handler returns.
*
* The important signals supported across all platforms are:
*
* - `SIGINT`: When you press Ctrl-C this signal gets broadcasted to
* your process session group. This is the normal way to terminate
* console applications.
*
* - `SIGQUIT`: When you press CTRL-\ this signal gets broadcasted to
* your process session group. This is the irregular way to kill an
* application in cases where maybe your `SIGINT` handler is broken
* although, Cosmopolitan Libc ShowCrashReports() should program it
* such as to attach a debugger to the process if possible, or else
* show a crash report. Also note that in New Technology you should
* press CTRL+BREAK rather than CTRL+\ to get this signal.
*
* - `SIGHUP`: This gets sent to your non-daemon processes when you
* close your terminal session.
*
* - `SIGTERM` is what the `kill` command sends by default. It's the
* choice signal for terminating daemons.
*
* - `SIGUSR1` and `SIGUSR2` can be anything you want. Their default
* action is to kill the process. By convention `SIGUSR1` is usually
* used by daemons to reload the config file.
*
* - `SIGCHLD` is sent when a process terminates and it takes a certain
* degree of UNIX mastery to address sanely.
*
* - `SIGALRM` is invoked by `setitimer()` and `alarm()`. It can be
* useful for interrupting i/o operations like `connect()`.
*
* - `SIGTRAP`: This happens when an INT3 instruction is encountered.
*
* - `SIGILL` happens on illegal instructions, e.g. `UD2`.
*
* - `SIGABRT` happens when you call `abort()`.
*
* - `SIGFPE` happens when you divide ints by zero, among other things.
*
* - `SIGSEGV` and `SIGBUS` indicate memory access errors and they have
* inconsistent semantics across platforms like FreeBSD.
*
* - `SIGWINCH` is sent when your terminal window is resized.
*
* - `SIGXCPU` and `SIGXFSZ` may be raised if you run out of resources,
* which can happen if your process, or the parent process that
* spawned your process, happened to call `setrlimit()`. Doing this is
* a wonderful idea.
*
* Using signals might make your C runtime slower. Upon successfully
* installing its first signal handling function, sigaction() will set
* the global variable `__interruptible` to true, to let everything else
* know that signals are in play. That way code which would otherwise be
* frequently calling sigprocmask() out of an abundance of caution, will
* no longer need to pay its outrageous cost.
*
* Signal handlers should avoid clobbering global variables like `errno`
* because most signals are asynchronous, i.e. the signal handler might
* be called at any assembly instruction. If something like a `SIGCHLD`
* handler doesn't save / restore the `errno` global when calling wait,
* then any i/o logic in the main program that checks `errno` will most
* likely break. This is rare in practice, since systems usually design
* signals to favor delivery from cancellation points before they block
* however that's not guaranteed.
*
* @return 0 on success or -1 w/ errno
* @see xsigaction() for a much better api
* @asyncsignalsafe
* @vforksafe
*/
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) {
int rc;
if (sig == SIGKILL || sig == SIGSTOP) {
rc = einval();
} else {
rc = __sigaction(sig, act, oldact);
if (!rc && act && (uintptr_t)act->sa_handler >= kSigactionMinRva) {
static bool once;
if (!once) {
__interruptible = true;
once = true;
}
}
if (IsWindows() && !rc && sig == SIGWINCH) {
_init_sigwinch(); // lazy b/c sigwinch is otherwise ignored
}
}
STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act),
DescribeSigaction(rc, oldact), rc);
return rc;
}