[glib/wip/gsubprocess] wip



commit f0786f961b45289062822e6e6ec669f3e1f9d1b5
Author: Colin Walters <walters verbum org>
Date:   Thu May 17 13:38:32 2012 -0400

    wip

 gio/Makefile.am                  |    2 +
 gio/gio.h                        |    1 +
 gio/gioenums.h                   |    4 +-
 gio/gmemoryinputstream.c         |   16 +-
 gio/gmemoryinputstream.h         |    5 +-
 gio/gsubprocess.c                |  448 +++++++++++++++++++++++++++++++-------
 gio/gsubprocess.h                |   29 ++-
 gio/tests/Makefile.am            |    8 +
 gio/tests/gsubprocess-testprog.c |   57 +++++
 gio/tests/gsubprocess.c          |   68 ++++++
 10 files changed, 539 insertions(+), 99 deletions(-)
---
diff --git a/gio/Makefile.am b/gio/Makefile.am
index dae04b4..9fffebd 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -424,6 +424,7 @@ libgio_2_0_la_SOURCES =		\
 	gsocketlistener.c	\
 	gsocketoutputstream.c	\
 	gsocketoutputstream.h	\
+	gsubprocess.c		\
 	gproxy.c		\
 	gproxyaddress.c         \
 	gproxyaddressenumerator.c \
@@ -598,6 +599,7 @@ gio_headers =			\
 	gsocketlistener.h	\
 	gsocketservice.h	\
 	gsrvtarget.h		\
+	gsubprocess.h		\
 	gtcpconnection.h	\
 	gtcpwrapperconnection.h \
 	gthreadedsocketservice.h\
diff --git a/gio/gio.h b/gio/gio.h
index 3fb914d..93ed5e4 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -122,6 +122,7 @@
 #include <gio/gsocketlistener.h>
 #include <gio/gsocketservice.h>
 #include <gio/gsrvtarget.h>
+#include <gio/gsubprocess.h>
 #include <gio/gtcpconnection.h>
 #include <gio/gtcpwrapperconnection.h>
 #include <gio/gtestdbus.h>
