Add signal handling with the GMainLoop
[dana/openbox.git] / obt / signal.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    obt/signal.c for the Openbox window manager
4    Copyright (c) 2010        Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "signal.h"
20
21 #ifdef HAVE_STDIO_H
22 #include <stdio.h>
23 #endif
24 #ifdef HAVE_STDLIB_H
25 #include <stdlib.h>
26 #endif
27 #ifdef HAVE_SIGNAL_H
28 #  include <signal.h>
29 #endif
30 #ifdef HAVE_UNISTD_H
31 #  include <unistd.h>
32 #endif
33
34 typedef struct _ObtSignalCallback ObtSignalCallback;
35
36 struct _ObtSignalCallback
37 {
38     ObtSignalHandler func;
39     gpointer data;
40 };
41
42 static gboolean signal_prepare(GSource *source, gint *timeout);
43 static gboolean signal_check(GSource *source);
44 static gboolean signal_occured(GSource *source, GSourceFunc callback,
45                                gpointer data);
46 static void sighandler(gint sig);
47
48 /* this should be more than the number of possible signals on any
49    architecture... */
50 #define NUM_SIGNALS 99
51
52 /* a set of all possible signals */
53 static sigset_t all_signals_set;
54
55 /* keep track of what signals have a signal handler installed, and remember
56    the action we replaced when installing it for when we clean up */
57 static struct {
58     guint installed; /* a ref count */
59     struct sigaction oldact;
60 } all_signals[NUM_SIGNALS];
61
62 /* signals which cause a core dump, these can't be used for callbacks */
63 static const gint core_signals[] =
64 {
65     SIGABRT,
66     SIGSEGV,
67     SIGFPE,
68     SIGILL,
69     SIGQUIT,
70     SIGTRAP,
71     SIGSYS,
72     SIGBUS,
73     SIGXCPU,
74     SIGXFSZ
75 };
76 #define NUM_CORE_SIGNALS (gint)(sizeof(core_signals) / sizeof(core_signals[0]))
77
78 static GSourceFuncs source_funcs = {
79     signal_prepare,
80     signal_check,
81     signal_occured,
82     NULL
83 };
84 static GSource *gsource = NULL;
85 static guint listeners = 0; /* a ref count for the signal listener */
86 static gboolean signal_fired;
87 guint signals_fired[NUM_SIGNALS];
88 GSList *callbacks[NUM_SIGNALS];
89
90 void obt_signal_listen(void)
91 {
92     if (!listeners) {
93         guint i;
94         struct sigaction action;
95         sigset_t sigset;
96
97         /* initialize the all_signals_set */
98         sigfillset(&all_signals_set);
99
100         sigemptyset(&sigset);
101         action.sa_handler = sighandler;
102         action.sa_mask = sigset;
103         action.sa_flags = SA_NOCLDSTOP;
104
105         /* always grab all the signals that cause core dumps */
106         for (i = 0; i < NUM_CORE_SIGNALS; ++i) {
107             /* SIGABRT is curiously not grabbed here!! that's because when we
108                get one of the core_signals, we use abort() to dump the core.
109                And having the abort() only go back to our signal handler again
110                is less than optimal */
111             if (core_signals[i] != SIGABRT) {
112                 sigaction(core_signals[i], &action,
113                           &all_signals[core_signals[i]].oldact);
114                 all_signals[core_signals[i]].installed++;
115             }
116         }
117
118         gsource = g_source_new(&source_funcs, sizeof(GSource));
119         g_source_set_priority(gsource, G_PRIORITY_HIGH);
120
121         g_source_attach(gsource, NULL);
122     }
123
124     ++listeners;
125 }
126
127 void obt_signal_stop(void)
128 {
129     --listeners;
130
131     if (!listeners) {
132         gint i;
133         GSList *it, *next;
134
135         g_source_unref(gsource);
136         gsource = NULL;
137
138         /* remove user defined signal handlers */
139         for (i = 0; i < NUM_SIGNALS; ++i)
140             for (it = callbacks[i]; it; it = next) {
141                 ObtSignalCallback *cb = it->data;
142                 next = g_slist_next(it);
143                 obt_signal_remove_callback(i, cb->func);
144             }
145
146         /* release all the signals that cause core dumps */
147         for (i = 0; i < NUM_CORE_SIGNALS; ++i) {
148             if (all_signals[core_signals[i]].installed) {
149                 sigaction(core_signals[i],
150                           &all_signals[core_signals[i]].oldact, NULL);
151                 all_signals[core_signals[i]].installed--;
152             }
153         }
154
155 #ifdef DEBUG
156         for (i = 0; i < NUM_SIGNALS; ++i)
157             g_assert(all_signals[i].installed == 0);
158 #endif
159     }
160 }
161
162 void obt_signal_add_callback(gint sig, ObtSignalHandler func, gpointer data)
163 {
164     ObtSignalCallback *cb;
165     gint i;
166
167     g_return_if_fail(func != NULL);
168     g_return_if_fail(sig >= 0 && sig <= NUM_SIGNALS);
169     for (i = 0; i < NUM_CORE_SIGNALS; ++i)
170         g_return_if_fail(sig != core_signals[i]);
171
172     cb = g_slice_new(ObtSignalCallback);
173     cb->func = func;
174     cb->data = data;
175     callbacks[sig] = g_slist_prepend(callbacks[sig], cb);
176
177     /* install the signal handler */
178     if (!all_signals[sig].installed) {
179         struct sigaction action;
180         sigset_t sigset;
181
182         sigemptyset(&sigset);
183         action.sa_handler = sighandler;
184         action.sa_mask = sigset;
185         action.sa_flags = SA_NOCLDSTOP;
186
187         sigaction(sig, &action, &all_signals[sig].oldact);
188     }
189
190     all_signals[sig].installed++;
191 }
192
193 void obt_signal_remove_callback(gint sig, ObtSignalHandler func)
194 {
195     GSList *it;
196     gint i;
197
198     g_return_if_fail(func != NULL);
199     g_return_if_fail(sig >= 0 && sig <= NUM_SIGNALS);
200     for (i = 0; i < NUM_CORE_SIGNALS; ++i)
201         g_return_if_fail(sig != core_signals[i]);
202
203     for (it = callbacks[sig]; it; it = g_slist_next(it)) {
204         ObtSignalCallback *cb = it->data;
205         if (cb->func == func) {
206             g_assert(all_signals[sig].installed > 0);
207
208             callbacks[sig] = g_slist_delete_link(callbacks[sig], it);
209             g_slice_free(ObtSignalCallback, cb);
210
211             /* uninstall the signal handler */
212             all_signals[sig].installed--;
213             if (!all_signals[sig].installed)
214                 sigaction(sig, &all_signals[sig].oldact, NULL);
215             break;
216         }
217     }
218 }
219
220 static gboolean signal_prepare(GSource *source, gint *timeout)
221 {
222     *timeout = -1;
223     return signal_fired;
224 }
225
226 static gboolean signal_check(GSource *source)
227 {
228     return signal_fired;
229 }
230
231 static gboolean signal_occured(GSource *source, GSourceFunc callback,
232                                gpointer data)
233 {
234     guint i;
235     sigset_t oldset;
236     guint fired[NUM_SIGNALS];
237
238     /* block signals so that we can do this without the data changing
239        on us */
240     sigprocmask(SIG_SETMASK, &all_signals_set, &oldset);
241
242     /* make a copy of the signals that fired */
243     for (i = 0; i < NUM_SIGNALS; ++i) {
244         fired[i] = signals_fired[i];
245         signals_fired[i] = 0;
246     }
247     signal_fired = FALSE;
248
249     sigprocmask(SIG_SETMASK, &oldset, NULL);
250
251     /* call the signal callbacks for the signals */
252     for (i = 0; i < NUM_SIGNALS; ++i) {
253         while (fired[i]) {
254             GSList *it;
255             for (it = callbacks[i]; it; it = g_slist_next(it)) {
256                 const ObtSignalCallback *cb = it->data;
257                 cb->func(i, cb->data);
258             }
259             --fired[i];
260         }
261     }
262
263     return TRUE; /* repeat */
264 }
265
266 static void sighandler(gint sig)
267 {
268     guint i;
269
270     g_return_if_fail(sig < NUM_SIGNALS);
271
272     for (i = 0; i < NUM_CORE_SIGNALS; ++i)
273         if (sig == core_signals[i]) {
274             /* XXX special case for signals that default to core dump.
275                but throw some helpful output here... */
276
277             fprintf(stderr, "How are you gentlemen? All your base are"
278                     " belong to us. (Openbox received signal %d)\n", sig);
279
280             /* die with a core dump */
281             abort();
282         }
283
284     signal_fired = TRUE;
285     ++signals_fired[sig];
286
287     /* i don't think we want to modify the GMainContext inside a signal
288        handler, so use a GSource instead of an idle func to call back
289        to the application */
290 }