[glib/wip/child-catchall: 3/3] GChildWatchSource: Allow passing -1 for pid
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/child-catchall: 3/3] GChildWatchSource: Allow passing -1 for pid
- Date: Tue, 30 Oct 2012 22:57:23 +0000 (UTC)
commit d3300725d94b79f57c2fc8db90a09b35ade456b7
Author: Colin Walters <walters verbum org>
Date: Thu Oct 25 02:54:05 2012 -0400
GChildWatchSource: Allow passing -1 for pid
With the goal of modifying gnome-session to use the new Linux
PR_SET_CHILD_SUBREAPER, it needs to be able to reap arbitary children
that may be reparented to it.
But if gnome-session is going to continue to work with GLib and GIO,
even if we converted all child watches in gnome-session to use this
new API, we'd still have to contend with the possibility of it having
a process spawned indirectly (stuff like the GDBus dbus-launch
invocation). Thus, we don't give the -1 child watch *every* waitpid,
only the ones which don't have a specific watcher.
Also, fix up g_spawn_sync() to hold the unix signal lock when forking.
This way we ensure that GLib itself gets the waitpid() result for the
synchronously spawned child, instead of racing with the worker thread.
https://bugzilla.gnome.org/show_bug.cgi?id=687061
glib/gmain-internal.h | 6 +
glib/gmain.c | 371 +++++++++++++++++++++++++++++++++++++++++--------
glib/gspawn.c | 31 ++++-
glib/tests/unix.c | 109 +++++++++++++++
4 files changed, 457 insertions(+), 60 deletions(-)
---
diff --git a/glib/gmain-internal.h b/glib/gmain-internal.h
index 648aff3..0877718 100644
--- a/glib/gmain-internal.h
+++ b/glib/gmain-internal.h
@@ -30,6 +30,12 @@ G_BEGIN_DECLS
GSource *_g_main_create_unix_signal_watch (int signum);
+#ifdef G_OS_UNIX
+void _g_main_unix_signal_lock (void);
+void _g_main_unix_signal_unlock (void);
+GSource * _g_main_child_watch_source_new_unix_unlocked (GPid pid);
+#endif
+
G_END_DECLS
#endif /* __G_MAIN_H__ */
diff --git a/glib/gmain.c b/glib/gmain.c
index af1092d..7db6400 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -185,6 +185,7 @@
typedef struct _GTimeoutSource GTimeoutSource;
typedef struct _GChildWatchSource GChildWatchSource;
typedef struct _GUnixSignalWatchSource GUnixSignalWatchSource;
+typedef struct _GUnixCatchallChildWatchSource GUnixCatchallChildWatchSource;
typedef struct _GPollRec GPollRec;
typedef struct _GSourceCallback GSourceCallback;
@@ -303,6 +304,12 @@ struct _GUnixSignalWatchSource
gboolean pending;
};
+struct _GUnixCatchallChildWatchSource
+{
+ GSource source;
+ GSList *pending_waits;
+};
+
struct _GPollRec
{
GPollFD *fd;
@@ -395,6 +402,13 @@ static gboolean g_unix_signal_watch_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data);
static void g_unix_signal_watch_finalize (GSource *source);
+static gboolean g_unix_catchall_watch_prepare (GSource *source,
+ gint *timeout);
+static gboolean g_unix_catchall_watch_check (GSource *source);
+static gboolean g_unix_catchall_watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data);
+static void g_unix_catchall_watch_finalize (GSource *source);
#endif
static gboolean g_idle_prepare (GSource *source,
gint *timeout);
@@ -428,6 +442,12 @@ static volatile int any_unix_signal_pending;
G_LOCK_DEFINE_STATIC (unix_signal_lock);
static GSList *unix_signal_watches;
static GSList *unix_child_watches;
+static GUnixCatchallChildWatchSource *unix_catchall_child_watch;
+
+typedef struct {
+ pid_t pid;
+ gint estatus;
+} UnixWaitStatus;
static GSourceFuncs g_unix_signal_funcs =
{
@@ -436,6 +456,14 @@ static GSourceFuncs g_unix_signal_funcs =
g_unix_signal_watch_dispatch,
g_unix_signal_watch_finalize
};
+
+static GSourceFuncs g_unix_catchall_funcs =
+{
+ g_unix_catchall_watch_prepare,
+ g_unix_catchall_watch_check,
+ g_unix_catchall_watch_dispatch,
+ g_unix_catchall_watch_finalize
+};
#endif /* !G_OS_WIN32 */
G_LOCK_DEFINE_STATIC (main_context_list);
static GSList *main_context_list = NULL;
@@ -4392,6 +4420,115 @@ wake_source (GSource *source)
G_UNLOCK(main_context_list);
}
+/* Here the application has explicitly requested use of waitpid(-1,
+ * ...). The contortions below are to be sure we dispatch normal
+ * GChildWatchSource for their requested pid, only passing other pids
+ * to the catchall source.
+ */
+static void
+dispatch_sigchld_with_catchall (void)
+{
+ gboolean caught_one_catchall = FALSE;
+
+ while (TRUE)
+ {
+ gboolean give_to_catchall = TRUE;
+ GSList *node;
+ int estatus;
+ pid_t pid;
+
+ /* Normal EINTR loop */
+ do
+ pid = waitpid (-1, &estatus, WNOHANG);
+ while (pid == -1 && errno == EINTR);
+
+ /* We're done if we have no child processes, or on an error.
+ * Ideally we'd log an error, if one somehow occurred, but we
+ * can't get ECHILD, we're handling EINTR, and the only way we'd
+ * get EINVAL is if the system didn't support WNOHANG, in which
+ * case we're screwed anyways. Those are all of the errnos
+ * listed in the glibc manual.
+ */
+ if (pid <= 0)
+ break;
+
+ for (node = unix_child_watches; node; node = node->next)
+ {
+ GChildWatchSource *source = node->data;
+
+ if (source->child_exited
+ || source->pid != pid)
+ continue;
+
+ source->child_status = estatus;
+ source->child_exited = TRUE;
+ give_to_catchall = FALSE;
+ wake_source ((GSource*)source);
+ }
+
+ /* The catchall watch only gets processes that don't already
+ * have a specific child watch.
+ */
+ if (!give_to_catchall)
+ continue;
+
+ if (unix_catchall_child_watch != NULL)
+ {
+ UnixWaitStatus *waitstatus = g_slice_new (UnixWaitStatus);
+ waitstatus->pid = pid;
+ waitstatus->estatus = estatus;
+
+ unix_catchall_child_watch->pending_waits = g_slist_prepend (unix_catchall_child_watch->pending_waits, waitstatus);
+ caught_one_catchall = TRUE;
+ }
+ }
+
+ if (caught_one_catchall)
+ wake_source ((GSource *) unix_catchall_child_watch);
+}
+
+/* In this case, we have to individually scan all of the children.
+ *
+ * The docs promise that we will not reap children that we are
+ * not explicitly watching, so that ties our hands from calling
+ * waitpid(-1) - that is a separate _catchall API. We also
+ * can't use siginfo's si_pid field since if multiple SIGCHLD
+ * arrive at the same time, one of them can be dropped (since a
+ * given UNIX signal can only be pending once).
+ */
+static void
+dispatch_sigchld_individually (void)
+{
+ GSList *node;
+
+ for (node = unix_child_watches; node; node = node->next)
+ {
+ GChildWatchSource *source = node->data;
+ pid_t pid;
+
+ if (source->child_exited)
+ continue;
+
+ do
+ {
+ pid = waitpid (source->pid, &source->child_status, WNOHANG);
+ if (pid > 0)
+ {
+ source->child_exited = TRUE;
+ wake_source ((GSource *) source);
+ }
+ else if (pid == -1 && errno == ECHILD)
+ {
+ g_warning ("GChildWatchSource: Exit status of a child process was requested but ECHILD was received by waitpid(). Most likely the process is ignoring SIGCHLD, or some other thread is invoking waitpid() with a nonpositive first argument; either behavior can break applications that use g_child_watch_add()/g_spawn_sync() either directly or indirectly.");
+ source->child_exited = TRUE;
+ source->child_status = 0;
+ wake_source ((GSource *) source);
+ }
+ }
+ while (pid == -1 && errno == EINTR);
+ }
+}
+
static void
dispatch_unix_signals (void)
{
@@ -4407,39 +4544,13 @@ dispatch_unix_signals (void)
{
unix_signal_pending[SIGCHLD] = FALSE;
- /* The only way we can do this is to scan all of the children.
- *
- * The docs promise that we will not reap children that we are not
- * explicitly watching, so that ties our hands from calling
- * waitpid(-1). We also can't use siginfo's si_pid field since if
- * multiple SIGCHLD arrive at the same time, one of them can be
- * dropped (since a given UNIX signal can only be pending once).
- */
- for (node = unix_child_watches; node; node = node->next)
+ if (unix_catchall_child_watch != NULL)
{
- GChildWatchSource *source = node->data;
-
- if (!source->child_exited)
- {
- pid_t pid;
- do
- {
- pid = waitpid (source->pid, &source->child_status, WNOHANG);
- if (pid > 0)
- {
- source->child_exited = TRUE;
- wake_source ((GSource *) source);
- }
- else if (pid == -1 && errno == ECHILD)
- {
- g_warning ("GChildWatchSource: Exit status of a child process was requested but ECHILD was received by waitpid(). Most likely the process is ignoring SIGCHLD, or some other thread is invoking waitpid() with a nonpositive first argument; either behavior can break applications that use g_child_watch_add()/g_spawn_sync() either directly or indirectly.");
- source->child_exited = TRUE;
- source->child_status = 0;
- wake_source ((GSource *) source);
- }
- }
- while (pid == -1 && errno == EINTR);
- }
+ dispatch_sigchld_with_catchall ();
+ }
+ else
+ {
+ dispatch_sigchld_individually ();
}
}
@@ -4484,6 +4595,14 @@ g_child_watch_check (GSource *source)
return child_watch_source->child_exited;
}
+static void
+g_child_watch_finalize (GSource *source)
+{
+ G_LOCK (unix_signal_lock);
+ unix_child_watches = g_slist_remove (unix_child_watches, source);
+ G_UNLOCK (unix_signal_lock);
+}
+
static gboolean
g_unix_signal_watch_prepare (GSource *source,
gint *timeout)
@@ -4530,6 +4649,14 @@ g_unix_signal_watch_dispatch (GSource *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);
+}
+
+static void
ensure_unix_signal_handler_installed_unlocked (int signum)
{
static sigset_t installed_signal_mask;
@@ -4577,23 +4704,114 @@ _g_main_create_unix_signal_watch (int signum)
return source;
}
-static void
-g_unix_signal_watch_finalize (GSource *source)
+static gboolean
+g_unix_catchall_watch_prepare (GSource *source,
+ gint *timeout)
{
+ gboolean res;
+ GUnixCatchallChildWatchSource *catchall_source;
+
+ catchall_source = (GUnixCatchallChildWatchSource *) source;
+
G_LOCK (unix_signal_lock);
- unix_signal_watches = g_slist_remove (unix_signal_watches, source);
+ res = catchall_source->pending_waits != NULL;
G_UNLOCK (unix_signal_lock);
+ return res;
+}
+
+static gboolean
+g_unix_catchall_watch_check (GSource *source)
+{
+ return g_unix_catchall_watch_prepare (source, NULL);
+}
+
+static gboolean
+g_unix_catchall_watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GUnixCatchallChildWatchSource *catchall_source;
+ GChildWatchFunc child_callback = (GChildWatchFunc) callback;
+ GSList *iter;
+ GSList *pending;
+
+ catchall_source = (GUnixCatchallChildWatchSource *) source;
+
+ if (!callback)
+ {
+ g_warning ("Unix catchall source dispatched without callback\n"
+ "You must call g_source_set_callback().");
+ return FALSE;
+ }
+
+ G_LOCK (unix_signal_lock);
+ pending = catchall_source->pending_waits;
+ catchall_source->pending_waits = NULL;
+ G_UNLOCK (unix_signal_lock);
+
+ for (iter = pending; iter; iter = iter->next)
+ {
+ UnixWaitStatus *status = iter->data;
+
+ (child_callback) (status->pid, status->estatus, user_data);
+
+ g_slice_free (UnixWaitStatus, status);
+ }
+
+ g_slist_free (pending);
+
+ return TRUE;
}
static void
-g_child_watch_finalize (GSource *source)
+g_unix_catchall_watch_finalize (GSource *source)
{
G_LOCK (unix_signal_lock);
- unix_child_watches = g_slist_remove (unix_child_watches, source);
+ g_assert (source == (GSource*)unix_catchall_child_watch);
+ unix_catchall_child_watch = NULL;
G_UNLOCK (unix_signal_lock);
}
-#endif /* G_OS_WIN32 */
+/**
+ * g_unix_catchall_child_source_new:
+ *
+ *
+ * Returns: A new #GSource
+ *
+ * Since: 2.36
+ */
+GSource *
+g_unix_catchall_child_source_new (void)
+{
+ GSource *source;
+
+ g_return_val_if_fail (unix_catchall_child_watch == NULL, NULL);
+
+ source = g_source_new (&g_unix_catchall_funcs, sizeof (GUnixCatchallChildWatchSource));
+
+ G_LOCK (unix_signal_lock);
+ ensure_unix_signal_handler_installed_unlocked (SIGCHLD);
+ unix_catchall_child_watch = (GUnixCatchallChildWatchSource*)source;
+ G_UNLOCK (unix_signal_lock);
+
+ return source;
+}
+
+/* Used by gspawn.c */
+void
+_g_main_unix_signal_lock (void)
+{
+ G_LOCK (unix_signal_lock);
+}
+
+/* Used by gspawn.c */
+void
+_g_main_unix_signal_unlock (void)
+{
+ G_UNLOCK (unix_signal_lock);
+}
+
+#endif /* !G_OS_WIN32 */
static gboolean
g_child_watch_dispatch (GSource *source,
@@ -4629,7 +4847,52 @@ g_unix_signal_handler (int signum)
g_wakeup_signal (glib_worker_context->wakeup);
}
-#endif /* !G_OS_WIN32 */
+/* must be holding unix_signal_lock */
+GSource *
+_g_main_child_watch_source_new_unix_unlocked (GPid pid)
+{
+ GSource *source;
+
+ ensure_unix_signal_handler_installed_unlocked (SIGCHLD);
+
+ if (pid == -1)
+ {
+ g_assert (unix_catchall_child_watch == NULL);
+ source = g_source_new (&g_unix_catchall_funcs, sizeof (GUnixCatchallChildWatchSource));
+ unix_catchall_child_watch = (GUnixCatchallChildWatchSource*)source;
+ }
+ else
+ {
+ GChildWatchSource *child_watch_source;
+
+ source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+ child_watch_source = (GChildWatchSource *)source;
+ child_watch_source->pid = pid;
+ unix_child_watches = g_slist_prepend (unix_child_watches, child_watch_source);
+ if (waitpid (pid, &child_watch_source->child_status, WNOHANG) > 0)
+ child_watch_source->child_exited = TRUE;
+ }
+
+ return source;
+}
+
+#else
+
+static GSource *
+child_watch_source_new_win32 (GPid pid)
+{
+ GSource *source;
+ GChildWatchSource *child_watch_source;
+
+ source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+ child_watch_source = (GChildWatchSource*) source;
+ child_watch_source->poll.fd = (gintptr) pid;
+ child_watch_source->poll.events = G_IO_IN;
+
+ g_source_add_poll (source, &child_watch_source->poll);
+}
+
+#endif
/**
* g_child_watch_source_new:
@@ -4655,6 +4918,15 @@ g_unix_signal_handler (int signum)
* compatible with calling <literal>waitpid</literal> with a
* nonpositive first argument in the application. Calling waitpid()
* for individual pids will still work fine.
+ *
+ * Since GLib 2.36, -1 may be given as @pid. In this mode, the source
+ * will watch child processes of the current process which don't
+ * already have another dedicated child watch. This allows a
+ * GLib-using process to perform a function equivalent to calling
+ * <literal>waitpid(-1, ...)</literal>. There can at present be at
+ * most one instance of a source in this mode. On Linux, it makes
+ * sense to use this together with
+ * <literal>prctl(PR_SET_CHILD_SUBREAPER)</literal>.
*
* Return value: the newly-created child watch source
*
@@ -4663,26 +4935,15 @@ g_unix_signal_handler (int signum)
GSource *
g_child_watch_source_new (GPid pid)
{
- GSource *source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
- GChildWatchSource *child_watch_source = (GChildWatchSource *)source;
-
- child_watch_source->pid = pid;
-
#ifdef G_OS_WIN32
- child_watch_source->poll.fd = (gintptr) pid;
- child_watch_source->poll.events = G_IO_IN;
-
- g_source_add_poll (source, &child_watch_source->poll);
-#else /* G_OS_WIN32 */
+ return child_watch_source_new_win32 (pid);
+#else
+ GSource *source;
G_LOCK (unix_signal_lock);
- ensure_unix_signal_handler_installed_unlocked (SIGCHLD);
- unix_child_watches = g_slist_prepend (unix_child_watches, child_watch_source);
- if (waitpid (pid, &child_watch_source->child_status, WNOHANG) > 0)
- child_watch_source->child_exited = TRUE;
+ source = _g_main_child_watch_source_new_unix_unlocked (pid);
G_UNLOCK (unix_signal_lock);
-#endif /* G_OS_WIN32 */
-
return source;
+#endif
}
/**
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 3545a78..4317e6a 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -47,6 +47,7 @@
#include "genviron.h"
#include "gmem.h"
#include "gmain.h"
+#include "gmain-internal.h"
#include "gshell.h"
#include "gstring.h"
#include "gstrfuncs.h"
@@ -281,9 +282,10 @@ g_spawn_sync (const gchar *working_directory,
gint ret;
GString *outstr = NULL;
GString *errstr = NULL;
- gboolean failed;
+ gboolean failed = FALSE;
gint status;
SyncWaitpidData waitpid_data;
+ gboolean have_unix_signal_lock = FALSE;
g_return_val_if_fail (argv != NULL, FALSE);
g_return_val_if_fail (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), FALSE);
@@ -300,6 +302,12 @@ g_spawn_sync (const gchar *working_directory,
if (standard_error)
*standard_error = NULL;
+
+ /* Hold a lock on any potential waitpid(-1, ...) calls until we have
+ * a child watch source set up.
+ */
+ _g_main_unix_signal_lock ();
+ have_unix_signal_lock = TRUE;
if (!fork_exec_with_pipes (FALSE,
working_directory,
@@ -319,11 +327,12 @@ g_spawn_sync (const gchar *working_directory,
standard_output ? &outpipe : NULL,
standard_error ? &errpipe : NULL,
error))
- return FALSE;
+ {
+ failed = TRUE;
+ goto out;
+ }
/* Read data from child. */
-
- failed = FALSE;
if (outpipe >= 0)
{
@@ -432,10 +441,18 @@ g_spawn_sync (const gchar *working_directory,
waitpid_data.loop = loop;
waitpid_data.status_p = &status;
- source = g_child_watch_source_new (pid);
+ /* Assumes we have the unix signal lock */
+ source = _g_main_child_watch_source_new_unix_unlocked (pid);
g_source_set_callback (source, (GSourceFunc)on_sync_waitpid, &waitpid_data, NULL);
g_source_attach (source, context);
g_source_unref (source);
+
+ /* Now we safely have our waitpid() queued up for the GLib worker
+ * thread to process. Unlock the mutex so it can continue.
+ */
+ g_assert (have_unix_signal_lock);
+ _g_main_unix_signal_unlock ();
+ have_unix_signal_lock = FALSE;
g_main_loop_run (loop);
@@ -443,6 +460,10 @@ g_spawn_sync (const gchar *working_directory,
g_main_loop_unref (loop);
}
+ out:
+ if (have_unix_signal_lock)
+ _g_main_unix_signal_unlock ();
+
if (failed)
{
if (outstr)
diff --git a/glib/tests/unix.c b/glib/tests/unix.c
index 329e19a..f649eec 100644
--- a/glib/tests/unix.c
+++ b/glib/tests/unix.c
@@ -147,6 +147,113 @@ test_sighup_add_remove (void)
}
+typedef struct {
+ GPid pid;
+ GMainLoop *loop;
+ gboolean regular_exited;
+ gboolean catchall_exited;
+} CatchAllData;
+
+static void
+on_catchall_child (GPid pid,
+ gint estatus,
+ gpointer user_data)
+{
+ CatchAllData *data = user_data;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+
+ g_spawn_check_exit_status (estatus, error);
+ g_assert_no_error (local_error);
+
+ if (pid == data->pid)
+ {
+ data->catchall_exited = TRUE;
+ if (data->regular_exited)
+ g_main_loop_quit (data->loop);
+ }
+}
+
+static void
+test_catchall_source (void)
+{
+ GMainLoop *mainloop;
+ CatchAllData data;
+ GSource *source;
+ GPid pid;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ char *child_args[] = { "/bin/true", "/bin/true", NULL };
+
+ memset (&data, 0, sizeof (data));
+ data.regular_exited = TRUE; /* For the next test */
+ mainloop = g_main_loop_new (NULL, FALSE);
+ data.loop = mainloop;
+
+ source = g_unix_catchall_child_source_new ();
+ g_source_set_callback (source, (GSourceFunc)on_catchall_child, &data, NULL);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ g_spawn_async (NULL, (char**)child_args, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid, error);
+ g_assert_no_error (local_error);
+ data.pid = pid;
+
+ g_main_loop_run (mainloop);
+ g_source_destroy (source);
+ g_main_loop_unref (mainloop);
+}
+
+static void
+on_regular_child (GPid pid,
+ gint estatus,
+ gpointer user_data)
+{
+ CatchAllData *data = user_data;
+ data->regular_exited = TRUE;
+
+ if (data->catchall_exited)
+ g_main_loop_quit (data->loop);
+}
+
+static void
+test_catchall_and_regular_child (void)
+{
+ GMainLoop *mainloop;
+ CatchAllData data;
+ GSource *source;
+ GPid pid;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ char *child_args[] = { "/bin/true", "/bin/true", NULL };
+
+ memset (&data, 0, sizeof (data));
+ mainloop = g_main_loop_new (NULL, FALSE);
+ data.loop = mainloop;
+
+ g_spawn_async (NULL, (char**)child_args, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid, error);
+ g_child_watch_add (pid, on_regular_child, &data);
+
+ source = g_child_watch_source_new (-1);
+ g_source_set_callback (source, (GSourceFunc)on_catchall_child, &data, NULL);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ g_spawn_async (NULL, (char**)child_args, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid, error);
+ g_assert_no_error (local_error);
+ data.pid = pid;
+
+ g_main_loop_run (mainloop);
+
+ g_source_destroy (source);
+ g_main_loop_unref (mainloop);
+
+ g_assert (data.regular_exited && data.catchall_exited);
+}
+
int
main (int argc,
char *argv[])
@@ -159,6 +266,8 @@ main (int argc,
g_test_add_func ("/glib-unix/sigterm", test_sigterm);
g_test_add_func ("/glib-unix/sighup_again", test_sighup);
g_test_add_func ("/glib-unix/sighup_add_remove", test_sighup_add_remove);
+ g_test_add_func ("/glib-unix/catchall-source", test_catchall_source);
+ g_test_add_func ("/glib-unix/catchall-and-regular-child", test_catchall_and_regular_child);
return g_test_run();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]