summaryrefslogtreecommitdiffstats
path: root/lib/fatal-signal.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/fatal-signal.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/lib/fatal-signal.c b/lib/fatal-signal.c
new file mode 100644
index 0000000..90d5a9b
--- /dev/null
+++ b/lib/fatal-signal.c
@@ -0,0 +1,371 @@
+/* Emergency actions in case of a fatal signal.
+ Copyright (C) 2003-2004, 2006-2023 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2003.
+
+ This file is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ This file is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+
+#include <config.h>
+
+/* Specification. */
+#include "fatal-signal.h"
+
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "glthread/lock.h"
+#include "thread-optim.h"
+#include "sig-handler.h"
+
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+
+/* ========================================================================= */
+
+
+/* The list of fatal signals.
+ These are those signals whose default action is to terminate the process
+ without a core dump, except
+ SIGKILL - because it cannot be caught,
+ SIGALRM SIGUSR1 SIGUSR2 SIGPOLL SIGIO SIGLOST - because applications
+ often use them for their own purpose,
+ SIGPROF SIGVTALRM - because they are used for profiling,
+ SIGSTKFLT - because it is more similar to SIGFPE, SIGSEGV, SIGBUS,
+ SIGSYS - because it is more similar to SIGABRT, SIGSEGV,
+ SIGPWR - because it of too special use,
+ SIGRTMIN...SIGRTMAX - because they are reserved for application use.
+ plus
+ SIGXCPU, SIGXFSZ - because they are quite similar to SIGTERM. */
+
+static int fatal_signals[] =
+ {
+ /* ISO C 99 signals. */
+#ifdef SIGINT
+ SIGINT,
+#endif
+#ifdef SIGTERM
+ SIGTERM,
+#endif
+ /* POSIX:2001 signals. */
+#ifdef SIGHUP
+ SIGHUP,
+#endif
+#ifdef SIGPIPE
+ SIGPIPE,
+#endif
+ /* BSD signals. */
+#ifdef SIGXCPU
+ SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+ SIGXFSZ,
+#endif
+ /* Native Windows signals. */
+#ifdef SIGBREAK
+ SIGBREAK,
+#endif
+ 0
+ };
+
+#define num_fatal_signals (SIZEOF (fatal_signals) - 1)
+
+/* Eliminate signals whose signal handler is SIG_IGN. */
+
+static void
+init_fatal_signals (void)
+{
+ /* This function is multithread-safe even without synchronization, because
+ if two threads execute it simultaneously, the fatal_signals[] array will
+ not change any more after the first of the threads has completed this
+ function. */
+ static bool fatal_signals_initialized = false;
+ if (!fatal_signals_initialized)
+ {
+ size_t i;
+
+ for (i = 0; i < num_fatal_signals; i++)
+ {
+ struct sigaction action;
+
+ if (sigaction (fatal_signals[i], NULL, &action) >= 0
+ && get_handler (&action) == SIG_IGN)
+ fatal_signals[i] = -1;
+ }
+
+ fatal_signals_initialized = true;
+ }
+}
+
+
+/* ========================================================================= */
+
+
+typedef _GL_ASYNC_SAFE void (*action_t) (int sig);
+
+/* Type of an entry in the actions array.
+ The 'action' field is accessed from within the fatal_signal_handler(),
+ therefore we mark it as 'volatile'. */
+typedef struct
+{
+ volatile action_t action;
+}
+actions_entry_t;
+
+/* The registered cleanup actions. */
+static actions_entry_t static_actions[32];
+static actions_entry_t * volatile actions = static_actions;
+static sig_atomic_t volatile actions_count = 0;
+static size_t actions_allocated = SIZEOF (static_actions);
+
+
+/* The saved signal handlers.
+ Size 32 would not be sufficient: On HP-UX, SIGXCPU = 33, SIGXFSZ = 34. */
+static struct sigaction saved_sigactions[64];
+
+
+/* Uninstall the handlers. */
+static _GL_ASYNC_SAFE void
+uninstall_handlers (void)
+{
+ size_t i;
+
+ for (i = 0; i < num_fatal_signals; i++)
+ if (fatal_signals[i] >= 0)
+ {
+ int sig = fatal_signals[i];
+ if (saved_sigactions[sig].sa_handler == SIG_IGN)
+ saved_sigactions[sig].sa_handler = SIG_DFL;
+ sigaction (sig, &saved_sigactions[sig], NULL);
+ }
+}
+
+
+/* The signal handler. It gets called asynchronously. */
+static _GL_ASYNC_SAFE void
+fatal_signal_handler (int sig)
+{
+ for (;;)
+ {
+ /* Get the last registered cleanup action, in a reentrant way. */
+ action_t action;
+ size_t n = actions_count;
+ if (n == 0)
+ break;
+ n--;
+ actions_count = n;
+ action = actions[n].action;
+ /* Execute the action. */
+ action (sig);
+ }
+
+ /* Now execute the signal's default action.
+ If the signal being delivered was blocked, the re-raised signal would be
+ delivered when this handler returns. But the way we install this handler,
+ no signal is blocked, and the re-raised signal is delivered already
+ during raise(). */
+ uninstall_handlers ();
+ raise (sig);
+}
+
+
+/* Install the handlers. */
+static void
+install_handlers (void)
+{
+ size_t i;
+ struct sigaction action;
+
+ action.sa_handler = &fatal_signal_handler;
+ /* If we get a fatal signal while executing fatal_signal_handler, enter
+ fatal_signal_handler recursively, since it is reentrant. Hence no
+ SA_RESETHAND. */
+ action.sa_flags = SA_NODEFER;
+ sigemptyset (&action.sa_mask);
+ for (i = 0; i < num_fatal_signals; i++)
+ if (fatal_signals[i] >= 0)
+ {
+ int sig = fatal_signals[i];
+
+ if (!(sig < sizeof (saved_sigactions) / sizeof (saved_sigactions[0])))
+ abort ();
+ sigaction (sig, &action, &saved_sigactions[sig]);
+ }
+}
+
+
+/* Lock that makes at_fatal_signal multi-thread safe. */
+gl_lock_define_initialized (static, at_fatal_signal_lock)
+
+/* Register a cleanup function to be executed when a catchable fatal signal
+ occurs. */
+int
+at_fatal_signal (action_t action)
+{
+ bool mt = gl_multithreaded ();
+
+ if (mt) gl_lock_lock (at_fatal_signal_lock);
+
+ static bool cleanup_initialized = false;
+ if (!cleanup_initialized)
+ {
+ init_fatal_signals ();
+ install_handlers ();
+ cleanup_initialized = true;
+ }
+
+ int ret = 0;
+
+ if (actions_count == actions_allocated)
+ {
+ /* Extend the actions array. Note that we cannot use xrealloc(),
+ because then the cleanup() function could access an already
+ deallocated array. */
+ actions_entry_t *old_actions = actions;
+ size_t old_actions_allocated = actions_allocated;
+ size_t new_actions_allocated = 2 * actions_allocated;
+ actions_entry_t *new_actions =
+ (actions_entry_t *)
+ malloc (new_actions_allocated * sizeof (actions_entry_t));
+ if (new_actions == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+
+ size_t k;
+ /* Don't use memcpy() here, because memcpy takes non-volatile arguments
+ and is therefore not guaranteed to complete all memory stores before
+ the next statement. */
+ for (k = 0; k < old_actions_allocated; k++)
+ new_actions[k] = old_actions[k];
+ actions = new_actions;
+ actions_allocated = new_actions_allocated;
+ /* Now we can free the old actions array. */
+ /* No, we can't do that. If fatal_signal_handler is running in a
+ different thread and has already fetched the actions pointer (getting
+ old_actions) but not yet accessed its n-th element, that thread may
+ crash when accessing an element of the already freed old_actions
+ array. */
+ #if 0
+ if (old_actions != static_actions)
+ free (old_actions);
+ #endif
+ }
+ /* The two uses of 'volatile' in the types above (and ISO C 99 section
+ 5.1.2.3.(5)) ensure that we increment the actions_count only after
+ the new action has been written to the memory location
+ actions[actions_count]. */
+ actions[actions_count].action = action;
+ actions_count++;
+
+ done:
+ if (mt) gl_lock_unlock (at_fatal_signal_lock);
+
+ return ret;
+}
+
+
+/* ========================================================================= */
+
+
+static sigset_t fatal_signal_set;
+
+static void
+do_init_fatal_signal_set (void)
+{
+ size_t i;
+
+ init_fatal_signals ();
+
+ sigemptyset (&fatal_signal_set);
+ for (i = 0; i < num_fatal_signals; i++)
+ if (fatal_signals[i] >= 0)
+ sigaddset (&fatal_signal_set, fatal_signals[i]);
+}
+
+/* Ensure that do_init_fatal_signal_set is called once only. */
+gl_once_define(static, fatal_signal_set_once)
+
+static void
+init_fatal_signal_set (void)
+{
+ gl_once (fatal_signal_set_once, do_init_fatal_signal_set);
+}
+
+/* Lock and counter that allow block_fatal_signals/unblock_fatal_signals pairs
+ to occur in different threads and even overlap in time. */
+gl_lock_define_initialized (static, fatal_signals_block_lock)
+static unsigned int fatal_signals_block_counter = 0;
+
+/* Temporarily delay the catchable fatal signals. */
+void
+block_fatal_signals (void)
+{
+ bool mt = gl_multithreaded ();
+
+ if (mt) gl_lock_lock (fatal_signals_block_lock);
+
+ if (fatal_signals_block_counter++ == 0)
+ {
+ init_fatal_signal_set ();
+ sigprocmask (SIG_BLOCK, &fatal_signal_set, NULL);
+ }
+
+ if (mt) gl_lock_unlock (fatal_signals_block_lock);
+}
+
+/* Stop delaying the catchable fatal signals. */
+void
+unblock_fatal_signals (void)
+{
+ bool mt = gl_multithreaded ();
+
+ if (mt) gl_lock_lock (fatal_signals_block_lock);
+
+ if (fatal_signals_block_counter == 0)
+ /* There are more calls to unblock_fatal_signals() than to
+ block_fatal_signals(). */
+ abort ();
+ if (--fatal_signals_block_counter == 0)
+ {
+ init_fatal_signal_set ();
+ sigprocmask (SIG_UNBLOCK, &fatal_signal_set, NULL);
+ }
+
+ if (mt) gl_lock_unlock (fatal_signals_block_lock);
+}
+
+
+unsigned int
+get_fatal_signals (int signals[64])
+{
+ init_fatal_signal_set ();
+
+ {
+ int *p = signals;
+ size_t i;
+
+ for (i = 0; i < num_fatal_signals; i++)
+ if (fatal_signals[i] >= 0)
+ *p++ = fatal_signals[i];
+ return p - signals;
+ }
+}
+
+const sigset_t *
+get_fatal_signal_set (void)
+{
+ init_fatal_signal_set ();
+ return &fatal_signal_set;
+}