[glib/wip/gsubprocess-ostreams: 5/9] gmain: Add private API to create Unix child watch that uses waitid()
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/gsubprocess-ostreams: 5/9] gmain: Add private API to create Unix child watch that uses waitid()
- Date: Wed, 25 Jul 2012 23:24:08 +0000 (UTC)
commit 7def8b8fdf2bf988c67e84f5872babdbc42b9d5d
Author: Colin Walters <walters verbum org>
Date: Mon May 21 17:09:06 2012 -0400
gmain: Add private API to create Unix child watch that uses waitid()
This avoids collecting the zombie child, which means that the PID
can't be reused. This prevents possible race conditions that might
occur were one to send e.g. SIGTERM to a child.
This race condition has always existed due to the way we called
waitpid() for the app, but the window was widened when we moved the
waitpid() calls into a separate thread.
If waitid() isn't available, we return NULL, and consumers of this
private API (namely, GSubprocess) will need to handle that.
https://bugzilla.gnome.org/show_bug.cgi?id=672102
configure.ac | 2 +-
glib/glib-private.c | 3 +-
glib/glib-private.h | 10 ++++
glib/gmain.c | 131 +++++++++++++++++++++++++++++++++++++++------------
4 files changed, 113 insertions(+), 33 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 04d7c05..bcba9d2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -985,7 +985,7 @@ AC_MSG_RESULT(unsigned $glib_size_type)
# Check for some functions
AC_CHECK_FUNCS(lstat strerror strsignal memmove vsnprintf stpcpy strcasecmp strncasecmp poll getcwd vasprintf setenv unsetenv getc_unlocked readlink symlink fdwalk memmem)
-AC_CHECK_FUNCS(chown lchmod lchown fchmod fchown link utimes getgrgid getpwuid)
+AC_CHECK_FUNCS(chown lchmod lchown fchmod fchown link utimes getgrgid getpwuid waitid)
AC_CHECK_FUNCS(getmntent_r setmntent endmntent hasmntopt getfsstat getvfsstat)
# Check for high-resolution sleep functions
AC_CHECK_FUNCS(splice)
diff --git a/glib/glib-private.c b/glib/glib-private.c
index 3946e77..f6ba3a4 100644
--- a/glib/glib-private.c
+++ b/glib/glib-private.c
@@ -38,7 +38,8 @@ glib__private__ (void)
g_wakeup_signal,
g_wakeup_acknowledge,
- g_get_worker_context
+ g_get_worker_context,
+ g_child_watch_source_new_with_flags
};
return &table;
diff --git a/glib/glib-private.h b/glib/glib-private.h
index fde0be8..ca43dd3 100644
--- a/glib/glib-private.h
+++ b/glib/glib-private.h
@@ -23,8 +23,16 @@
#include <glib.h>
#include "gwakeup.h"
+typedef enum {
+ _G_CHILD_WATCH_FLAGS_NONE = 0,
+ _G_CHILD_WATCH_FLAGS_WNOWAIT = (1 << 0)
+} _GChildWatchFlags;
+
G_GNUC_INTERNAL
GMainContext * g_get_worker_context (void);
+G_GNUC_INTERNAL
+GSource * g_child_watch_source_new_with_flags (GPid pid,
+ gint flags);
#define GLIB_PRIVATE_CALL(symbol) (glib__private__()->symbol)
@@ -39,6 +47,8 @@ typedef struct {
/* See gmain.c */
GMainContext * (* g_get_worker_context) (void);
+ GSource * (* g_child_watch_source_new_with_flags) (GPid pid,
+ gint flags);
/* Add other private functions here, initialize them in glib-private.c */
} GLibPrivateVTable;
diff --git a/glib/gmain.c b/glib/gmain.c
index d2f42b0..2c9989d 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -292,7 +292,9 @@ struct _GChildWatchSource
#ifdef G_OS_WIN32
GPollFD poll;
#else /* G_OS_WIN32 */
- gboolean child_exited;
+ guint child_exited : 1;
+ guint wnowait : 1;
+ guint reserved : 30;
#endif /* G_OS_WIN32 */
};
@@ -4375,6 +4377,62 @@ wake_source (GSource *source)
G_UNLOCK(main_context_list);
}
+/* This is a hack; we want to use the newer waitid() call, but the use
+ * of the waitpid() status code API is baked into the GChildWatchSource
+ * callback signature, so we need to synthesize it from the siginfo_t
+ * we get from waitid().
+ */
+#ifdef HAVE_WAITID
+#define __G_MAKE_EXITSTATUS(ecode, signum) ((ecode) << 8 | (signum))
+static gint
+waitpid_status_from_siginfo (siginfo_t *siginfo)
+{
+ G_STATIC_ASSERT(((WIFEXITED (__G_MAKE_EXITSTATUS (1, 0))) && \
+ !(WIFSIGNALED (__G_MAKE_EXITSTATUS (1, 0))) && \
+ (WEXITSTATUS (__G_MAKE_EXITSTATUS (1, 0)) == 1)));
+ G_STATIC_ASSERT((!WIFEXITED (__G_MAKE_EXITSTATUS (0, 1))) && \
+ (WIFSIGNALED (__G_MAKE_EXITSTATUS (0, 1))) && \
+ (WTERMSIG (__G_MAKE_EXITSTATUS (0, 1)) == 1));
+ if (siginfo->si_code == CLD_EXITED)
+ return __G_MAKE_EXITSTATUS(siginfo->si_status, 0);
+ else if (siginfo->si_code == CLD_KILLED || siginfo->si_code == CLD_DUMPED)
+ return __G_MAKE_EXITSTATUS(0, siginfo->si_status);
+ else
+ return __G_MAKE_EXITSTATUS(0xFF, 0);
+}
+#undef __G_MAKE_EXITSTATUS
+#endif /* HAVE_WAITID */
+
+/* Returns TRUE if we need to wake the source */
+static gboolean
+unix_child_watch_source_waitpid (GChildWatchSource *source)
+{
+ if (source->child_exited)
+ return FALSE;
+
+#ifdef HAVE_WAITID
+ if (source->wnowait)
+ {
+ siginfo_t siginfo;
+ siginfo.si_pid = 0;
+ if (waitid (P_PID, (id_t)source->pid, &siginfo, WEXITED | WNOHANG | WNOWAIT) == 0
+ && siginfo.si_pid == source->pid)
+ {
+ source->child_exited = TRUE;
+ source->child_status = waitpid_status_from_siginfo (&siginfo);
+ return TRUE;
+ }
+ } else
+#endif
+ if (waitpid (source->pid, &source->child_status, WNOHANG) > 0)
+ {
+ source->child_exited = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static void
dispatch_unix_signals (void)
{
@@ -4401,16 +4459,9 @@ dispatch_unix_signals (void)
for (node = unix_child_watches; node; node = node->next)
{
GChildWatchSource *source = node->data;
-
- if (!source->child_exited)
- {
- if (waitpid (source->pid, &source->child_status, WNOHANG) > 0)
- {
- source->child_exited = TRUE;
-
- wake_source ((GSource *) source);
- }
- }
+
+ if (unix_child_watch_source_waitpid (source))
+ wake_source ((GSource *) source);
}
}
@@ -4601,6 +4652,43 @@ g_unix_signal_handler (int signum)
#endif /* !G_OS_WIN32 */
+GSource *
+g_child_watch_source_new_with_flags (GPid pid,
+ gint flags)
+{
+ GSource *source;
+ GChildWatchSource *child_watch_source;
+
+#if defined(G_OS_UNIX) && !defined(HAVE_WAITID)
+ if (flags & _G_CHILD_WATCH_FLAGS_WNOWAIT)
+ return NULL;
+#else
+ source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+ child_watch_source = (GChildWatchSource *)source;
+
+ child_watch_source->pid = pid;
+#if defined(G_OS_UNIX) && defined(HAVE_WAITID)
+ if (flags & _G_CHILD_WATCH_FLAGS_WNOWAIT)
+ child_watch_source->wnowait = TRUE;
+#endif
+
+#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 */
+ G_LOCK (unix_signal_lock);
+ ensure_unix_signal_handler_installed_unlocked (SIGCHLD);
+ unix_child_watches = g_slist_prepend (unix_child_watches, child_watch_source);
+ unix_child_watch_source_waitpid (child_watch_source);
+ G_UNLOCK (unix_signal_lock);
+#endif /* G_OS_WIN32 */
+
+ return source;
+#endif
+}
+
/**
* g_child_watch_source_new:
* @pid: process to watch. On POSIX the pid of a child process. On
@@ -4633,26 +4721,7 @@ 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 */
- 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;
- G_UNLOCK (unix_signal_lock);
-#endif /* G_OS_WIN32 */
-
- return source;
+ return g_child_watch_source_new_with_flags (pid, 0);
}
/**
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]