diff --git a/gio/gioenums.h b/gio/gioenums.h
index 4ae0609..9f79b45 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -444,6 +444,7 @@ typedef enum {
  * @G_IO_ERROR_PROXY_NEED_AUTH: Proxy server needs authentication. Since 2.26
  * @G_IO_ERROR_PROXY_NOT_ALLOWED: Proxy connection is not allowed by ruleset.
  *     Since 2.26
+ * @G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL: The subprocess terminated abnormally.  Since: 2.34
  *
  * Error codes returned by GIO functions.
  *
@@ -492,7 +493,8 @@ typedef enum {
   G_IO_ERROR_PROXY_FAILED,
   G_IO_ERROR_PROXY_AUTH_FAILED,
   G_IO_ERROR_PROXY_NEED_AUTH,
-  G_IO_ERROR_PROXY_NOT_ALLOWED
+  G_IO_ERROR_PROXY_NOT_ALLOWED,
+  G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL
 } GIOErrorEnum;
 
 
diff --git a/gio/gmemoryinputstream.c b/gio/gmemoryinputstream.c
index 7523044..ff39037 100644
--- a/gio/gmemoryinputstream.c
+++ b/gio/gmemoryinputstream.c
@@ -221,9 +221,17 @@ g_memory_input_stream_new_from_data (const void     *data,
  * Returns: new #GInputStream read from @bytes
  **/
 GInputStream *
-g_memory_input_stream_new_from_bytes (GBytes  bytes)
+g_memory_input_stream_new_from_bytes (GBytes  *bytes)
 {
   
+  GInputStream *stream;
+
+  stream = g_memory_input_stream_new ();
+
+  g_memory_input_stream_add_bytes (G_MEMORY_INPUT_STREAM (stream),
+				   bytes);
+
+  return stream;
 }
 
 /**
@@ -259,12 +267,12 @@ g_memory_input_stream_add_data (GMemoryInputStream *stream,
  */
 void
 g_memory_input_stream_add_bytes (GMemoryInputStream *stream,
-				 GBytes              bytes)
+				 GBytes             *bytes)
 {
   GMemoryInputStreamPrivate *priv;
  
   g_return_if_fail (G_IS_MEMORY_INPUT_STREAM (stream));
-  g_return_if_fail (data != NULL);
+  g_return_if_fail (bytes != NULL);
 
   priv = stream->priv;
 
@@ -308,7 +316,7 @@ g_memory_input_stream_read (GInputStream  *stream,
 
   for (; l && rest > 0; l = l->next)
     {
-      guint8*chunk_data;
+      const guint8*chunk_data;
       chunk = (GBytes *)l->data;
 
       chunk_data = g_bytes_get_data (chunk, &len);
diff --git a/gio/gmemoryinputstream.h b/gio/gmemoryinputstream.h
index de881f6..3ec8e36 100644
--- a/gio/gmemoryinputstream.h
+++ b/gio/gmemoryinputstream.h
@@ -72,13 +72,14 @@ GInputStream * g_memory_input_stream_new           (void);
 GInputStream * g_memory_input_stream_new_from_data (const void         *data,
                                                     gssize              len,
                                                     GDestroyNotify      destroy);
-GInputStream * g_memory_input_stream_new_from_bytes (GBytes             bytes);
+GInputStream * g_memory_input_stream_new_from_bytes (GBytes            *bytes);
+
 void           g_memory_input_stream_add_data      (GMemoryInputStream *stream,
                                                     const void         *data,
                                                     gssize              len,
                                                     GDestroyNotify      destroy);
 void           g_memory_input_stream_add_bytes     (GMemoryInputStream     *stream,
-						    GBytes                  bytes);
+						    GBytes                 *bytes);
 
 G_END_DECLS
 
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
index 42cf800..0c1982c 100644
--- a/gio/gsubprocess.c
+++ b/gio/gsubprocess.c
@@ -25,10 +25,24 @@
 #include "gsubprocess.h"
 #include "gasyncresult.h"
 #include "giostream.h"
+#include "gmemoryinputstream.h"
 #include "glibintl.h"
 
+#include <string.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
+#include <gstdio.h>
+#include <glib-unix.h>
+#include <fcntl.h>
+#endif
+
 typedef struct _GSubprocessClass GSubprocessClass;
 
+struct _GSubprocessClass {
+  GObjectClass parent_class;
+};
+
 typedef enum {
   G_SUBPROCESS_STATE_BUILDING,
   G_SUBPROCESS_STATE_RUNNING,
@@ -72,16 +86,10 @@ struct _GSubprocess
   /* Used when we're writing input to the child via a pipe. */
   GOutputStream *child_input_pipe_stream;
 
-  /* Used on non-Unix when we're configured to have the child read
-   * input from a file.
-   */
-  GInputStream *stdin_from_file_stream;
-
   GPid pid;
-  GSource *child_watch;
 
   int exit_status;
-}
+};
 
 G_DEFINE_TYPE (GSubprocess, g_subprocess, G_TYPE_OBJECT);
 
@@ -110,8 +118,6 @@ g_subprocess_dispose (GObject *object)
   g_clear_object (&self->stdin_stream);
 
   g_clear_object (&self->child_input_pipe_stream);
-  g_clear_object (&self->stdin_from_file_stream);
-  g_clear_pointer (&self->child_watch, g_source_unref);
 
   if (G_OBJECT_CLASS (g_subprocess_parent_class)->dispose != NULL)
     G_OBJECT_CLASS (g_subprocess_parent_class)->dispose (object);
@@ -131,7 +137,7 @@ g_subprocess_finalize (GObject *object)
 }
 
 static void
-g_subprocess_class_init (GTcpConnectionClass *class)
+g_subprocess_class_init (GSubprocessClass *class)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
 
@@ -328,22 +334,15 @@ void
 g_subprocess_append_args_va (GSubprocess       *self,
 			     va_list            args)
 {
-  GPtrArray *child_argv = NULL;
-  GSubprocess *ret;
   const char *arg;
 
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
 
-  va_start (args, first);
-  
-  arg = first;
-  do
+  while ((arg = va_arg (args, const char *)) != NULL)
     {
       g_ptr_array_add (self->child_argv, g_strdup (arg));
-    } while ((arg = va_arg (args, const char *)) != NULL);
-
-  va_end (args);
+    }
 }
 
 /**** GSpawnFlags wrappers ****/
