[glib/wip/gsubprocess] gmain: Add private API to create Unix child watch that uses waitid()



commit 0f344628e08794f16c0be2d5ffbb4fe15a82f9b2
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.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=672102

 glib/glib-private.c |    3 +-
 glib/glib-private.h |   10 +++++
 glib/gmain.c        |  113 +++++++++++++++++++++++++++++++++++++--------------
 3 files changed, 94 insertions(+), 32 deletions(-)
---
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 760f179..255128c 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -284,7 +284,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 */
 };
 
@@ -4248,6 +4250,54 @@ 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().
+ *
+ * The glibc bits/waitstatus.h implies that this is portable enough.
+ */ 
+static gint
+waitpid_status_from_siginfo (siginfo_t *siginfo)
+{
+#define __G_MAKE_EXITSTATUS(ecode, signum) ((ecode) << 8 | (signum))
+  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_STOPPED)
+   return __G_MAKE_EXITSTATUS(0, siginfo->si_status);
+ else
+   return __G_MAKE_EXITSTATUS(0xFF, 0);
+#undef __G_MAKE_EXITSTATUS
+}
+
+/* Returns TRUE if we need to wake the source */
+static gboolean
+unix_child_watch_source_waitpid (GChildWatchSource    *source)
+{
+  if (source->child_exited)
+    return FALSE;
+
+  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 if (waitpid (source->pid, &source->child_status, WNOHANG) > 0)
+    {
+      source->child_exited = TRUE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 static void
 dispatch_unix_signals (void)
 {
@@ -4274,16 +4324,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);
         }
     }
 
@@ -4474,6 +4517,33 @@ g_unix_signal_handler (int signum)
 
 #endif /* !G_OS_WIN32 */
 
+GSource *
+g_child_watch_source_new_with_flags (GPid pid,
+				     gint flags)
+{
+  GSource *source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
+  GChildWatchSource *child_watch_source = (GChildWatchSource *)source;
+
+  child_watch_source->pid = pid;
+  if (flags & _G_CHILD_WATCH_FLAGS_WNOWAIT)
+    child_watch_source->wnowait = TRUE;
+
+#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;
+}
+
 /**
  * g_child_watch_source_new:
  * @pid: process to watch. On POSIX the pid of a child process. On
@@ -4506,26 +4576,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]