mirror of
https://github.com/netdata/netdata.git
synced 2025-05-10 11:50:55 +00:00

* do not recurse cleanup on shutdown * make exit_initiated sigatomic_t and hide it behind a function * fix freebsd and windows
271 lines
9.6 KiB
C
271 lines
9.6 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "common.h"
|
|
#include "daemon/daemon-status-file.h"
|
|
|
|
#ifdef ENABLE_SENTRY
|
|
#include "sentry-native/sentry-native.h"
|
|
#endif
|
|
|
|
typedef enum signal_action {
|
|
NETDATA_SIGNAL_IGNORE,
|
|
NETDATA_SIGNAL_EXIT_CLEANLY,
|
|
#if defined(FSANITIZE_ADDRESS)
|
|
NETDATA_SIGNAL_EXIT_NOW,
|
|
#endif
|
|
NETDATA_SIGNAL_REOPEN_LOGS,
|
|
NETDATA_SIGNAL_RELOAD_HEALTH,
|
|
NETDATA_SIGNAL_DEADLY,
|
|
} SIGNAL_ACTION;
|
|
|
|
static struct {
|
|
int signo; // the signal
|
|
const char *name; // the name of the signal
|
|
size_t count; // the number of signals received
|
|
SIGNAL_ACTION action; // the action to take
|
|
EXIT_REASON reason;
|
|
} signals_waiting[] = {
|
|
{ SIGPIPE, "SIGPIPE", 0, NETDATA_SIGNAL_IGNORE, EXIT_REASON_NONE },
|
|
{ SIGINT , "SIGINT", 0, NETDATA_SIGNAL_EXIT_CLEANLY, EXIT_REASON_SIGINT },
|
|
{ SIGQUIT, "SIGQUIT", 0, NETDATA_SIGNAL_EXIT_CLEANLY, EXIT_REASON_SIGQUIT },
|
|
{ SIGTERM, "SIGTERM", 0, NETDATA_SIGNAL_EXIT_CLEANLY, EXIT_REASON_SIGTERM },
|
|
{ SIGHUP, "SIGHUP", 0, NETDATA_SIGNAL_REOPEN_LOGS, EXIT_REASON_NONE },
|
|
#if defined(FSANITIZE_ADDRESS)
|
|
{ SIGUSR1, "SIGUSR1", 0, NETDATA_SIGNAL_EXIT_NOW, EXIT_REASON_NONE },
|
|
#endif
|
|
{ SIGUSR2, "SIGUSR2", 0, NETDATA_SIGNAL_RELOAD_HEALTH, EXIT_REASON_NONE },
|
|
{ SIGBUS, "SIGBUS", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGBUS },
|
|
{ SIGSEGV, "SIGSEGV", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGSEGV },
|
|
{ SIGFPE, "SIGFPE", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGFPE },
|
|
{ SIGILL, "SIGILL", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGILL },
|
|
{ SIGABRT, "SIGABRT", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGABRT },
|
|
{ SIGSYS, "SIGSYS", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGSYS },
|
|
{ SIGXCPU, "SIGXCPU", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGXCPU },
|
|
{ SIGXFSZ, "SIGXFSZ", 0, NETDATA_SIGNAL_DEADLY, EXIT_REASON_SIGXFSZ },
|
|
};
|
|
|
|
static void (*original_handlers[NSIG])(int) = {0};
|
|
static void (*original_sigactions[NSIG])(int, siginfo_t *, void *) = {0};
|
|
|
|
void nd_signal_handler(int signo, siginfo_t *info, void *context __maybe_unused) {
|
|
|
|
for(size_t i = 0; i < _countof(signals_waiting) ; i++) {
|
|
if(signals_waiting[i].signo != signo)
|
|
continue;
|
|
|
|
signals_waiting[i].count++;
|
|
|
|
#if defined(FSANITIZE_ADDRESS)
|
|
if(signals_waiting[i].action == NETDATA_SIGNAL_EXIT_NOW)
|
|
exit(1);
|
|
#endif
|
|
|
|
if(signals_waiting[i].action == NETDATA_SIGNAL_DEADLY) {
|
|
bool chained_handler = original_sigactions[signo] || (original_handlers[signo] && original_handlers[signo] != SIG_IGN && original_handlers[signo] != SIG_DFL);
|
|
|
|
// Update the status file
|
|
SIGNAL_CODE sc = info ? signal_code(signo, info->si_code) : 0;
|
|
|
|
// Get fault address based on signal type
|
|
void *fault_address = NULL;
|
|
if (info && (signo == SIGSEGV || signo == SIGBUS || signo == SIGILL || signo == SIGFPE))
|
|
fault_address = info->si_addr;
|
|
|
|
if(daemon_status_file_deadly_signal_received(signals_waiting[i].reason, sc, fault_address, chained_handler)) {
|
|
// this is a duplicate event, do not send it to sentry
|
|
#ifdef ENABLE_SENTRY
|
|
nd_sentry_crash_report(false);
|
|
#else
|
|
chained_handler = false;
|
|
#endif
|
|
}
|
|
|
|
// log it
|
|
char b[1024];
|
|
strncpyz(b, "SIGNAL HANDLER: received deadly signal: ", sizeof(b) - 1);
|
|
strcat(b, signals_waiting[i].name);
|
|
if(sc) {
|
|
char buf[128];
|
|
SIGNAL_CODE_2str_h(sc, buf, sizeof(buf));
|
|
|
|
strcat(b, " (");
|
|
strcat(b, buf);
|
|
strcat(b, ")");
|
|
}
|
|
strcat(b, " in thread ");
|
|
print_uint64(&b[strlen(b)], gettid_cached());
|
|
strcat(b, " ");
|
|
strcat(b, nd_thread_tag_async_safe());
|
|
strcat(b, "!\n");
|
|
|
|
if(write(STDERR_FILENO, b, strlen(b)) == -1) {
|
|
// nothing to do - we cannot write but there is no way to complain about it
|
|
;
|
|
}
|
|
|
|
// Chain to the original handler if it exists
|
|
if(chained_handler) {
|
|
if (original_sigactions[signo]) {
|
|
original_sigactions[signo](signo, info, context);
|
|
return; // Original handler should handle the signal
|
|
}
|
|
|
|
if (original_handlers[signo]) {
|
|
original_handlers[signo](signo);
|
|
return; // Original handler should handle the signal
|
|
}
|
|
}
|
|
|
|
// If there's no original handler or we can't chain, reset to default and re-raise
|
|
struct sigaction sa;
|
|
sa.sa_handler = SIG_DFL;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = 0;
|
|
sigaction(signo, &sa, NULL);
|
|
|
|
// Re-raise the signal, which now uses the default action.
|
|
raise(signo);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Unmask all signals the netdata main signal handler uses.
|
|
// All other signals remain masked.
|
|
static void posix_unmask_my_signals(void) {
|
|
sigset_t sigset;
|
|
sigemptyset(&sigset);
|
|
|
|
for (size_t i = 0; i < _countof(signals_waiting) ; i++)
|
|
sigaddset(&sigset, signals_waiting[i].signo);
|
|
|
|
if (pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) != 0)
|
|
netdata_log_error("SIGNAL: cannot unmask netdata signals");
|
|
}
|
|
|
|
void nd_cleanup_deadly_signals(void) {
|
|
struct sigaction act;
|
|
memset(&act, 0, sizeof(struct sigaction));
|
|
|
|
// ignore all signals while we run in a signal handler
|
|
sigfillset(&act.sa_mask);
|
|
|
|
for (size_t i = 0; i < _countof(signals_waiting); i++) {
|
|
if(signals_waiting[i].action != NETDATA_SIGNAL_DEADLY)
|
|
continue;
|
|
|
|
act.sa_flags = 0;
|
|
act.sa_handler = SIG_DFL;
|
|
|
|
if (sigaction(signals_waiting[i].signo, &act, NULL) == -1)
|
|
netdata_log_error("SIGNAL: Failed to cleanup signal handler for: %s", signals_waiting[i].name);
|
|
}
|
|
|
|
memset(original_handlers, 0, sizeof(original_handlers));
|
|
memset(original_sigactions, 0, sizeof(original_sigactions));
|
|
}
|
|
|
|
void nd_initialize_signals(bool chain_existing) {
|
|
signals_block_all_except_deadly();
|
|
|
|
struct sigaction act;
|
|
memset(&act, 0, sizeof(struct sigaction));
|
|
|
|
// ignore all signals while we run in a signal handler
|
|
sigfillset(&act.sa_mask);
|
|
|
|
for (size_t i = 0; i < _countof(signals_waiting); i++) {
|
|
int signo = signals_waiting[i].signo;
|
|
|
|
// If chaining is requested, get the current handler first
|
|
struct sigaction old_act;
|
|
if (chain_existing &&
|
|
sigaction(signo, NULL, &old_act) == 0 &&
|
|
(uintptr_t)old_act.sa_handler != (uintptr_t)nd_signal_handler) {
|
|
// Save the original handlers for chaining
|
|
if (old_act.sa_flags & SA_SIGINFO)
|
|
original_sigactions[signo] = old_act.sa_sigaction;
|
|
else
|
|
original_handlers[signo] = old_act.sa_handler;
|
|
}
|
|
|
|
switch (signals_waiting[i].action) {
|
|
case NETDATA_SIGNAL_IGNORE:
|
|
act.sa_flags = 0;
|
|
act.sa_handler = SIG_IGN;
|
|
break;
|
|
default:
|
|
act.sa_flags = SA_SIGINFO;
|
|
act.sa_sigaction = nd_signal_handler;
|
|
break;
|
|
}
|
|
|
|
if (sigaction(signals_waiting[i].signo, &act, NULL) == -1)
|
|
netdata_log_error("SIGNAL: Failed to change signal handler for: %s", signals_waiting[i].name);
|
|
}
|
|
}
|
|
|
|
static void process_triggered_signals(void) {
|
|
size_t found;
|
|
do {
|
|
found = 0;
|
|
for (size_t i = 0; i < _countof(signals_waiting) ; i++) {
|
|
if (!signals_waiting[i].count)
|
|
continue;
|
|
|
|
found++;
|
|
signals_waiting[i].count = 0;
|
|
const char *name = signals_waiting[i].name;
|
|
|
|
switch (signals_waiting[i].action) {
|
|
case NETDATA_SIGNAL_RELOAD_HEALTH:
|
|
nd_log_limits_unlimited();
|
|
netdata_log_info("SIGNAL: Received %s. Reloading HEALTH configuration...", name);
|
|
nd_log_limits_reset();
|
|
execute_command(CMD_RELOAD_HEALTH, NULL, NULL);
|
|
break;
|
|
|
|
case NETDATA_SIGNAL_REOPEN_LOGS:
|
|
nd_log_limits_unlimited();
|
|
netdata_log_info("SIGNAL: Received %s. Reopening all log files...", name);
|
|
nd_log_limits_reset();
|
|
execute_command(CMD_REOPEN_LOGS, NULL, NULL);
|
|
break;
|
|
|
|
case NETDATA_SIGNAL_EXIT_CLEANLY:
|
|
nd_log_limits_unlimited();
|
|
netdata_log_info("SIGNAL: Received %s. Cleaning up to exit...", name);
|
|
commands_exit();
|
|
netdata_cleanup_and_exit_gracefully(signals_waiting[i].reason);
|
|
exit(0);
|
|
break;
|
|
|
|
case NETDATA_SIGNAL_DEADLY:
|
|
_exit(1);
|
|
break;
|
|
|
|
default:
|
|
netdata_log_info("SIGNAL: Received %s. No signal handler configured. Ignoring it.", name);
|
|
break;
|
|
}
|
|
}
|
|
} while(found);
|
|
}
|
|
|
|
void nd_process_signals(void) {
|
|
posix_unmask_my_signals();
|
|
const usec_t save_every_ut = 15 * 60 * USEC_PER_SEC;
|
|
usec_t last_update_mt = now_monotonic_usec();
|
|
|
|
while (true) {
|
|
usec_t mt = now_monotonic_usec();
|
|
if ((mt - last_update_mt) >= save_every_ut) {
|
|
daemon_status_file_update_status(DAEMON_STATUS_NONE);
|
|
last_update_mt += save_every_ut;
|
|
}
|
|
|
|
poll(NULL, 0, 13 * MSEC_PER_SEC + 379);
|
|
process_triggered_signals();
|
|
}
|
|
}
|