@@ -394,8 +393,8 @@ g_subprocess_set_search_path (GSubprocess     *self,
  * been called.
  */
 void
-g_subprocess_set_search_path (GSubprocess     *self,
-			      gboolean         do_leave_descriptors_open)
+g_subprocess_set_leave_descriptors_open (GSubprocess     *self,
+					 gboolean         do_leave_descriptors_open)
 {
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
@@ -488,8 +487,6 @@ void
 g_subprocess_override_environ (GSubprocess       *self,
 			       gchar            **envp)
 {
-  gchar **new_envp;
-
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
   g_return_if_fail (envp != NULL);
@@ -714,7 +711,7 @@ void
 g_subprocess_set_standard_input_bytes (GSubprocess       *self,
 				       GBytes            *buf)
 {
-  GMemoryInputStream *stream;
+  GInputStream *stream;
 
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
@@ -778,7 +775,6 @@ g_subprocess_set_standard_output_to_devnull (GSubprocess       *self,
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
 
-  g_clear_object (&self->stdout_stream);
   g_free (self->stdout_path);
   self->stdout_path = NULL;
   self->stdout_to_devnull = FALSE;
@@ -815,7 +811,6 @@ g_subprocess_set_standard_output_file_path (GSubprocess       *self,
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
 
-  g_clear_object (&self->stdout_stream);
   g_free (self->stdout_path);
   self->stdout_path = NULL;
   self->stdout_to_devnull = FALSE;
@@ -849,7 +844,8 @@ g_subprocess_set_standard_output_unix_fd (GSubprocess       *self,
   g_free (self->stdout_path);
   self->stdout_path = NULL;
   self->stdout_to_devnull = FALSE;
-  g_clear_object (&self->stdout_stream);
+  self->stderr_to_stdout = FALSE;
+  self->stdout_fd = -1;
 
   self->stdout_fd = fd;
 }
@@ -879,8 +875,9 @@ g_subprocess_set_standard_error_to_devnull (GSubprocess       *self,
 
   g_free (self->stderr_path);
   self->stderr_path = NULL;
+  self->stderr_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
   self->stderr_fd = -1;
-  g_clear_object (&self->stderr_stream);
 
   self->stderr_to_devnull = to_devnull;
 }
@@ -910,8 +907,9 @@ g_subprocess_set_standard_error_to_stdout (GSubprocess       *self,
 
   g_free (self->stderr_path);
   self->stderr_path = NULL;
+  self->stderr_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
   self->stderr_fd = -1;
-  g_clear_object (&self->stderr_stream);
 
   self->stderr_to_stdout = to_stdout;
 
@@ -942,10 +940,11 @@ g_subprocess_set_standard_error_file_path (GSubprocess       *self,
 
   g_free (self->stderr_path);
   self->stderr_path = NULL;
+  self->stderr_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
   self->stderr_fd = -1;
-  g_clear_object (&self->stderr_stream);
 
-  self->stderr_to_devnull = to_devnull;
+  self->stderr_path = g_strdup (file_path);
 }
 
 /**
@@ -973,7 +972,8 @@ g_subprocess_set_standard_error_unix_fd (GSubprocess       *self,
   g_free (self->stderr_path);
   self->stderr_path = NULL;
   self->stderr_to_devnull = FALSE;
-  g_clear_object (&self->stderr_stream);
+  self->stderr_to_stdout = FALSE;
+  self->stderr_fd = -1;
 
   self->stderr_fd = fd;
 }
@@ -1001,32 +1001,75 @@ g_subprocess_start (GSubprocess       *self,
 					cancellable, error);
 }
 
+/**
+ * g_subprocess_run_sync:
+ * @self: a #GSubprocess
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * When called, this runs the child process synchronously using
+ * the current configuration.  You must have at least initialized
+ * an argument vector using g_subprocess_append_args() or a related
+ * function.
+ *
+ * This function simply wraps g_subprocess_start() and
+ * g_subprocess_wait_sync().  See the documentation for both of those,
+ * as well as g_subprocess_query_success().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * already been called.
+ */
+gboolean
+g_subprocess_run_sync (GSubprocess   *self,
+		       GCancellable  *cancellable,
+		       GError       **error)
+{
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING, FALSE);
+
+  if (!g_subprocess_start (self, cancellable, error))
+    goto out;
+  if (!g_subprocess_wait_sync (self, cancellable, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+#ifdef G_OS_UNIX
+
+static inline void
+safe_dup2 (int   a,
+	   int   b)
+{
+  do
+    {
+      dup2 (a, b);
+    } while (errno == EINTR);
+}
+
 static void
 g_subprocess_internal_child_setup (gpointer        user_data)
 {
   GSubprocess *self = user_data;
 
   if (self->stdin_fd >= 0)
-    dup2 (self->stdin_fd, 0);
+    safe_dup2 (self->stdin_fd, 0);
   if (self->stdout_fd >= 0)
-    dup2 (self->stdout_fd, 1);
+    safe_dup2 (self->stdout_fd, 1);
   if (self->stderr_fd >= 0)
-    dup2 (self->stderr_fd, 2);
+    safe_dup2 (self->stderr_fd, 2);
+  if (self->stderr_to_stdout)
+    safe_dup2 (1, 2);
 
   if (self->child_setup)
     self->child_setup (self->child_setup_user_data);
 }
 
-static void
-g_subprocess_on_child_exited (GPid        pid,
-			      gint        status,
-			      gpointer    user_data)
-{
-  GSubprocess *self = user_data;
-
-  self->state = G_SUBPROCESS_STATE_TERMINATED;
-  self->exit_status = status;
-}
+#endif
 
 static void
 internal_error_occurred (GSubprocess   *self,
@@ -1109,11 +1152,14 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
   if (stdout_stream)
     g_return_val_if_fail (self->stdout_fd == -1
 			  && self->stdout_path == NULL
-			  && self->stdout_to_devnull == FALSE);
+			  && self->stdout_to_devnull == FALSE,
+			  FALSE);
   if (stderr_stream)
     g_return_val_if_fail (self->stderr_fd == -1
 			  && self->stderr_path == NULL
-			  && self->stderr_to_devnull == FALSE);
+			  && self->stderr_to_devnull == FALSE
+			  && self->stderr_to_stdout == FALSE,
+			  FALSE);
 
   self->state = G_SUBPROCESS_STATE_RUNNING;
   
@@ -1123,7 +1169,8 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
       self->stdin_fd = open (self->stdin_path, O_RDONLY);
       if (self->stdin_fd < 0)
 	{
-	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno));
+	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
+		       _("Failed to open file '%s'"), self->stdin_path);
 	  goto out;
 	}
       g_free (self->stdin_path);
@@ -1131,11 +1178,12 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
     }
   if (self->stdout_path)
     {
-      self->stdout_fd = g_open (self->stdout_path, O_CREAT | O_APPEND | O_WRONLY | O_BINARY,
+      self->stdout_fd = g_open (self->stdout_path, O_CREAT | O_APPEND | O_WRONLY,
 				0666);
       if (self->stdout_fd < 0)
 	{
-	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno));
+	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
+		       _("Failed to open file '%s'"), self->stdout_path);
 	  goto out;
 	}
       g_free (self->stdout_path);
@@ -1143,11 +1191,12 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
     }
   if (self->stderr_path)
     {
-      self->stderr_fd = g_open (self->stderr_path, O_CREAT | O_APPEND | O_WRONLY | O_BINARY,
+      self->stderr_fd = g_open (self->stderr_path, O_CREAT | O_APPEND | O_WRONLY,
 				0666);
       if (self->stderr_fd < 0)
 	{
-	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno));
+	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
+		       _("Failed to open file '%s'"), self->stdout_path);
 	  goto out;
 	}
       g_free (self->stderr_path);
@@ -1159,9 +1208,9 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
       GFile *stdin_file;
 
       stdin_file = g_file_new_for_path (self->stdin_path);
-      self->stdin_from_file_stream = g_file_read (stdin_file, cancellable, error);
+      self->stdin_stream = g_file_read (stdin_file, cancellable, error);
       g_object_unref (stdin_file);
-      if (!self->stdin_from_file_stream)
+      if (!self->stdin_stream)
 	goto out;
       g_free (self->stdin_path);
       self->stdin_path = NULL;
@@ -1245,6 +1294,7 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
   if (!g_spawn_async_with_pipes (self->working_directory,
 				 real_argv,
 				 self->child_envp,
+				 spawn_flags,
 				 child_setup, child_setup_user_data,
 				 &self->pid,
 				 stdin_arg, stdout_arg, stderr_arg,
@@ -1254,7 +1304,7 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
   if (stdin_pipe_fd != -1)
     {
       g_assert (self->stdin_stream);
-      self->child_input_pipe_stream = g_unix_output_stream_new (stdin_pipe_fd);
+      self->child_input_pipe_stream = g_unix_output_stream_new (stdin_pipe_fd, TRUE);
       g_output_stream_splice_async (self->child_input_pipe_stream,
 				    self->stdin_stream,
 				    G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
@@ -1268,23 +1318,13 @@ g_subprocess_start_with_pipes (GSubprocess       *self,
   if (stdout_pipe_fd != -1)
     {
       g_assert (stdout_stream);
-      ret_stdout_stream = g_unix_input_stream_new (stdout_pipe_fd);
+      ret_stdout_stream = g_unix_input_stream_new (stdout_pipe_fd, TRUE);
     }
 
   if (stderr_pipe_fd != -1)
     {
       g_assert (stderr_stream);
-      ret_stderr_stream = g_unix_input_stream_new (stderr_pipe_fd);
-    }
-
-  if (!self->detached)
-    {
-      self->child_watch = g_child_watch_source_new (self->pid);
-      g_source_set_callback (self->child_watch,
-			     (GSourceFunc) g_subprocess_on_child_exited,
-			     g_object_ref (self), (GDestroyNotify)g_object_unref);
-      g_source_attach (self->child_watch,
-		       g_main_context_get_thread_default ());
+      ret_stderr_stream = g_unix_input_stream_new (stderr_pipe_fd, TRUE);
     }
 
   ret = TRUE;
@@ -1334,27 +1374,269 @@ g_subprocess_get_pid (GSubprocess     *self)
 }
 
 /**
- * g_subprocess_wait_async:
+ * g_subprocess_add_watch:
  * @self: a #GSubprocess
- * @cancellable: a #GCancellable
- * @callback: Function invoked when child has terminated
- * @user_data: Data for @callback
+ * @function: Callback invoked when child has exited
+ * @user_data: Data for @function
  *
- * This function can only be called when the process has been started
- * via g_subprocess_start() or a related function.
+ * This function is similar to using g_child_watch_add(), except it
+ * operates on the thread default main context.  See
+ * g_main_context_get_thread_default().
+ *
+ * For more information, see the documentation of
+ * g_subprocess_add_watch_full().
+ *
+ * Returns: (transfer full): A newly-created #GSource for this process
  */
-void
-g_subprocess_wait_async (GSubprocess        *self,
-			 GCancellable       *cancellable,
-			 GAsyncReadyCallback callback,
-			 gpointer            user_data)
+GSource *
+g_subprocess_add_watch (GSubprocess             *self,
+			GSubprocessWatchFunc     function,
+			gpointer                 user_data)
+{
+  return g_subprocess_add_watch_full (self, G_PRIORITY_DEFAULT, function, user_data, NULL);
+}
+
+typedef struct {
+  GSubprocess *self;
+  GSubprocessWatchFunc *callback;
+  gpointer user_data;
+  GDestroyNotify notify;
+} GSubprocessWatchTrampolineData;
+
+static void
+g_subprocess_child_watch_func (GPid       pid,
+			       gint       exit_status,
+			       gpointer   user_data)
 {
+  GSubprocessWatchTrampolineData *data = user_data;
+
+  data->self->exit_status = exit_status;
+  data->self->state = G_SUBPROCESS_STATE_TERMINATED;
+  
+  if (data->callback)
+    data->callback (data->self, data->user_data);
+}
+
+static void
+g_subprocess_trampoline_data_destroy (gpointer user_data)
+{
+  GSubprocessWatchTrampolineData *data = user_data;
+
+  if (data->notify)
+    data->notify (data->user_data);
+  
+  g_object_unref (data->self);
+  g_free (data);
+}
+
+/**
+ * g_subprocess_add_watch_full:
+ * @self: a #GSubprocess
+ * @priority: I/O priority
+ * @function: Callback invoked when child has exited
+ * @user_data: Data for @function
+ * @notify: Destroy notify
+ *
+ * This function is similar to using g_child_watch_add_full(), except
+ * it operates on the thread default main context.  See
+ * g_main_context_get_thread_default().
+ *
+ * Inside the callback, you should call either
+ * g_subprocess_query_success() or g_subprocess_get_exit_code() to
+ * determine the status of the child.
+ *
+ * Returns: (transfer full): A newly-created #GSource for this process
+ */
+GSource *
+g_subprocess_add_watch_full (GSubprocess             *self,
+			     gint                     priority,
+			     GSubprocessWatchFunc     function,
+			     gpointer                 user_data,
+			     GDestroyNotify           notify)
+{
+  GSource *source;
+  GSubprocessWatchTrampolineData *trampoline_data;
+
   g_return_val_if_fail (G_IS_SUBPROCESS (self), 0);
-  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_RUNNING
-			|| self->state == G_SUBPROCESS_STATE_TERMINATED, 0);
+  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_RUNNING, 0);
+
+  source = g_child_watch_source_new (self->pid);
+  g_source_set_priority (source, priority);
+  trampoline_data = g_new (GSubprocessWatchTrampolineData, 1);
+  trampoline_data->self = g_object_ref (self);
+  trampoline_data->callback = function;
+  trampoline_data->user_data = user_data;
+  trampoline_data->notify = notify;
+  g_source_set_callback (source, (GSourceFunc)g_subprocess_child_watch_func, trampoline_data,
+			 g_subprocess_trampoline_data_destroy);
+  g_source_attach (source, g_main_context_get_thread_default ());
+
+  return source;
+}
 
+/**
+ * g_subprocess_query_success:
+ * @self: a #GSubprocess
+ * @error: a #GError
+ *
+ * Unlike the g_spawn_async_with_pipes() family of functions, this
+ * function by default sets an error if the child exits abnormally
+ * (e.g. with a nonzero exit code, or via a fatal signal).
+ *
+ * In the case where the child exits abnormally, the resulting @error
+ * will have domain %G_IO_ERROR, code %G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL.
+ *
+ * You can query the actual exit status via
+ * g_subprocess_get_exit_code().
+ *
+ * It is invalid to call this function unless the child has actually
+ * terminated.  You can wait for the child to exit via
+ * g_subprocess_add_watch(), or synchronously via
+ * g_subprocess_wait_sync().
+ *
+ * Returns: %TRUE if child exited successfully, %FALSE otherwise
+ */
+gboolean
+g_subprocess_query_success (GSubprocess   *self,
+			    GError       **error)
+{
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!self->detached, FALSE);
+  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_TERMINATED, FALSE);
+
+#ifdef G_OS_UNIX
+  if (WIFEXITED (self->exit_status))
+    {
+      if (WEXITSTATUS (self->exit_status) != 0)
+	{
+	  g_set_error (error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL,
+		       _("Child process %ld exited with code %ld"),
+		       (long) self->pid, (long) self->exit_status);
+	  goto out;
+	}
+    }
+  else if (WIFSIGNALED (self->exit_status))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL,
+		   _("Child process %ld killed by signal %ld"),
+		   (long) self->pid, (long) WTERMSIG (self->exit_status));
+      goto out;
+    }
+  else if (WIFSTOPPED (self->exit_status))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL,
+		   _("Child process %ld stopped by signal %ld"),
+		   (long) self->pid, (long) WSTOPSIG (self->exit_status));
+      goto out;
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL,
+		   _("Child process %ld exited abnormally"),
+		   (long) self->pid);
+      goto out;
+    }
+#else
+  if (self->exit_status != 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL,
+		   _("Child process %ld exited abnormally"),
+		   (long) self->pid);
+      goto out;
+    }
+#endif
+      
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * g_subprocess_get_exit_code:
+ * @self: a #GSubprocess
+ *
+ * Returns the exit code from the process, in the same form as
+ * provided by g_spawn_async_with_pipes().  In the typical case where
+ * you simply want to print an error message if the child exited
+ * abnormally, use g_subprocess_query_success().
+ *
+ * It is invalid to call this function unless the child has actually
+ * terminated.  You can wait for the child to exit via
+ * g_subprocess_add_watch(), or synchronously via
+ * g_subprocess_wait_sync().
+ *
+ * Returns: Exit code of child
+ */
+gint
+g_subprocess_get_exit_code (GSubprocess   *self)
+{
+  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!self->detached, FALSE);
+  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_TERMINATED, FALSE);
+
+  return self->exit_status;
 }
 
