summaryrefslogtreecommitdiff
path: root/mate-session/mdm-signal-handler.c
diff options
context:
space:
mode:
Diffstat (limited to 'mate-session/mdm-signal-handler.c')
-rw-r--r--mate-session/mdm-signal-handler.c553
1 files changed, 553 insertions, 0 deletions
diff --git a/mate-session/mdm-signal-handler.c b/mate-session/mdm-signal-handler.c
new file mode 100644
index 0000000..46a835b
--- /dev/null
+++ b/mate-session/mdm-signal-handler.c
@@ -0,0 +1,553 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006 Red Hat, Inc.
+ * Copyright (C) 2007 William Jon McCann <[email protected]>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#if HAVE_EXECINFO_H
+ #include <execinfo.h>
+#endif
+#include <syslog.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+
+#include "mdm-signal-handler.h"
+
+#define MDM_SIGNAL_HANDLER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((o), MDM_TYPE_SIGNAL_HANDLER, MdmSignalHandlerPrivate))
+
+typedef struct {
+ int signal_number;
+ MdmSignalHandlerFunc func;
+ gpointer data;
+ guint id;
+} CallbackData;
+
+struct MdmSignalHandlerPrivate {
+ GHashTable* lookup;
+ GHashTable* id_lookup;
+ GHashTable* action_lookup;
+ guint next_id;
+ GDestroyNotify fatal_func;
+ gpointer fatal_data;
+};
+
+static void mdm_signal_handler_class_init(MdmSignalHandlerClass* klass);
+static void mdm_signal_handler_init(MdmSignalHandler* signal_handler);
+static void mdm_signal_handler_finalize(GObject* object);
+
+static gpointer signal_handler_object = NULL;
+static int signal_pipes[2];
+static int signals_blocked = 0;
+static sigset_t signals_block_mask;
+static sigset_t signals_oldmask;
+
+G_DEFINE_TYPE(MdmSignalHandler, mdm_signal_handler, G_TYPE_OBJECT)
+
+static void block_signals_push(void)
+{
+ signals_blocked++;
+
+ if (signals_blocked == 1)
+ {
+ /* Set signal mask */
+ sigemptyset(&signals_block_mask);
+ sigfillset(&signals_block_mask);
+ sigprocmask(SIG_BLOCK, &signals_block_mask, &signals_oldmask);
+ }
+}
+
+static void block_signals_pop(void)
+{
+ signals_blocked--;
+
+ if (signals_blocked == 0)
+ {
+ /* Set signal mask */
+ sigprocmask(SIG_SETMASK, &signals_oldmask, NULL);
+ }
+}
+
+static gboolean signal_io_watch(GIOChannel* ioc, GIOCondition condition, MdmSignalHandler* handler)
+{
+ char buf[256];
+ gboolean is_fatal;
+ gsize bytes_read;
+ int i;
+
+ block_signals_push();
+
+ g_io_channel_read_chars(ioc, buf, sizeof(buf), &bytes_read, NULL);
+
+ is_fatal = FALSE;
+
+ for (i = 0; i < bytes_read; i++)
+ {
+ int signum;
+ GSList* handlers;
+ GSList* l;
+
+ signum = (gint32) buf[i];
+
+ g_debug("MdmSignalHandler: handling signal %d", signum);
+ handlers = g_hash_table_lookup(handler->priv->lookup, GINT_TO_POINTER(signum));
+
+ g_debug("MdmSignalHandler: Found %u callbacks", g_slist_length(handlers));
+
+ for (l = handlers; l != NULL; l = l->next)
+ {
+ gboolean res;
+ CallbackData* data;
+
+ data = g_hash_table_lookup(handler->priv->id_lookup, l->data);
+
+ if (data != NULL)
+ {
+ if (data->func != NULL)
+ {
+ g_debug("MdmSignalHandler: running %d handler: %p", signum, data->func);
+
+ res = data->func(signum, data->data);
+
+ if (!res)
+ {
+ is_fatal = TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ block_signals_pop();
+
+ if (is_fatal)
+ {
+ if (handler->priv->fatal_func != NULL)
+ {
+ g_debug("MdmSignalHandler: Caught termination signal - calling fatal func");
+ handler->priv->fatal_func(handler->priv->fatal_data);
+ }
+ else
+ {
+ g_debug("MdmSignalHandler: Caught termination signal - exiting");
+ exit (1);
+ }
+
+ return FALSE;
+ }
+
+ g_debug("MdmSignalHandler: Done handling signals");
+
+ return TRUE;
+}
+
+static void fallback_get_backtrace(void)
+{
+ #if HAVE_EXECINFO_H
+ void* frames[64];
+ size_t size;
+ char** strings;
+ size_t i;
+
+ size = backtrace(frames, G_N_ELEMENTS(frames));
+
+ if ((strings = backtrace_symbols(frames, size)))
+ {
+ syslog(LOG_CRIT, "******************* START ********************************");
+
+ for (i = 0; i < size; i++)
+ {
+ syslog(LOG_CRIT, "Frame %zd: %s", i, strings[i]);
+ }
+
+ free(strings);
+ syslog(LOG_CRIT, "******************* END **********************************");
+ }
+ else
+ {
+ #endif
+ g_warning ("MDM crashed, but symbols couldn't be retrieved.");
+ #if HAVE_EXECINFO_H
+ }
+ #endif
+}
+
+static gboolean crashlogger_get_backtrace(void)
+{
+ gboolean success = FALSE;
+ int pid;
+
+ pid = fork();
+
+ if (pid > 0)
+ {
+ /* Wait for the child to finish */
+ int estatus;
+
+ if (waitpid(pid, &estatus, 0) != -1)
+ {
+ /* Only succeed if the crashlogger succeeded */
+ if (WIFEXITED(estatus) && (WEXITSTATUS(estatus) == 0))
+ {
+ success = TRUE;
+ }
+ }
+ }
+ else if (pid == 0)
+ {
+ /* Child process */
+ execl(LIBEXECDIR "/mdm-crash-logger", LIBEXECDIR "/mdm-crash-logger", NULL);
+ }
+
+ return success;
+}
+
+
+static void mdm_signal_handler_backtrace(void)
+{
+ struct stat s;
+ gboolean fallback = TRUE;
+
+ /* Try to use gdb via mdm-crash-logger if it exists, since
+ * we get much better information out of it. Otherwise
+ * fall back to execinfo.
+ */
+ if (g_stat(LIBEXECDIR "/mdm-crash-logger", &s) == 0)
+ {
+ fallback = crashlogger_get_backtrace() ? FALSE : TRUE;
+ }
+
+ if (fallback)
+ {
+ fallback_get_backtrace();
+ }
+}
+
+static void signal_handler(int signo)
+{
+ static int in_fatal = 0;
+ int ignore;
+ guchar signo_byte = signo;
+
+ /* avoid loops */
+ if (in_fatal > 0)
+ {
+ return;
+ }
+
+ ++in_fatal;
+
+ switch (signo)
+ {
+ case SIGSEGV:
+ case SIGBUS:
+ case SIGILL:
+ case SIGABRT:
+ case SIGTRAP:
+ mdm_signal_handler_backtrace();
+ exit(1);
+ break;
+ case SIGFPE:
+ case SIGPIPE:
+ /* let the fatal signals interrupt us */
+ --in_fatal;
+ mdm_signal_handler_backtrace();
+ ignore = write(signal_pipes [1], &signo_byte, 1);
+ break;
+ default:
+ --in_fatal;
+ ignore = write(signal_pipes [1], &signo_byte, 1);
+ break;
+ }
+}
+
+static void catch_signal(MdmSignalHandler *handler, int signal_number)
+{
+ struct sigaction action;
+ struct sigaction* old_action;
+
+ g_debug("MdmSignalHandler: Registering for %d signals", signal_number);
+
+ action.sa_handler = signal_handler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ old_action = g_new0(struct sigaction, 1);
+
+ sigaction(signal_number, &action, old_action);
+
+ g_hash_table_insert(handler->priv->action_lookup, GINT_TO_POINTER(signal_number), old_action);
+}
+
+static void uncatch_signal(MdmSignalHandler* handler, int signal_number)
+{
+ struct sigaction* old_action;
+
+ g_debug("MdmSignalHandler: Unregistering for %d signals", signal_number);
+
+ old_action = g_hash_table_lookup(handler->priv->action_lookup, GINT_TO_POINTER(signal_number));
+ g_hash_table_remove(handler->priv->action_lookup, GINT_TO_POINTER(signal_number));
+
+ sigaction(signal_number, old_action, NULL);
+
+ g_free(old_action);
+}
+
+guint mdm_signal_handler_add(MdmSignalHandler* handler, int signal_number, MdmSignalHandlerFunc callback, gpointer data)
+{
+ CallbackData* cdata;
+ GSList* list;
+
+ g_return_val_if_fail(MDM_IS_SIGNAL_HANDLER(handler), 0);
+
+ cdata = g_new0(CallbackData, 1);
+ cdata->signal_number = signal_number;
+ cdata->func = callback;
+ cdata->data = data;
+ cdata->id = handler->priv->next_id++;
+
+ g_debug("MdmSignalHandler: Adding handler %u: signum=%d %p", cdata->id, cdata->signal_number, cdata->func);
+
+ if (g_hash_table_lookup(handler->priv->action_lookup, GINT_TO_POINTER(signal_number)) == NULL)
+ {
+ catch_signal(handler, signal_number);
+ }
+
+ /* ID lookup owns the CallbackData */
+ g_hash_table_insert(handler->priv->id_lookup, GUINT_TO_POINTER(cdata->id), cdata);
+
+ list = g_hash_table_lookup(handler->priv->lookup, GINT_TO_POINTER(signal_number));
+ list = g_slist_prepend(list, GUINT_TO_POINTER (cdata->id));
+
+ g_hash_table_insert(handler->priv->lookup, GINT_TO_POINTER(signal_number), list);
+
+ return cdata->id;
+}
+
+void mdm_signal_handler_add_fatal(MdmSignalHandler* handler)
+{
+ g_return_if_fail(MDM_IS_SIGNAL_HANDLER(handler));
+
+ mdm_signal_handler_add(handler, SIGILL, NULL, NULL);
+ mdm_signal_handler_add(handler, SIGBUS, NULL, NULL);
+ mdm_signal_handler_add(handler, SIGSEGV, NULL, NULL);
+ mdm_signal_handler_add(handler, SIGABRT, NULL, NULL);
+ mdm_signal_handler_add(handler, SIGTRAP, NULL, NULL);
+}
+
+static void callback_data_free(CallbackData* d)
+{
+ g_free(d);
+}
+
+static void mdm_signal_handler_remove_and_free_data(MdmSignalHandler* handler, CallbackData* cdata)
+{
+ GSList* list;
+
+ g_return_if_fail(MDM_IS_SIGNAL_HANDLER(handler));
+
+ list = g_hash_table_lookup(handler->priv->lookup, GINT_TO_POINTER(cdata->signal_number));
+ list = g_slist_remove_all(list, GUINT_TO_POINTER(cdata->id));
+
+ if (list == NULL)
+ {
+ uncatch_signal(handler, cdata->signal_number);
+ }
+
+ g_debug("MdmSignalHandler: Removing handler %u: signum=%d %p", cdata->signal_number, cdata->id, cdata->func);
+ /* put changed list back in */
+ g_hash_table_insert(handler->priv->lookup, GINT_TO_POINTER(cdata->signal_number), list);
+
+ g_hash_table_remove(handler->priv->id_lookup, GUINT_TO_POINTER(cdata->id));
+}
+
+void mdm_signal_handler_remove(MdmSignalHandler* handler, guint id)
+{
+ CallbackData* found;
+
+ g_return_if_fail(MDM_IS_SIGNAL_HANDLER(handler));
+
+ found = g_hash_table_lookup(handler->priv->id_lookup, GUINT_TO_POINTER(id));
+
+ if (found != NULL)
+ {
+ mdm_signal_handler_remove_and_free_data(handler, found);
+ found = NULL;
+ }
+}
+
+static CallbackData* find_callback_data_by_func(MdmSignalHandler* handler, guint signal_number, MdmSignalHandlerFunc callback, gpointer data)
+{
+ GSList* list;
+ GSList* l;
+ CallbackData* found;
+
+ found = NULL;
+
+ list = g_hash_table_lookup(handler->priv->lookup, GINT_TO_POINTER(signal_number));
+
+ for (l = list; l != NULL; l = l->next)
+ {
+ guint id;
+ CallbackData* d;
+
+ id = GPOINTER_TO_UINT(l->data);
+
+ d = g_hash_table_lookup(handler->priv->id_lookup, GUINT_TO_POINTER (id));
+
+ if (d != NULL && d->func == callback && d->data == data)
+ {
+ found = d;
+ break;
+ }
+ }
+
+ return found;
+}
+
+void mdm_signal_handler_remove_func(MdmSignalHandler* handler, guint signal_number, MdmSignalHandlerFunc callback, gpointer data)
+{
+ CallbackData* found;
+
+ g_return_if_fail(MDM_IS_SIGNAL_HANDLER(handler));
+
+ found = find_callback_data_by_func(handler, signal_number, callback, data);
+
+ if (found != NULL)
+ {
+ mdm_signal_handler_remove_and_free_data(handler, found);
+ found = NULL;
+ }
+
+ /* FIXME: once all handlers are removed deregister signum handler */
+}
+
+static void mdm_signal_handler_class_init(MdmSignalHandlerClass* klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = mdm_signal_handler_finalize;
+
+ g_type_class_add_private(klass, sizeof(MdmSignalHandlerPrivate));
+}
+
+static void signal_list_free(GSList *list)
+{
+ g_slist_free(list);
+}
+
+void mdm_signal_handler_set_fatal_func(MdmSignalHandler* handler, MdmShutdownHandlerFunc func, gpointer user_data)
+{
+ g_return_if_fail(MDM_IS_SIGNAL_HANDLER(handler));
+
+ handler->priv->fatal_func = func;
+ handler->priv->fatal_data = user_data;
+}
+
+static void mdm_signal_handler_init(MdmSignalHandler* handler)
+{
+ GIOChannel* ioc;
+
+ handler->priv = MDM_SIGNAL_HANDLER_GET_PRIVATE(handler);
+
+ handler->priv->next_id = 1;
+
+ handler->priv->lookup = g_hash_table_new(NULL, NULL);
+ handler->priv->id_lookup = g_hash_table_new(NULL, NULL);
+ handler->priv->action_lookup = g_hash_table_new(NULL, NULL);
+
+ if (pipe(signal_pipes) == -1)
+ {
+ g_error ("Could not create pipe() for signal handling");
+ }
+
+ ioc = g_io_channel_unix_new(signal_pipes[0]);
+ g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
+ g_io_add_watch_full(ioc, G_PRIORITY_HIGH, G_IO_IN, (GIOFunc) signal_io_watch, handler, NULL);
+ g_io_channel_set_close_on_unref(ioc, TRUE);
+ g_io_channel_unref(ioc);
+}
+
+static void mdm_signal_handler_finalize(GObject* object)
+{
+ MdmSignalHandler* handler;
+ GList* l;
+
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(MDM_IS_SIGNAL_HANDLER(object));
+
+ handler = MDM_SIGNAL_HANDLER(object);
+
+ g_debug("MdmSignalHandler: Finalizing signal handler");
+
+ g_return_if_fail(handler->priv != NULL);
+
+ for (l = g_hash_table_get_values(handler->priv->lookup); l != NULL; l = l->next)
+ {
+ signal_list_free((GSList*) l->data);
+ }
+
+ g_hash_table_destroy(handler->priv->lookup);
+
+ for (l = g_hash_table_get_values(handler->priv->id_lookup); l != NULL; l = l->next)
+ {
+ callback_data_free((CallbackData*) l->data);
+ }
+
+ g_hash_table_destroy(handler->priv->id_lookup);
+
+ for (l = g_hash_table_get_values(handler->priv->action_lookup); l != NULL; l = l->next)
+ {
+ g_free(l->data);
+ }
+
+ g_hash_table_destroy(handler->priv->action_lookup);
+
+ close(signal_pipes[0]);
+ close(signal_pipes[1]);
+
+ G_OBJECT_CLASS(mdm_signal_handler_parent_class)->finalize(object);
+}
+
+MdmSignalHandler* mdm_signal_handler_new(void)
+{
+ if (signal_handler_object != NULL)
+ {
+ g_object_ref(signal_handler_object);
+ }
+ else
+ {
+ signal_handler_object = g_object_new(MDM_TYPE_SIGNAL_HANDLER, NULL);
+ g_object_add_weak_pointer(signal_handler_object, (gpointer*) &signal_handler_object);
+ }
+
+ return MDM_SIGNAL_HANDLER(signal_handler_object);
+}