#include "config.h"
#include "glib-unix.h"
+#include "gmain-internal.h"
#include <string.h>
/**
* SECTION:gunix
- * @short_description: UNIX-specific utilities and integration
+ * @title: UNIX-specific utilities and integration
+ * @short_description: pipes, signal handling
* @include: glib-unix.h
*
* Most of GLib is intended to be portable; in constrast, this set of
* @error: a #GError
*
* Similar to the UNIX pipe() call, but on modern systems like Linux
- * uses the pipe2 system call, which atomically creates a pipe with
+ * uses the pipe2() system call, which atomically creates a pipe with
* the configured flags. The only supported flag currently is
- * FD_CLOEXEC. If for example you want to configure O_NONBLOCK, that
+ * %FD_CLOEXEC. If for example you want to configure %O_NONBLOCK, that
* must still be done separately with fcntl().
*
- * <note>This function does *not* take O_CLOEXEC, it takes FD_CLOEXEC as if
+ * <note>This function does *not* take %O_CLOEXEC, it takes %FD_CLOEXEC as if
* for fcntl(); these are different on Linux/glibc.</note>
*
* Returns: %TRUE on success, %FALSE if not (and errno will be set).
}
return TRUE;
}
+
+/**
+ * g_unix_signal_source_new:
+ * @signum: A signal number
+ *
+ * Create a #GSource that will be dispatched upon delivery of the UNIX
+ * signal @signum. Currently only %SIGHUP, %SIGINT, and %SIGTERM can
+ * be monitored. Note that unlike the UNIX default, all sources which
+ * have created a watch will be dispatched, regardless of which
+ * underlying thread invoked g_unix_signal_create_watch().
+ *
+ * For example, an effective use of this function is to handle SIGTERM
+ * cleanly; flushing any outstanding files, and then calling
+ * g_main_loop_quit (). It is not safe to do any of this a regular
+ * UNIX signal handler; your handler may be invoked while malloc() or
+ * another library function is running, causing reentrancy if you
+ * attempt to use it from the handler. None of the GLib/GObject API
+ * is safe against this kind of reentrancy.
+ *
+ * The interaction of this source when combined with native UNIX
+ * functions like sigprocmask() is not defined.
+ *
+ * <note>For reliable behavior, if your program links to gthread
+ * (either directly or indirectly via GObject, GIO, or a higher level
+ * library), you should ensure g_thread_init() is called before using
+ * this function. For example, if your program uses GObject, call
+ * g_type_init().</note>
+ *
+ * The source will not initially be associated with any #GMainContext
+ * and must be added to one with g_source_attach() before it will be
+ * executed.
+ *
+ * Returns: A newly created #GSource
+ */
+GSource *
+g_unix_signal_source_new (int signum)
+{
+ g_return_val_if_fail (signum == SIGHUP || signum == SIGINT || signum == SIGTERM, NULL);
+
+ return _g_main_create_unix_signal_watch (signum);
+}
+
+/**
+ * g_unix_signal_add_watch_full:
+ * @signum: Signal number
+ * @priority: the priority of the signal source. Typically this will be in
+ * the range between #G_PRIORITY_DEFAULT and #G_PRIORITY_HIGH.
+ * @handler: Callback
+ * @user_data: Data for @handler
+ * @notify: #GDestroyNotify for @handler
+ *
+ * A convenience function for g_unix_signal_source_new(), which
+ * attaches to the default #GMainContext. You can remove the watch
+ * using g_source_remove().
+ *
+ * Returns: An ID (greater than 0) for the event source
+ */
+guint
+g_unix_signal_add_watch_full (int signum,
+ int priority,
+ GSourceFunc handler,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ guint id;
+ GSource *source;
+
+ source = g_unix_signal_source_new (signum);
+
+ if (priority != G_PRIORITY_DEFAULT)
+ g_source_set_priority (source, priority);
+
+ g_source_set_callback (source, handler, user_data, notify);
+ id = g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ return id;
+}
typedef struct _GTimeoutSource GTimeoutSource;
typedef struct _GChildWatchSource GChildWatchSource;
+typedef struct _GUnixSignalWatchSource GUnixSignalWatchSource;
typedef struct _GPollRec GPollRec;
typedef struct _GSourceCallback GSourceCallback;
#endif /* G_OS_WIN32 */
};
+struct _GUnixSignalWatchSource
+{
+ GSource source;
+ int signum;
+ gboolean pending;
+};
+
struct _GPollRec
{
GPollFD *fd;
static gboolean g_child_watch_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data);
+#ifdef G_OS_UNIX
+static void g_unix_signal_handler (int signum);
+static void init_unix_signal_wakeup_state_unlocked (void);
+static void init_unix_signal_wakeup_state (void);
+static gboolean g_unix_signal_watch_prepare (GSource *source,
+ gint *timeout);
+static gboolean g_unix_signal_watch_check (GSource *source);
+static gboolean g_unix_signal_watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data);
+static void g_unix_signal_watch_finalize (GSource *source);
+#endif
static gboolean g_idle_prepare (GSource *source,
gint *timeout);
static gboolean g_idle_check (GSource *source);
* signal was received.
*/
#define _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR 'C'
-/* Guards unix_signal_wake_up_pipe */
+#define _UNIX_SIGNAL_PIPE_SIGHUP_CHAR 'H'
+#define _UNIX_SIGNAL_PIPE_SIGINT_CHAR 'I'
+#define _UNIX_SIGNAL_PIPE_SIGTERM_CHAR 'T'
+/* Guards all the data below */
G_LOCK_DEFINE_STATIC (unix_signal_lock);
-static gint unix_signal_wake_up_pipe[2] = {-1, -1};
-
-/* Child status monitoring code */
enum {
- CHILD_WATCH_UNINITIALIZED,
- CHILD_WATCH_INITIALIZED_SINGLE,
- CHILD_WATCH_INITIALIZED_THREADED
+ UNIX_SIGNAL_UNINITIALIZED = 0,
+ UNIX_SIGNAL_INITIALIZED_SINGLE,
+ UNIX_SIGNAL_INITIALIZED_THREADED
};
-static gint child_watch_init_state = CHILD_WATCH_UNINITIALIZED;
+static gint unix_signal_init_state = UNIX_SIGNAL_UNINITIALIZED;
+typedef struct {
+ gboolean sigchld_handler_installed : 1;
+ gboolean sighup_handler_installed : 1;
+ gboolean sigint_handler_installed : 1;
+ gboolean sigterm_handler_installed : 1;
+
+ /* These are only used in the UNIX_SIGNAL_INITIALIZED_SINGLE case */
+ gboolean sighup_delivered : 1;
+ gboolean sigint_delivered : 1;
+ gboolean sigterm_delivered : 1;
+} UnixSignalState;
+static UnixSignalState unix_signal_state;
+static gint unix_signal_wake_up_pipe[2];
+GSList *unix_signal_watches;
+
+/* Not guarded ( FIXME should it be? ) */
static gint child_watch_count = 1;
+
+static GSourceFuncs g_unix_signal_funcs =
+{
+ g_unix_signal_watch_prepare,
+ g_unix_signal_watch_check,
+ g_unix_signal_watch_dispatch,
+ g_unix_signal_watch_finalize
+};
#endif /* !G_OS_WIN32 */
G_LOCK_DEFINE_STATIC (main_context_list);
static GSList *main_context_list = NULL;
return check_for_child_exited (source);
}
-
static gboolean
g_child_watch_check (GSource *source)
{
return check_for_child_exited (source);
}
+static gboolean
+check_for_signal_delivery (GSource *source)
+{
+ GUnixSignalWatchSource *unix_signal_source = (GUnixSignalWatchSource*) source;
+ gboolean delivered;
+
+ G_LOCK (unix_signal_lock);
+ if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_SINGLE)
+ {
+ switch (unix_signal_source->signum)
+ {
+ case SIGHUP:
+ delivered = unix_signal_state.sighup_delivered;
+ break;
+ case SIGINT:
+ delivered = unix_signal_state.sigint_delivered;
+ break;
+ case SIGTERM:
+ delivered = unix_signal_state.sigterm_delivered;
+ break;
+ default:
+ g_assert_not_reached ();
+ delivered = FALSE;
+ break;
+ }
+ }
+ else
+ {
+ g_assert (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED);
+ delivered = unix_signal_source->pending;
+ }
+ G_UNLOCK (unix_signal_lock);
+
+ return delivered;
+}
+
+static gboolean
+g_unix_signal_watch_prepare (GSource *source,
+ gint *timeout)
+{
+ *timeout = -1;
+
+ return check_for_signal_delivery (source);
+}
+
+static gboolean
+g_unix_signal_watch_check (GSource *source)
+{
+ return check_for_signal_delivery (source);
+}
+
+static gboolean
+g_unix_signal_watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GUnixSignalWatchSource *unix_signal_source;
+
+ unix_signal_source = (GUnixSignalWatchSource *) source;
+
+ if (!callback)
+ {
+ g_warning ("Unix signal source dispatched without callback\n"
+ "You must call g_source_set_callback().");
+ return FALSE;
+ }
+
+ (callback) (user_data);
+
+ G_LOCK (unix_signal_lock);
+ if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_SINGLE)
+ {
+ switch (unix_signal_source->signum)
+ {
+ case SIGHUP:
+ unix_signal_state.sighup_delivered = FALSE;
+ break;
+ case SIGINT:
+ unix_signal_state.sigint_delivered = FALSE;
+ break;
+ case SIGTERM:
+ unix_signal_state.sigterm_delivered = FALSE;
+ break;
+ }
+ }
+ else
+ {
+ g_assert (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED);
+ unix_signal_source->pending = FALSE;
+ }
+ G_UNLOCK (unix_signal_lock);
+
+ return TRUE;
+}
+
+static void
+ensure_unix_signal_handler_installed_unlocked (int signum)
+{
+ struct sigaction action;
+
+ switch (signum)
+ {
+ case SIGHUP:
+ if (unix_signal_state.sighup_handler_installed)
+ return;
+ unix_signal_state.sighup_handler_installed = TRUE;
+ break;
+ case SIGINT:
+ if (unix_signal_state.sigint_handler_installed)
+ return;
+ unix_signal_state.sigint_handler_installed = TRUE;
+ break;
+ case SIGTERM:
+ if (unix_signal_state.sigterm_handler_installed)
+ return;
+ unix_signal_state.sigterm_handler_installed = TRUE;
+ break;
+ }
+
+ init_unix_signal_wakeup_state_unlocked ();
+
+ action.sa_handler = g_unix_signal_handler;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = 0;
+ sigaction (signum, &action, NULL);
+}
+
+GSource *
+_g_main_create_unix_signal_watch (int signum)
+{
+ GSource *source;
+ GUnixSignalWatchSource *unix_signal_source;
+
+ init_unix_signal_wakeup_state ();
+
+ source = g_source_new (&g_unix_signal_funcs, sizeof (GUnixSignalWatchSource));
+ unix_signal_source = (GUnixSignalWatchSource *) source;
+
+ unix_signal_source->signum = signum;
+ unix_signal_source->pending = FALSE;
+
+ G_LOCK (unix_signal_lock);
+ ensure_unix_signal_handler_installed_unlocked (signum);
+ unix_signal_watches = g_slist_prepend (unix_signal_watches, unix_signal_source);
+ G_UNLOCK (unix_signal_lock);
+
+ return source;
+}
+
+static void
+g_unix_signal_watch_finalize (GSource *source)
+{
+ G_LOCK (unix_signal_lock);
+ unix_signal_watches = g_slist_remove (unix_signal_watches, source);
+ G_UNLOCK (unix_signal_lock);
+}
+
#endif /* G_OS_WIN32 */
static gboolean
#ifndef G_OS_WIN32
static void
-g_child_watch_signal_handler (int signum)
+g_unix_signal_handler (int signum)
{
- child_watch_count ++;
+ if (signum == SIGCHLD)
+ child_watch_count ++;
- if (child_watch_init_state == CHILD_WATCH_INITIALIZED_THREADED)
+ if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED)
{
- char buf[1] = { _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR };
+ char buf[1];
+ switch (signum)
+ {
+ case SIGCHLD:
+ buf[0] = _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR;
+ break;
+ case SIGHUP:
+ buf[0] = _UNIX_SIGNAL_PIPE_SIGHUP_CHAR;
+ break;
+ case SIGINT:
+ buf[0] = _UNIX_SIGNAL_PIPE_SIGINT_CHAR;
+ break;
+ case SIGTERM:
+ buf[0] = _UNIX_SIGNAL_PIPE_SIGTERM_CHAR;
+ break;
+ default:
+ /* Shouldn't happen */
+ return;
+ }
write (unix_signal_wake_up_pipe[1], buf, 1);
}
else
{
- /* We count on the signal interrupting the poll in the same thread.
- */
+ /* We count on the signal interrupting the poll in the same thread. */
+ switch (signum)
+ {
+ case SIGCHLD:
+ /* Nothing to do - the handler will call waitpid() */
+ break;
+ case SIGHUP:
+ unix_signal_state.sighup_delivered = TRUE;
+ break;
+ case SIGINT:
+ unix_signal_state.sigint_delivered = TRUE;
+ break;
+ case SIGTERM:
+ unix_signal_state.sigterm_delivered = TRUE;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
}
}
static void
-g_child_watch_source_init_single (void)
+deliver_unix_signal (int signum)
{
- struct sigaction action;
+ GSList *iter;
+ g_assert (signum == SIGHUP || signum == SIGINT || signum == SIGTERM);
- g_assert (! g_thread_supported());
- g_assert (child_watch_init_state == CHILD_WATCH_UNINITIALIZED);
-
- child_watch_init_state = CHILD_WATCH_INITIALIZED_SINGLE;
+ G_LOCK (unix_signal_lock);
+ for (iter = unix_signal_watches; iter; iter = iter->next)
+ {
+ GUnixSignalWatchSource *source = iter->data;
- action.sa_handler = g_child_watch_signal_handler;
- sigemptyset (&action.sa_mask);
- action.sa_flags = SA_NOCLDSTOP;
- sigaction (SIGCHLD, &action, NULL);
+ if (source->signum != signum)
+ continue;
+
+ source->pending = TRUE;
+ }
+ G_UNLOCK (unix_signal_lock);
}
static gpointer unix_signal_helper_thread (gpointer data) G_GNUC_NORETURN;
{
gchar b[128];
ssize_t i, bytes_read;
+ gboolean sigterm_received = FALSE;
+ gboolean sigint_received = FALSE;
+ gboolean sighup_received = FALSE;
bytes_read = read (unix_signal_wake_up_pipe[0], b, sizeof (b));
if (bytes_read < 0)
* that info down the pipe would require a more structured
* data stream (as opposed to a single byte).
*/
- _g_main_wake_up_all_contexts ();
+ break;
+ case _UNIX_SIGNAL_PIPE_SIGTERM_CHAR:
+ sigterm_received = TRUE;
+ break;
+ case _UNIX_SIGNAL_PIPE_SIGHUP_CHAR:
+ sighup_received = TRUE;
+ break;
+ case _UNIX_SIGNAL_PIPE_SIGINT_CHAR:
+ sigint_received = TRUE;
break;
default:
g_warning ("Invalid char '%c' read from child watch pipe", b[i]);
break;
}
+ if (sigterm_received)
+ deliver_unix_signal (SIGTERM);
+ if (sigint_received)
+ deliver_unix_signal (SIGINT);
+ if (sighup_received)
+ deliver_unix_signal (SIGHUP);
+ _g_main_wake_up_all_contexts ();
}
}
}
static void
-_g_main_init_unix_signal_wakeup_pipe (void)
+init_unix_signal_wakeup_state_unlocked (void)
{
GError *error = NULL;
- G_LOCK (unix_signal_lock);
-
- if (unix_signal_wake_up_pipe[0] >= 0)
- goto out;
+ if (!g_thread_supported ())
+ {
+ /* There is nothing to do for initializing in the non-threaded
+ * case.
+ */
+ if (unix_signal_init_state == UNIX_SIGNAL_UNINITIALIZED)
+ unix_signal_init_state = UNIX_SIGNAL_INITIALIZED_SINGLE;
+ return;
+ }
- g_assert (g_thread_supported());
+ if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED)
+ return;
if (!g_unix_pipe_flags (unix_signal_wake_up_pipe, FD_CLOEXEC, &error))
g_error ("Cannot create UNIX signal wake up pipe: %s\n", error->message);
fcntl (unix_signal_wake_up_pipe[1], F_SETFL, O_NONBLOCK | fcntl (unix_signal_wake_up_pipe[1], F_GETFL));
/* We create a helper thread that polls on the wakeup pipe indefinitely */
- /* FIXME: Think this through for races */
if (g_thread_create (unix_signal_helper_thread, NULL, FALSE, &error) == NULL)
g_error ("Cannot create a thread to monitor UNIX signals: %s\n", error->message);
- out:
- G_UNLOCK (unix_signal_lock);
+ unix_signal_init_state = UNIX_SIGNAL_INITIALIZED_THREADED;
}
static void
-g_child_watch_source_init_multi_threaded (void)
+init_unix_signal_wakeup_state (void)
{
- struct sigaction action;
-
- _g_main_init_unix_signal_wakeup_pipe ();
-
- child_watch_init_state = CHILD_WATCH_INITIALIZED_THREADED;
+ G_LOCK (unix_signal_lock);
- action.sa_handler = g_child_watch_signal_handler;
- sigemptyset (&action.sa_mask);
- action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
- sigaction (SIGCHLD, &action, NULL);
-}
+ init_unix_signal_wakeup_state_unlocked ();
-static void
-g_child_watch_source_init_promote_single_to_threaded (void)
-{
- g_child_watch_source_init_multi_threaded ();
+ G_UNLOCK (unix_signal_lock);
}
static void
g_child_watch_source_init (void)
{
- if (g_thread_supported())
- {
- if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
- g_child_watch_source_init_multi_threaded ();
- else if (child_watch_init_state == CHILD_WATCH_INITIALIZED_SINGLE)
- g_child_watch_source_init_promote_single_to_threaded ();
- }
- else
+ init_unix_signal_wakeup_state ();
+
+ G_LOCK (unix_signal_lock);
+ if (!unix_signal_state.sigchld_handler_installed)
{
- if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
- g_child_watch_source_init_single ();
+ struct sigaction action;
+ action.sa_handler = g_unix_signal_handler;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+ sigaction (SIGCHLD, &action, NULL);
+ unix_signal_state.sigchld_handler_installed = TRUE;
}
+ G_UNLOCK (unix_signal_lock);
}
#endif /* !G_OS_WIN32 */