-void             g_subprocess_wait_async_finish (GSubprocess        *self,
-						 GAsyncResult       *result,
-						 GError            **error);
+static void
+g_subprocess_on_sync_watch (GSubprocess   *self,
+			    gpointer       user_data)
+{
+  GMainLoop *loop = user_data;
+
+  g_main_loop_quit (loop);
+}
+
+/**
+ * g_subprocess_wait_sync:
+ * @self: a #GSubprocess
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Synchronously wait for the subprocess to terminate.  This function
+ * will also invoke g_subprocess_query_success(), meaning that by
+ * default @error will be set if the subprocess exits abornmally.
+ * 
+ * Returns: %TRUE if child exited successfully, %FALSE otherwise
+ */
+gboolean
+g_subprocess_wait_sync (GSubprocess        *self,
+			GCancellable       *cancellable,
+			GError            **error)
+{
+  gboolean ret = FALSE;
+  gboolean pushed_thread_default = FALSE;
+  GMainContext *context = NULL;
+  GMainLoop *loop = NULL;
+  GSource *source = NULL;
+
+  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!self->detached, FALSE);
+  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_RUNNING, FALSE);
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  pushed_thread_default = TRUE;
+  loop = g_main_loop_new (context, TRUE);
+  
+  source = g_subprocess_add_watch (self, g_subprocess_on_sync_watch, loop);
+
+  g_main_loop_run (loop);
+
+  if (!g_subprocess_query_success (self, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (pushed_thread_default)
+    g_main_context_pop_thread_default (context);
+  if (source)
+    g_source_unref (source);
+  if (context)
+    g_main_context_unref (context);
+  if (loop)
+    g_main_loop_unref (loop);
+
+  return ret;
+}
diff --git a/gio/gsubprocess.h b/gio/gsubprocess.h
index 3acd2a0..76d24f4 100644
--- a/gio/gsubprocess.h
+++ b/gio/gsubprocess.h
@@ -150,23 +150,34 @@ gboolean         g_subprocess_start_with_pipes (GSubprocess       *self,
 						GCancellable      *cancellable,
 						GError           **error);
 
+gboolean         g_subprocess_run_sync (GSubprocess   *self,
+					GCancellable  *cancellable,
+					GError       **error);
+
 GPid             g_subprocess_get_pid (GSubprocess     *self);
 
-void             g_subprocess_wait_async (GSubprocess        *self,
-					  GCancellable       *cancellable,
-					  GAsyncReadyCallback callback,
-					  gpointer            user_data);
+typedef void  (GSubprocessWatchFunc)    (GSubprocess      *subprocess,
+					 gpointer          user_data);
+
+GSource *        g_subprocess_add_watch (GSubprocess                  *self,
+					 GSubprocessWatchFunc          function,
+					 gpointer                      user_data);
 
-void             g_subprocess_wait_async_finish (GSubprocess        *self,
-						 GAsyncResult       *result,
-						 GError            **error);
+GSource *        g_subprocess_add_watch_full (GSubprocess                  *self,
+					      gint                          priority,
+					      GSubprocessWatchFunc          function,
+					      gpointer                      user_data,
+					      GDestroyNotify                notify);
+
+gboolean         g_subprocess_query_success (GSubprocess   *self,
+					     GError       **error);
+
+gint             g_subprocess_get_exit_code (GSubprocess   *self);
 
 gboolean         g_subprocess_wait_sync (GSubprocess   *self,
 					 GCancellable  *cancellable,
 					 GError       **error);
 
-gint             g_subprocess_get_exit_code (GSubprocess   *self);
-
 /**** Static utility functions ****/
 
 gboolean         g_subprocess_spawn_sync_get_output_utf8 (gchar        **argv,
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index c5cf59d..7a19800 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -45,6 +45,7 @@ TEST_PROGS +=	 		\
 	srvtarget		\
 	contexts		\
 	gsettings		\
+	gsubprocess		\
 	gschema-compile		\
 	async-close-output-stream \
 	gdbus-addresses		\
@@ -263,6 +264,13 @@ contexts_LDADD		  = $(progs_ldadd) \
 gsettings_SOURCES	  = gsettings.c
 gsettings_LDADD		  = $(progs_ldadd)
 
+gsubprocess_SOURCES	  = gsubprocess.c
+gsubprocess_LDADD	  = $(progs_ldadd)
+
+noinst_PROGRAMS += gsubprocess-testprog
+gsubprocess_testprog_SOURCES  = gsubprocess-testprog.c
+gsubprocess_testprog_LDADD = $(progs_ldadd)
+
 gschema_compile_SOURCES	  = gschema-compile.c
 gschema_compile_LDADD	  = $(progs_ldadd)
 
diff --git a/gio/tests/gsubprocess-testprog.c b/gio/tests/gsubprocess-testprog.c
new file mode 100644
index 0000000..434e5c3
--- /dev/null
+++ b/gio/tests/gsubprocess-testprog.c
@@ -0,0 +1,57 @@
+#include <gio/gio.h>
+#include <string.h>
+
+static GOptionEntry options[] = {
+  {NULL}
+};
+
+static int
+echo_mode (int argc,
+	   char **argv)
+{
+  int i;
+
+  for (i = 1; i < argc; i++)
+    g_print ("%s\n", argv[i]);
+
+  return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+  GOptionContext *context;
+  GError *error = NULL;
+  const char *mode;
+
+  g_type_init ();
+
+  context = g_option_context_new ("MODE - Test GSubprocess stuff");
+  g_option_context_add_main_entries (context, options, NULL);
+  if (!g_option_context_parse (context, &argc, &argv, &error))
+    {
+      g_printerr ("%s: %s\n", argv[0], error->message);
+      return 1;
+    }
+
+  if (argc < 2)
+    {
+      g_printerr ("MODE argument required\n");
+      return 1;
+    }
+
+  mode = argv[1];
+  if (strcmp (mode, "noop") == 0)
+    return 0;
+  else if (strcmp (mode, "exit1") == 0)
+    return 1;
+  else if (strcmp (mode, "echo") == 0)
+    return echo_mode (argc, argv);
+  else
+    {
+      g_printerr ("Unknown MODE %s\n", argv[1]);
+      return 1;
+    }
+
+  return TRUE;
+}
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
new file mode 100644
index 0000000..5b0a96a
--- /dev/null
+++ b/gio/tests/gsubprocess.c
@@ -0,0 +1,68 @@
+#include <gio/gio.h>
+#include <string.h>
+
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#endif
+
+static GSubprocess *
+get_test_subprocess (void)
+{
+  char *cwd_path;
+  GSubprocess *ret;
+
+  cwd_path = g_build_filename (g_get_current_dir (), 
+			       "gsubprocess-testprog", NULL);
+  ret = g_subprocess_new_with_args (cwd_path, NULL);
+  return ret;
+}
+
+static void
+test_noop (void)
+{
+  GSubprocess *proc = get_test_subprocess ();
+  GError *local_error = NULL;
+  GError **error = &local_error;
+
+  g_subprocess_append_args (proc, "noop", NULL);
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_no_error (local_error);
+  
+  g_object_unref (proc);
+}
+
+static void
+test_exit1 (void)
+{
+  GSubprocess *proc = get_test_subprocess ();
+  GError *local_error = NULL;
+  GError **error = &local_error;
+
+  g_subprocess_append_args (proc, "exit1", NULL);
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL);
+
+#ifdef G_OS_UNIX
+  {
+    int ecode = g_subprocess_get_exit_code (proc);
+    g_assert (WIFEXITED (ecode) && WEXITSTATUS (ecode) == 1);
+  }
+#endif
+
+  g_object_unref (proc);
+}
+
+int
+main (int argc, char **argv)
+{
+  g_type_init ();
+
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/gsubprocess/noop", test_noop);
+  g_test_add_func ("/gsubprocess/exit1", test_exit1);
+
+  return g_test_run ();
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]