summaryrefslogtreecommitdiffstats
path: root/lib/cleanup.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/cleanup.c279
1 files changed, 279 insertions, 0 deletions
diff --git a/lib/cleanup.c b/lib/cleanup.c
new file mode 100644
index 0000000..02bef2a
--- /dev/null
+++ b/lib/cleanup.c
@@ -0,0 +1,279 @@
+/*
+ * cleanup.c -- simple dynamic cleanup function management
+ * Copyright (C) 1995 Markus Armbruster.
+ * Copyright (C) 2007 Colin Watson.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; see the file docs/COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
+ * Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h> /* SunOS's losing assert.h needs it */
+#include <assert.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "xalloc.h"
+
+#include "manconfig.h" /* for FATAL */
+#include "cleanup.h"
+
+
+
+/* Dealing with signals */
+
+
+/* saved signal actions */
+#ifdef SIGHUP
+static struct sigaction saved_hup_action;
+#endif /* SIGHUP */
+static struct sigaction saved_int_action;
+static struct sigaction saved_term_action;
+
+
+/* Run cleanups, then reraise signal with default handler. */
+static _Noreturn void
+sighandler (int signo)
+{
+ struct sigaction act;
+ sigset_t set;
+
+ do_cleanups_sigsafe (true);
+
+ /* set default signal action */
+ memset (&act, 0, sizeof act);
+ act.sa_handler = SIG_DFL;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction (signo, &act, NULL)) {
+ /* should not happen */
+ _exit (FATAL); /* exit() is taboo from signal handlers! */
+ }
+
+ /* unmask signo */
+ if ( sigemptyset (&set)
+ || sigaddset (&set, signo)
+ || sigprocmask (SIG_UNBLOCK, &set, NULL)) {
+ /* shouldn't happen */
+ _exit (FATAL); /* exit() is taboo from signal handlers! */
+ }
+
+ /* signal has now default action and is unmasked,
+ reraise it to terminate program abnormally */
+ raise (signo);
+ abort();
+}
+
+
+/* Save signo's current action to oldact, if its handler is SIG_DFL
+ install sighandler, return 0 on success, -1 on failure. */
+static int
+trap_signal (int signo, struct sigaction *oldact)
+{
+ if (sigaction (signo, NULL, oldact)) {
+ return -1;
+ }
+
+ if (oldact->sa_handler == SIG_DFL) {
+ struct sigaction act;
+
+ memset (&act, 0, sizeof act);
+ act.sa_handler = sighandler;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ return sigaction (signo, &act, oldact);
+ }
+
+ return 0;
+}
+
+
+/* Trap some abnormal exits to call do_cleanups(). */
+static int
+trap_abnormal_exits (void)
+{
+#ifdef SIGHUP
+ if (trap_signal (SIGHUP, &saved_hup_action))
+ return -1;
+#endif /* SIGHUP */
+ if (trap_signal (SIGINT, &saved_int_action))
+ return -1;
+ if (trap_signal (SIGTERM, &saved_term_action))
+ return -1;
+ return 0;
+}
+
+
+/* Restore signo's action from oldact if its current handler is
+ sighandler, return 0 on success, -1 on failure. */
+static int
+untrap_signal (int signo, struct sigaction *oldact)
+{
+ struct sigaction act;
+ if (sigaction (signo, NULL, &act)) {
+ return -1;
+ }
+
+ if (act.sa_handler == sighandler) {
+ return sigaction (signo, oldact, NULL);
+ }
+
+ return 0;
+}
+
+
+/* Undo a previous trap_abnormal_exits(). */
+static int
+untrap_abnormal_exits (void)
+{
+#ifdef SIGHUP
+ if (untrap_signal (SIGHUP, &saved_hup_action))
+ return -1;
+#endif /* SIGHUP */
+ if (untrap_signal (SIGINT, &saved_int_action))
+ return -1;
+ if (untrap_signal (SIGTERM, &saved_term_action))
+ return -1;
+ return 0;
+}
+
+
+
+typedef struct {
+ cleanup_fun fun;
+ void *arg;
+ int sigsafe;
+} slot;
+
+static slot *stack = NULL; /* stack of cleanup functions */
+static unsigned nslots = 0; /* #slots in stack */
+static unsigned tos = 0; /* top of stack, 0 <= tos <= nslots */
+
+/* Call cleanup functions in stack from from top to bottom,
+ * Automatically called on program termination via exit(3) or default
+ * action for SIGHUP, SIGINT or SIGTERM.
+ * Since this may be called from a signal handler, do not use free().
+ * If in_sighandler is true, cleanup functions with sigsafe=0 will not be
+ * called.
+ */
+void
+do_cleanups_sigsafe (bool in_sighandler)
+{
+ unsigned i;
+
+ assert (tos <= nslots);
+ for (i = tos; i > 0; --i)
+ if (!in_sighandler || stack[i-1].sigsafe)
+ stack[i-1].fun (stack[i-1].arg);
+}
+
+/* Call cleanup functions in stack from from top to bottom,
+ * Automatically called on program termination via exit(3).
+ */
+void
+do_cleanups (void)
+{
+ do_cleanups_sigsafe (false);
+ tos = 0;
+ nslots = 0;
+ free (stack);
+ stack = NULL;
+}
+
+
+/* Push a cleanup function on the cleanup stack,
+ * return 0 on success, -1 on failure.
+ * Caution: the cleanup function may be called from signal handlers if
+ * sigsafe=1. If you just want a convenient atexit() wrapper, pass
+ * sigsafe=0.
+ */
+int
+push_cleanup (cleanup_fun fun, void *arg, int sigsafe)
+{
+ static bool handler_installed = false;
+
+ assert (tos <= nslots);
+
+ if (!handler_installed) {
+ if (atexit (do_cleanups))
+ return -1;
+ handler_installed = true;
+ }
+
+ if (tos == nslots) {
+ /* stack is full, allocate another slot */
+ /* stack is not expected to grow much, otherwise we would double it */
+ slot *new_stack;
+
+ if (stack) {
+ new_stack = xnrealloc (stack, nslots+1, sizeof (slot));
+ } else {
+ new_stack = xnmalloc (nslots+1, sizeof (slot));
+ }
+
+ if (!new_stack) return -1;
+ stack = new_stack;
+ ++nslots;
+ }
+
+ assert (tos < nslots);
+ stack[tos].fun = fun;
+ stack[tos].arg = arg;
+ stack[tos].sigsafe = sigsafe;
+ ++tos;
+
+
+ trap_abnormal_exits();
+
+ return 0;
+}
+
+
+/* Remove topmost cleanup function from the cleanup stack that matches the
+ * given values.
+ */
+void
+pop_cleanup (cleanup_fun fun, void *arg)
+{
+ unsigned i, j;
+
+ assert (tos > 0);
+
+ for (i = tos; i > 0; --i) {
+ if (stack[i-1].fun == fun && stack[i-1].arg == arg) {
+ for (j = i; j < tos; ++j)
+ stack[j-1] = stack[j];
+ --tos;
+ break;
+ }
+ }
+
+ if (tos == 0) untrap_abnormal_exits();
+}
+
+
+/* Pop all cleanup functions from the cleanup stack. */
+void
+pop_all_cleanups (void)
+{
+ tos = 0;
+ untrap_abnormal_exits();
+}