Add signal_code_to_string() in signal-util.c and cover the si_code values defined in libc's siginfo-consts.h. Fall back to the numeric value when no symbolic name is known. Co-developed-by: Codex (GPT-5) <noreply@openai.com> Signed-off-by: Emanuele Rocca <emanuele.rocca@arm.com>
492 lines
14 KiB
C
492 lines
14 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <threads.h>
|
|
#include <unistd.h>
|
|
|
|
#include "errno-util.h"
|
|
#include "parse-util.h"
|
|
#include "process-util.h"
|
|
#include "signal-util.h"
|
|
#include "stdio-util.h"
|
|
#include "string-table.h"
|
|
#include "string-util.h"
|
|
|
|
int reset_all_signal_handlers(void) {
|
|
int ret = 0, r;
|
|
|
|
for (int sig = 1; sig < _NSIG; sig++) {
|
|
|
|
/* These two cannot be caught... */
|
|
if (IN_SET(sig, SIGKILL, SIGSTOP))
|
|
continue;
|
|
|
|
/* On Linux the first two RT signals are reserved by glibc, and sigaction() will return
|
|
* EINVAL for them. */
|
|
r = RET_NERRNO(sigaction(sig, &sigaction_default, NULL));
|
|
if (r != -EINVAL)
|
|
RET_GATHER(ret, r);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int reset_signal_mask(void) {
|
|
sigset_t ss;
|
|
|
|
if (sigemptyset(&ss) < 0)
|
|
return -errno;
|
|
|
|
return RET_NERRNO(sigprocmask(SIG_SETMASK, &ss, NULL));
|
|
}
|
|
|
|
int sigaction_many_internal(const struct sigaction *sa, ...) {
|
|
int sig, r = 0;
|
|
va_list ap;
|
|
|
|
va_start(ap, sa);
|
|
|
|
/* negative signal ends the list. 0 signal is skipped. */
|
|
while ((sig = va_arg(ap, int)) >= 0) {
|
|
|
|
if (sig == 0)
|
|
continue;
|
|
|
|
RET_GATHER(r, RET_NERRNO(sigaction(sig, sa, NULL)));
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int sigset_add_many_ap(sigset_t *ss, va_list ap) {
|
|
int sig, r = 0;
|
|
|
|
assert(ss);
|
|
|
|
while ((sig = va_arg(ap, int)) >= 0) {
|
|
|
|
if (sig == 0)
|
|
continue;
|
|
|
|
if (sigaddset(ss, sig) < 0) {
|
|
if (r >= 0)
|
|
r = -errno;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int sigset_add_many_internal(sigset_t *ss, ...) {
|
|
va_list ap;
|
|
int r;
|
|
|
|
va_start(ap, ss);
|
|
r = sigset_add_many_ap(ss, ap);
|
|
va_end(ap);
|
|
|
|
return r;
|
|
}
|
|
|
|
int sigprocmask_many_internal(int how, sigset_t *ret_old_mask, ...) {
|
|
va_list ap;
|
|
sigset_t ss;
|
|
int r;
|
|
|
|
if (sigemptyset(&ss) < 0)
|
|
return -errno;
|
|
|
|
va_start(ap, ret_old_mask);
|
|
r = sigset_add_many_ap(&ss, ap);
|
|
va_end(ap);
|
|
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return RET_NERRNO(sigprocmask(how, &ss, ret_old_mask));
|
|
}
|
|
|
|
static const char *const static_signal_table[] = {
|
|
[SIGHUP] = "HUP",
|
|
[SIGINT] = "INT",
|
|
[SIGQUIT] = "QUIT",
|
|
[SIGILL] = "ILL",
|
|
[SIGTRAP] = "TRAP",
|
|
[SIGABRT] = "ABRT",
|
|
[SIGBUS] = "BUS",
|
|
[SIGFPE] = "FPE",
|
|
[SIGKILL] = "KILL",
|
|
[SIGUSR1] = "USR1",
|
|
[SIGSEGV] = "SEGV",
|
|
[SIGUSR2] = "USR2",
|
|
[SIGPIPE] = "PIPE",
|
|
[SIGALRM] = "ALRM",
|
|
[SIGTERM] = "TERM",
|
|
#ifdef SIGSTKFLT
|
|
[SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */
|
|
#endif
|
|
[SIGCHLD] = "CHLD",
|
|
[SIGCONT] = "CONT",
|
|
[SIGSTOP] = "STOP",
|
|
[SIGTSTP] = "TSTP",
|
|
[SIGTTIN] = "TTIN",
|
|
[SIGTTOU] = "TTOU",
|
|
[SIGURG] = "URG",
|
|
[SIGXCPU] = "XCPU",
|
|
[SIGXFSZ] = "XFSZ",
|
|
[SIGVTALRM] = "VTALRM",
|
|
[SIGPROF] = "PROF",
|
|
[SIGWINCH] = "WINCH",
|
|
[SIGIO] = "IO",
|
|
[SIGPWR] = "PWR",
|
|
[SIGSYS] = "SYS"
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(static_signal, int);
|
|
|
|
const char* signal_to_string(int signo) {
|
|
static thread_local char buf[STRLEN("RTMIN+") + DECIMAL_STR_MAX(int)];
|
|
const char *name;
|
|
|
|
name = static_signal_to_string(signo);
|
|
if (name)
|
|
return name;
|
|
|
|
if (signo >= SIGRTMIN && signo <= SIGRTMAX)
|
|
xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN);
|
|
else
|
|
xsprintf(buf, "%d", signo);
|
|
|
|
return buf;
|
|
}
|
|
|
|
int signal_from_string(const char *s) {
|
|
const char *p;
|
|
int signo, r;
|
|
|
|
/* Check that the input is a signal number. */
|
|
if (safe_atoi(s, &signo) >= 0) {
|
|
if (SIGNAL_VALID(signo))
|
|
return signo;
|
|
else
|
|
return -ERANGE;
|
|
}
|
|
|
|
/* Drop "SIG" prefix. */
|
|
if (startswith(s, "SIG"))
|
|
s += 3;
|
|
|
|
/* Check that the input is a signal name. */
|
|
signo = static_signal_from_string(s);
|
|
if (signo > 0)
|
|
return signo;
|
|
|
|
/* Check that the input is RTMIN or
|
|
* RTMIN+n (0 <= n <= SIGRTMAX-SIGRTMIN). */
|
|
p = startswith(s, "RTMIN");
|
|
if (p) {
|
|
if (*p == '\0')
|
|
return SIGRTMIN;
|
|
if (*p != '+')
|
|
return -EINVAL;
|
|
|
|
r = safe_atoi(p, &signo);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (signo < 0 || signo > SIGRTMAX - SIGRTMIN)
|
|
return -ERANGE;
|
|
|
|
return signo + SIGRTMIN;
|
|
}
|
|
|
|
/* Check that the input is RTMAX or
|
|
* RTMAX-n (0 <= n <= SIGRTMAX-SIGRTMIN). */
|
|
p = startswith(s, "RTMAX");
|
|
if (p) {
|
|
if (*p == '\0')
|
|
return SIGRTMAX;
|
|
if (*p != '-')
|
|
return -EINVAL;
|
|
|
|
r = safe_atoi(p, &signo);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (signo > 0 || signo < SIGRTMIN - SIGRTMAX)
|
|
return -ERANGE;
|
|
|
|
return signo + SIGRTMAX;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const char *const sigill_code_table[] = {
|
|
[ILL_ILLOPC] = "ILL_ILLOPC",
|
|
[ILL_ILLOPN] = "ILL_ILLOPN",
|
|
[ILL_ILLADR] = "ILL_ILLADR",
|
|
[ILL_ILLTRP] = "ILL_ILLTRP",
|
|
[ILL_PRVOPC] = "ILL_PRVOPC",
|
|
[ILL_PRVREG] = "ILL_PRVREG",
|
|
[ILL_COPROC] = "ILL_COPROC",
|
|
[ILL_BADSTK] = "ILL_BADSTK",
|
|
[ILL_BADIADDR] = "ILL_BADIADDR",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigill_code, int);
|
|
|
|
static const char *const sigfpe_code_table[] = {
|
|
[FPE_INTDIV] = "FPE_INTDIV",
|
|
[FPE_INTOVF] = "FPE_INTOVF",
|
|
[FPE_FLTDIV] = "FPE_FLTDIV",
|
|
[FPE_FLTOVF] = "FPE_FLTOVF",
|
|
[FPE_FLTUND] = "FPE_FLTUND",
|
|
[FPE_FLTRES] = "FPE_FLTRES",
|
|
[FPE_FLTINV] = "FPE_FLTINV",
|
|
[FPE_FLTSUB] = "FPE_FLTSUB",
|
|
[FPE_FLTUNK] = "FPE_FLTUNK",
|
|
[FPE_CONDTRAP] = "FPE_CONDTRAP",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigfpe_code, int);
|
|
|
|
static const char *const sigsegv_code_table[] = {
|
|
[SEGV_MAPERR] = "SEGV_MAPERR",
|
|
[SEGV_ACCERR] = "SEGV_ACCERR",
|
|
[SEGV_BNDERR] = "SEGV_BNDERR",
|
|
[SEGV_PKUERR] = "SEGV_PKUERR",
|
|
[SEGV_ACCADI] = "SEGV_ACCADI",
|
|
[SEGV_ADIDERR] = "SEGV_ADIDERR",
|
|
[SEGV_ADIPERR] = "SEGV_ADIPERR",
|
|
[SEGV_MTEAERR] = "SEGV_MTEAERR",
|
|
[SEGV_MTESERR] = "SEGV_MTESERR",
|
|
[SEGV_CPERR] = "SEGV_CPERR",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigsegv_code, int);
|
|
|
|
static const char *const sigbus_code_table[] = {
|
|
[BUS_ADRALN] = "BUS_ADRALN",
|
|
[BUS_ADRERR] = "BUS_ADRERR",
|
|
[BUS_OBJERR] = "BUS_OBJERR",
|
|
[BUS_MCEERR_AR] = "BUS_MCEERR_AR",
|
|
[BUS_MCEERR_AO] = "BUS_MCEERR_AO",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigbus_code, int);
|
|
|
|
static const char *const sigtrap_code_table[] = {
|
|
[TRAP_BRKPT] = "TRAP_BRKPT",
|
|
[TRAP_TRACE] = "TRAP_TRACE",
|
|
[TRAP_BRANCH] = "TRAP_BRANCH",
|
|
[TRAP_HWBKPT] = "TRAP_HWBKPT",
|
|
[TRAP_UNK] = "TRAP_UNK",
|
|
[TRAP_PERF] = "TRAP_PERF",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigtrap_code, int);
|
|
|
|
static const char *const sigsys_code_table[] = {
|
|
[SYS_SECCOMP] = "SYS_SECCOMP",
|
|
[SYS_USER_DISPATCH] = "SYS_USER_DISPATCH",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigsys_code, int);
|
|
|
|
/* sigchld_code_table is already defined in src/basic/process-util.c with
|
|
* decoded, lower-case values (CLD_EXITED -> "exited"). For consistency with
|
|
* all other values decoded here, provide an uppercase variant. */
|
|
static const char *const sigchld_uppercase_code_table[] = {
|
|
[CLD_EXITED] = "CLD_EXITED",
|
|
[CLD_KILLED] = "CLD_KILLED",
|
|
[CLD_DUMPED] = "CLD_DUMPED",
|
|
[CLD_TRAPPED] = "CLD_TRAPPED",
|
|
[CLD_STOPPED] = "CLD_STOPPED",
|
|
[CLD_CONTINUED] = "CLD_CONTINUED",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigchld_uppercase_code, int);
|
|
|
|
static const char *const sigpoll_code_table[] = {
|
|
[POLL_IN] = "POLL_IN",
|
|
[POLL_OUT] = "POLL_OUT",
|
|
[POLL_MSG] = "POLL_MSG",
|
|
[POLL_ERR] = "POLL_ERR",
|
|
[POLL_PRI] = "POLL_PRI",
|
|
[POLL_HUP] = "POLL_HUP",
|
|
};
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigpoll_code, int);
|
|
|
|
const char* signal_code_to_string(int signo, int code) {
|
|
switch (code) {
|
|
/* SI_KERNEL is 0x80 (128). Defining a table just for SI_KERNEL and
|
|
* SI_USER would be a waste of .rodata space: special case them instead. */
|
|
case SI_KERNEL:
|
|
return "SI_KERNEL";
|
|
case SI_USER:
|
|
return "SI_USER";
|
|
/* The following values are all negatives. SI_ASYNCNL is -60. Similarly
|
|
* to the special case for SI_KERNEL above, avoid using a table for negative
|
|
* values as well. */
|
|
case SI_ASYNCNL:
|
|
return "SI_ASYNCNL";
|
|
case SI_DETHREAD:
|
|
return "SI_DETHREAD";
|
|
case SI_TKILL:
|
|
return "SI_TKILL";
|
|
case SI_SIGIO:
|
|
return "SI_SIGIO";
|
|
case SI_ASYNCIO:
|
|
return "SI_ASYNCIO";
|
|
case SI_MESGQ:
|
|
return "SI_MESGQ";
|
|
case SI_TIMER:
|
|
return "SI_TIMER";
|
|
case SI_QUEUE:
|
|
return "SI_QUEUE";
|
|
}
|
|
|
|
switch (signo) {
|
|
case SIGILL:
|
|
return sigill_code_to_string(code);
|
|
case SIGFPE:
|
|
return sigfpe_code_to_string(code);
|
|
case SIGSEGV:
|
|
return sigsegv_code_to_string(code);
|
|
case SIGBUS:
|
|
return sigbus_code_to_string(code);
|
|
case SIGTRAP:
|
|
return sigtrap_code_to_string(code);
|
|
case SIGCHLD:
|
|
return sigchld_uppercase_code_to_string(code);
|
|
case SIGPOLL:
|
|
return sigpoll_code_to_string(code);
|
|
case SIGSYS:
|
|
return sigsys_code_to_string(code);
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void nop_signal_handler(int sig) {
|
|
/* nothing here */
|
|
}
|
|
|
|
int signal_is_blocked(int sig) {
|
|
sigset_t ss;
|
|
int r;
|
|
|
|
r = pthread_sigmask(SIG_SETMASK, NULL, &ss);
|
|
if (r != 0)
|
|
return -r;
|
|
|
|
return RET_NERRNO(sigismember(&ss, sig));
|
|
}
|
|
|
|
int autoreaping_enabled(void) {
|
|
struct sigaction sa;
|
|
|
|
if (sigaction(SIGCHLD, /* act= */ NULL, &sa) < 0)
|
|
return -errno;
|
|
|
|
return sa.sa_handler == SIG_IGN || FLAGS_SET(sa.sa_flags, SA_NOCLDWAIT);
|
|
}
|
|
|
|
int pop_pending_signal_internal(int sig, ...) {
|
|
sigset_t ss;
|
|
va_list ap;
|
|
int r;
|
|
|
|
if (sig < 0) /* Empty list? */
|
|
return -EINVAL;
|
|
|
|
if (sigemptyset(&ss) < 0)
|
|
return -errno;
|
|
|
|
/* Add first signal (if the signal is zero, we'll silently skip it, to make it easier to build
|
|
* parameter lists where some element are sometimes off, similar to how sigset_add_many_ap() handles
|
|
* this.) */
|
|
if (sig > 0 && sigaddset(&ss, sig) < 0)
|
|
return -errno;
|
|
|
|
/* Add all other signals */
|
|
va_start(ap, sig);
|
|
r = sigset_add_many_ap(&ss, ap);
|
|
va_end(ap);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sigtimedwait(&ss, NULL, &(const struct timespec) {});
|
|
if (r < 0) {
|
|
if (errno == EAGAIN)
|
|
return 0;
|
|
|
|
return -errno;
|
|
}
|
|
|
|
return r; /* Returns the signal popped */
|
|
}
|
|
|
|
void propagate_signal(int sig, siginfo_t *siginfo) {
|
|
pid_t p;
|
|
|
|
/* To be called from a signal handler. Will raise the same signal again, in our process + in our threads.
|
|
*
|
|
* Note that we use getpid() instead of getpid_cached(). We might have forked with raw_clone()
|
|
* earlier (see PID 1), and hence let's go to the raw syscall here. In particular as this is not
|
|
* performance sensitive code.
|
|
*
|
|
* Note that we use kill() rather than raise() as fallback, for similar reasons. */
|
|
|
|
p = getpid();
|
|
|
|
if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0)
|
|
assert_se(kill(p, sig) >= 0);
|
|
}
|
|
|
|
const struct sigaction sigaction_ignore = {
|
|
.sa_handler = SIG_IGN,
|
|
.sa_flags = SA_RESTART,
|
|
};
|
|
|
|
const struct sigaction sigaction_default = {
|
|
.sa_handler = SIG_DFL,
|
|
.sa_flags = SA_RESTART,
|
|
};
|
|
|
|
const struct sigaction sigaction_nop_nocldstop = {
|
|
.sa_handler = nop_signal_handler,
|
|
.sa_flags = SA_NOCLDSTOP|SA_RESTART,
|
|
};
|
|
|
|
int parse_signo(const char *s, int *ret) {
|
|
int sig, r;
|
|
|
|
r = safe_atoi(s, &sig);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!SIGNAL_VALID(sig))
|
|
return -EINVAL;
|
|
|
|
if (ret)
|
|
*ret = sig;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sigterm_process_group_handler(int signal, siginfo_t *info, void *ucontext) {
|
|
assert(signal == SIGTERM);
|
|
assert(info);
|
|
|
|
/* If the sender is not us, propagate the signal to all processes in
|
|
* the same process group */
|
|
if (si_code_from_process(info->si_code) &&
|
|
pid_is_valid(info->si_pid) &&
|
|
info->si_pid != getpid_cached())
|
|
(void) kill(0, signal);
|
|
}
|