/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- obt/signal.c for the Openbox window manager Copyright (c) 2010 Dana Jansens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. See the COPYING file for a copy of the GNU General Public License. */ #include "signal.h" #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_SIGNAL_H # include #endif #ifdef HAVE_UNISTD_H # include #endif typedef struct _ObtSignalCallback ObtSignalCallback; struct _ObtSignalCallback { ObtSignalHandler func; gpointer data; }; static gboolean signal_prepare(GSource *source, gint *timeout); static gboolean signal_check(GSource *source); static gboolean signal_occurred(GSource *source, GSourceFunc callback, gpointer data); static void sighandler(gint sig); /* this should be more than the number of possible signals on any architecture... */ #define NUM_SIGNALS 99 /* a set of all possible signals */ static sigset_t all_signals_set; /* keep track of what signals have a signal handler installed, and remember the action we replaced when installing it for when we clean up */ static struct { guint installed; /* a ref count */ struct sigaction oldact; } all_signals[NUM_SIGNALS]; /* signals which cause a core dump, these can't be used for callbacks */ static const gint core_signals[] = { SIGABRT, SIGSEGV, SIGFPE, SIGILL, SIGQUIT, SIGTRAP, SIGSYS, SIGBUS, SIGXCPU, SIGXFSZ }; #define NUM_CORE_SIGNALS (gint)(sizeof(core_signals) / sizeof(core_signals[0])) static GSourceFuncs source_funcs = { signal_prepare, signal_check, signal_occurred, NULL }; static GSource *gsource = NULL; static guint listeners = 0; /* a ref count for the signal listener */ static gboolean signal_fired; guint signals_fired[NUM_SIGNALS]; GSList *callbacks[NUM_SIGNALS]; void obt_signal_listen(void) { if (!listeners) { guint i; struct sigaction action; sigset_t sigset; /* initialize the all_signals_set */ sigfillset(&all_signals_set); sigemptyset(&sigset); action.sa_handler = sighandler; action.sa_mask = sigset; action.sa_flags = SA_NOCLDSTOP; /* always grab all the signals that cause core dumps */ for (i = 0; i < NUM_CORE_SIGNALS; ++i) { /* SIGABRT is curiously not grabbed here!! that's because when we get one of the core_signals, we use abort() to dump the core. And having the abort() only go back to our signal handler again is less than optimal */ if (core_signals[i] != SIGABRT) { sigaction(core_signals[i], &action, &all_signals[core_signals[i]].oldact); all_signals[core_signals[i]].installed++; } } gsource = g_source_new(&source_funcs, sizeof(GSource)); g_source_set_priority(gsource, G_PRIORITY_HIGH); g_source_attach(gsource, NULL); } ++listeners; } void obt_signal_stop(void) { --listeners; if (!listeners) { gint i; GSList *it, *next; g_source_unref(gsource); gsource = NULL; /* remove user defined signal handlers */ for (i = 0; i < NUM_SIGNALS; ++i) for (it = callbacks[i]; it; it = next) { ObtSignalCallback *cb = it->data; next = g_slist_next(it); obt_signal_remove_callback(i, cb->func); } /* release all the signals that cause core dumps */ for (i = 0; i < NUM_CORE_SIGNALS; ++i) { if (all_signals[core_signals[i]].installed) { sigaction(core_signals[i], &all_signals[core_signals[i]].oldact, NULL); all_signals[core_signals[i]].installed--; } } #ifdef DEBUG for (i = 0; i < NUM_SIGNALS; ++i) g_assert(all_signals[i].installed == 0); #endif } } void obt_signal_add_callback(gint sig, ObtSignalHandler func, gpointer data) { ObtSignalCallback *cb; gint i; g_return_if_fail(func != NULL); g_return_if_fail(sig >= 0 && sig <= NUM_SIGNALS); for (i = 0; i < NUM_CORE_SIGNALS; ++i) g_return_if_fail(sig != core_signals[i]); cb = g_slice_new(ObtSignalCallback); cb->func = func; cb->data = data; callbacks[sig] = g_slist_prepend(callbacks[sig], cb); /* install the signal handler */ if (!all_signals[sig].installed) { struct sigaction action; sigset_t sigset; sigemptyset(&sigset); action.sa_handler = sighandler; action.sa_mask = sigset; action.sa_flags = SA_NOCLDSTOP; sigaction(sig, &action, &all_signals[sig].oldact); } all_signals[sig].installed++; } void obt_signal_remove_callback(gint sig, ObtSignalHandler func) { GSList *it; gint i; g_return_if_fail(func != NULL); g_return_if_fail(sig >= 0 && sig <= NUM_SIGNALS); for (i = 0; i < NUM_CORE_SIGNALS; ++i) g_return_if_fail(sig != core_signals[i]); for (it = callbacks[sig]; it; it = g_slist_next(it)) { ObtSignalCallback *cb = it->data; if (cb->func == func) { g_assert(all_signals[sig].installed > 0); callbacks[sig] = g_slist_delete_link(callbacks[sig], it); g_slice_free(ObtSignalCallback, cb); /* uninstall the signal handler */ all_signals[sig].installed--; if (!all_signals[sig].installed) sigaction(sig, &all_signals[sig].oldact, NULL); break; } } } static gboolean signal_prepare(GSource *source, gint *timeout) { *timeout = -1; return signal_fired; } static gboolean signal_check(GSource *source) { return signal_fired; } static gboolean signal_occurred(GSource *source, GSourceFunc callback, gpointer data) { guint i; sigset_t oldset; guint fired[NUM_SIGNALS]; /* block signals so that we can do this without the data changing on us */ sigprocmask(SIG_SETMASK, &all_signals_set, &oldset); /* make a copy of the signals that fired */ for (i = 0; i < NUM_SIGNALS; ++i) { fired[i] = signals_fired[i]; signals_fired[i] = 0; } signal_fired = FALSE; sigprocmask(SIG_SETMASK, &oldset, NULL); /* call the signal callbacks for the signals */ for (i = 0; i < NUM_SIGNALS; ++i) { while (fired[i]) { GSList *it; for (it = callbacks[i]; it; it = g_slist_next(it)) { const ObtSignalCallback *cb = it->data; cb->func(i, cb->data); } --fired[i]; } } return TRUE; /* repeat */ } static void sighandler(gint sig) { guint i; g_return_if_fail(sig < NUM_SIGNALS); for (i = 0; i < NUM_CORE_SIGNALS; ++i) if (sig == core_signals[i]) { /* XXX special case for signals that default to core dump. but throw some helpful output here... */ fprintf(stderr, "How are you gentlemen? All your base are" " belong to us. (Openbox received signal %d)\n", sig); /* die with a core dump */ abort(); } signal_fired = TRUE; ++signals_fired[sig]; /* i don't think we want to modify the GMainContext inside a signal handler, so use a GSource instead of an idle func to call back to the application */ }