1 /*
2  * Copyright (c) 2007 Brian Tarricone <bjt23@cornell.edu>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301 USA
18  */
19 
20 /**
21  * SECTION: xfce-posix-signal-handler
22  * @title: POSIX Signal Handling
23  * @short_description: a callback system for handling POSIX signals safely
24  *
25  * Due to reentrancy issues, there is a restricted set of functions/syscalls
26  * that are allowed to be performed inside a POSIX signal handler.  In
27  * general, it's safer to defer any signal-related processing until after the
28  * signal handler has run.  The functionality in this module automatically
29  * handles this, and allows you to set a handler function (with optional user
30  * data) for any signal.
31  */
32 
33 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif
36 
37 #include <stdio.h>
38 
39 #ifdef HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 
43 #ifdef HAVE_SIGNAL_H
44 #include <signal.h>
45 #endif
46 
47 #ifdef HAVE_STRING_H
48 #include <string.h>
49 #endif
50 
51 #ifdef HAVE_ERRNO_H
52 #include <errno.h>
53 #endif
54 
55 #include <glib.h>
56 
57 #include <libxfce4util/libxfce4util.h>
58 #include <libxfce4util/libxfce4util-alias.h>
59 
60 #define SIGNAL_PIPE_READ   __signal_pipe[0]
61 #define SIGNAL_PIPE_WRITE  __signal_pipe[1]
62 
63 typedef struct
64 {
65     gint signal_id;
66     XfcePosixSignalHandler handler;
67     gpointer user_data;
68     struct sigaction old_sa;
69 } XfcePosixSignalHandlerData;
70 
71 static gboolean __inited = FALSE;
72 static int __signal_pipe[2] = { -1, -1 };
73 static GHashTable *__handlers = NULL;
74 static GIOChannel *__signal_io = NULL;
75 static guint __io_watch_id = 0;
76 
77 
78 static void
xfce_posix_signal_handler_data_free(XfcePosixSignalHandlerData * hdata)79 xfce_posix_signal_handler_data_free(XfcePosixSignalHandlerData *hdata)
80 {
81     if(!hdata)
82         return;
83 
84     sigaction(hdata->signal_id, &hdata->old_sa, NULL);
85     g_free(hdata);
86 }
87 
88 static gboolean
xfce_posix_signal_handler_pipe_io(GIOChannel * source,GIOCondition condition,gpointer data)89 xfce_posix_signal_handler_pipe_io(GIOChannel *source,
90                                   GIOCondition condition,
91                                   gpointer data)
92 {
93     gint signal_id = 0;
94     GError *error = NULL;
95     gsize bin = 0;
96     XfcePosixSignalHandlerData *hdata;
97 
98     if(G_IO_STATUS_NORMAL == g_io_channel_read_chars(source, (gchar *)&signal_id,
99                                                      sizeof(signal_id), &bin,
100                                                      &error)
101        && bin == sizeof(signal_id))
102     {
103         hdata = g_hash_table_lookup(__handlers, GINT_TO_POINTER(signal_id));
104         if(hdata)
105             hdata->handler(signal_id, hdata->user_data);
106     } else {
107         if(error) {
108             g_critical("Signal pipe read failed: %s\n", error->message);
109             g_error_free(error);
110         } else {
111             g_critical("Short read from signal pipe (expected %d, got %d)\n",
112                        (int)sizeof(signal_id), (int)bin);
113         }
114     }
115 
116     return TRUE;
117 }
118 
119 static void
xfce_posix_signal_handler(gint signal_id)120 xfce_posix_signal_handler(gint signal_id)
121 {
122     write(SIGNAL_PIPE_WRITE, &signal_id, sizeof(signal_id));
123 }
124 
125 
126 /**
127  * xfce_posix_signal_handler_init:
128  * @error: (out) (allow-none) (transfer full): Location of a #GError to store any possible errors.
129  *
130  * Initializes the POSIX signal handler system.  Must be called
131  * before setting any POSIX signal handlers.
132  *
133  * Returns: %TRUE on success, %FALSE on failure, in which case
134  *          @error will be set.
135  **/
136 gboolean
xfce_posix_signal_handler_init(GError ** error)137 xfce_posix_signal_handler_init(GError **error)
138 {
139     if(G_UNLIKELY(__inited))
140         return TRUE;
141 
142     if(pipe(__signal_pipe)) {
143         if(error) {
144             g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno),
145                         _("pipe() failed: %s"), strerror(errno));
146         }
147         return FALSE;
148     }
149 
150     __handlers = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
151                                        (GDestroyNotify)xfce_posix_signal_handler_data_free);
152 
153     __signal_io = g_io_channel_unix_new(SIGNAL_PIPE_READ);
154     g_io_channel_set_close_on_unref(__signal_io, FALSE);
155     g_io_channel_set_encoding(__signal_io, NULL, NULL);
156     g_io_channel_set_buffered(__signal_io, FALSE);
157     __io_watch_id = g_io_add_watch(__signal_io, G_IO_IN,
158                                    xfce_posix_signal_handler_pipe_io, NULL);
159 
160     __inited = TRUE;
161     return TRUE;
162 }
163 
164 /**
165  * xfce_posix_signal_handler_shutdown:
166  *
167  * Frees all memory associated with the POSIX signal handling system
168  * and restores all default signal handlers.
169  **/
170 void
xfce_posix_signal_handler_shutdown(void)171 xfce_posix_signal_handler_shutdown(void)
172 {
173     if(G_UNLIKELY(!__inited))
174         return;
175 
176     g_source_remove(__io_watch_id);
177     __io_watch_id = 0;
178     g_io_channel_unref(__signal_io);
179     __signal_io = NULL;
180 
181     g_hash_table_destroy(__handlers);
182     __handlers = NULL;
183 
184     close(SIGNAL_PIPE_READ);
185     SIGNAL_PIPE_READ = -1;
186     close(SIGNAL_PIPE_WRITE);
187     SIGNAL_PIPE_WRITE = -1;
188 
189     __inited = FALSE;
190 }
191 
192 /**
193  * xfce_posix_signal_handler_set_handler:
194  * @signal: A POSIX signal id number.
195  * @handler: (scope call): A callback function.
196  * @user_data: Arbitrary data that will be passed to @handler.
197  * @error: (out) (allow-none) (transfer full): Location of a #GError to store any possible errors.
198  *
199  * Sets @handler to be called whenever @signal is caught by the
200  * application.  The @user_data parameter will be passed as an argument
201  * to @handler.
202  *
203  * Returns: %TRUE on success, %FALSE otherwise, in which case
204  *          @error will be set.
205  **/
206 gboolean
xfce_posix_signal_handler_set_handler(gint signal,XfcePosixSignalHandler handler,gpointer user_data,GError ** error)207 xfce_posix_signal_handler_set_handler(gint signal,
208                                       XfcePosixSignalHandler handler,
209                                       gpointer user_data,
210                                       GError **error)
211 {
212     XfcePosixSignalHandlerData *hdata;
213     struct sigaction sa;
214 
215     if(G_UNLIKELY(!__inited)) {
216         if(error) {
217             g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
218                         _("xfce_posix_signal_handler_init() must be called first"));
219         }
220         return FALSE;
221     }
222 
223     if(!handler) {
224         g_warning("NULL signal handler supplied; removing existing handler");
225         xfce_posix_signal_handler_restore_handler(signal);
226         return TRUE;
227     }
228 
229     if(g_hash_table_lookup(__handlers, GINT_TO_POINTER(signal)))
230         xfce_posix_signal_handler_restore_handler(signal);
231 
232     hdata = g_new0(XfcePosixSignalHandlerData, 1);
233     hdata->signal_id = signal;
234     hdata->handler = handler;
235     hdata->user_data = user_data;
236 
237     memset(&sa, 0, sizeof(sa));
238     sa.sa_handler = xfce_posix_signal_handler;
239     sa.sa_flags = SA_RESTART;
240 
241     if(sigaction(signal, &sa, &hdata->old_sa)) {
242         if(error) {
243             g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno),
244                         _("sigaction() failed: %s\n"), strerror(errno));
245         }
246         g_free(hdata);
247         return FALSE;
248     }
249 
250     g_hash_table_insert(__handlers, GINT_TO_POINTER(signal), hdata);
251 
252     return TRUE;
253 }
254 
255 /**
256  * xfce_posix_signal_handler_restore_handler:
257  * @signal: A POSIX signal id number.
258  *
259  * Restores the default handler for @signal.
260  **/
261 void
xfce_posix_signal_handler_restore_handler(gint signal)262 xfce_posix_signal_handler_restore_handler(gint signal)
263 {
264     if(G_UNLIKELY(!__inited))
265         return;
266 
267     g_hash_table_remove(__handlers, GINT_TO_POINTER(signal));
268 }
269 
270 
271 
272 #define __XFCE_POSIX_SIGNAL_HANDLER_C__
273 #include <libxfce4util/libxfce4util-aliasdef.c>
274