[glib/wip/gsubprocess] wip



commit 90cad9256c1b541b39270d968903417983118d6f
Author: Colin Walters <walters verbum org>
Date:   Thu May 17 09:28:18 2012 -0400

    wip

 gio/gsubprocess.c |  478 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 gio/gsubprocess.h |   16 ++-
 2 files changed, 476 insertions(+), 18 deletions(-)
---
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
index 91f7f8d..42cf800 100644
--- a/gio/gsubprocess.c
+++ b/gio/gsubprocess.c
@@ -43,11 +43,13 @@ struct _GSubprocess
   GPtrArray *child_argv;
   gchar **child_envp;
 
+  gboolean detached : 1;
   gboolean search_path : 1;
   gboolean leave_descriptors_open : 1;
   gboolean stdin_to_devnull : 1;
   gboolean stdout_to_devnull : 1;
   gboolean stderr_to_devnull : 1;
+  gboolean stderr_to_stdout : 1;
 
   gchar *working_directory;
 
@@ -57,12 +59,28 @@ struct _GSubprocess
   gint stdin_fd;
   gchar *stdin_path;
   GInputStream *stdin_stream;
+
   gchar *stdout_path;
-  GInputStream *stdout_stream;
+  gint stdout_fd;
   gchar *stderr_path;
-  GInputStream *stderr_stream;
+  gint stderr_fd;
+
+  GCancellable *cancellable;
+
+  GError *internal_error;
+
+  /* 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);
@@ -79,6 +97,9 @@ g_subprocess_init (GSubprocess  *self)
   self->child_argv = g_ptr_array_new_with_free_func (g_free);
   self->search_path = TRUE;
   self->stdin_to_devnull = TRUE;
+  self->stdin_fd = -1;
+  self->stdout_fd = -1;
+  self->stderr_fd = -1;
 }
 
 static void
@@ -87,8 +108,10 @@ g_subprocess_dispose (GObject *object)
   GSubprocess *self = G_SUBPROCESS (object);
 
   g_clear_object (&self->stdin_stream);
-  g_clear_object (&self->stdout_stream);
-  g_clear_object (&self->stderr_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);
@@ -325,6 +348,16 @@ g_subprocess_append_args_va (GSubprocess       *self,
 
 /**** GSpawnFlags wrappers ****/
 
+void
+g_subprocess_set_detached (GSubprocess     *self,
+			   gboolean         detached)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  
+  self->detached = detached;
+}
+
 /**
  * g_subprocess_set_search_path:
  * @self: a #GSubprocess
@@ -507,9 +540,12 @@ g_subprocess_set_child_setup (GSubprocess           *self,
 {
   g_return_if_fail (G_IS_SUBPROCESS (self));
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
-
+#ifdef G_OS_UNIX
   self->child_setup = child_setup;
   self->child_setup_user_data = user_data;
+#else
+  g_warning ("g_subprocess_set_child_setup is only available on Unix");
+#endif
 }
 
 /**** Input and Output ****/
@@ -541,10 +577,11 @@ g_subprocess_set_standard_input_file_path (GSubprocess       *self,
   g_return_if_fail (file_path != NULL);
 
   g_clear_object (&self->stdin_stream);
+  g_free (self->stdin_path);
+  self->stdin_path = NULL;
   self->stdin_to_devnull = FALSE;
   self->stdin_fd = -1;
 
-  g_free (self->stdin_path);
   self->stdin_path = g_strdup (file_path);
 }
 
@@ -574,6 +611,7 @@ g_subprocess_set_standard_input_unix_fd (GSubprocess       *self,
   g_free (self->stdin_path);
   self->stdin_path = NULL;
   self->stdin_to_devnull = FALSE;
+  self->stdin_fd = -1;
 
   self->stdin_fd = fd;
 }
@@ -609,6 +647,7 @@ g_subprocess_set_standard_input_to_devnull (GSubprocess       *self,
   g_clear_object (&self->stdin_stream);
   g_free (self->stdin_path);
   self->stdin_path = NULL;
+  self->stdin_to_devnull = FALSE;
   self->stdin_fd = -1;
 
   self->stdin_to_devnull = to_devnull;
@@ -646,12 +685,12 @@ g_subprocess_set_standard_input_stream (GSubprocess       *self,
   g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
   g_return_if_fail (stream != NULL);
 
+  g_clear_object (&self->stdin_stream);
   g_free (self->stdin_path);
   self->stdin_path = NULL;
-  self->stdin_fd = -1;
   self->stdin_to_devnull = FALSE;
+  self->stdin_fd = -1;
 
-  g_clear_object (&self->stdin_stream);
   self->stdin_stream = g_object_ref (stream);
 }
 
@@ -739,10 +778,11 @@ 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;
   self->stdout_fd = -1;
-  g_clear_object (&self->stdout_stream);
 
   self->stdout_to_devnull = to_devnull;
 }
@@ -756,6 +796,11 @@ g_subprocess_set_standard_output_to_devnull (GSubprocess       *self,
  * given subprocess.  The file will not be opened until
  * g_subprocess_start() has been called.
  *
+ * <note>
+ *  The output file will be opened with similar semantics to 
+ *  g_file_append_to(); on Unix, open(path, O_CREAT, 0644).
+ * </note>
+ *
  * Calling this function overrides any previous calls, as well as
  * other related functions such as
  * g_subprocess_set_standard_output_to_devnull().
@@ -770,12 +815,13 @@ 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;
   self->stdout_fd = -1;
-  g_clear_object (&self->stdout_stream);
 
-  self->stdout_to_devnull = to_devnull;
+  self->stdout_path = g_strdup (file_path);
 }
 
 /**
@@ -840,6 +886,38 @@ g_subprocess_set_standard_error_to_devnull (GSubprocess       *self,
 }
 
 /**
+ * g_subprocess_set_standard_error_to_stdout:
+ * @self: a #GSubprocess
+ * @to_devnull: If %TRUE, redirect process error output to standard output
+ *
+ * The default is for the child process to inherit the standard error
+ * of the current process.  Specify %TRUE for it to be merged with
+ * standard output.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_error_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ */
+void
+g_subprocess_set_standard_error_to_stdout (GSubprocess       *self,
+					   gboolean           to_stdout)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_free (self->stderr_path);
+  self->stderr_path = NULL;
+  self->stderr_fd = -1;
+  g_clear_object (&self->stderr_stream);
+
+  self->stderr_to_stdout = to_stdout;
+
+}
+
+/**
  * g_subprocess_set_standard_error_file_path:
  * @self: a #GSubprocess
  * @file_path: String containing path to file to use as standard input
@@ -900,13 +978,383 @@ g_subprocess_set_standard_error_unix_fd (GSubprocess       *self,
   self->stderr_fd = fd;
 }
 
-GInputStream *
-g_subprocess_request_standard_output (GSubprocess  *self)
+/**
+ * g_subprocess_start:
+ * @self: a #GSubprocess
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * When called, this runs the child process asynchronously using
+ * the current configuration.  You must have at least initialized
+ * an argument vector using g_subprocess_append_args() or a related
+ * function.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * already been called.
+ */
+gboolean
+g_subprocess_start (GSubprocess       *self,
+		    GCancellable      *cancellable,
+		    GError           **error)
+{
+  return g_subprocess_start_with_pipes (self, NULL, NULL,
+					cancellable, error);
+}
+
+static void
+g_subprocess_internal_child_setup (gpointer        user_data)
+{
+  GSubprocess *self = user_data;
+
+  if (self->stdin_fd >= 0)
+    dup2 (self->stdin_fd, 0);
+  if (self->stdout_fd >= 0)
+    dup2 (self->stdout_fd, 1);
+  if (self->stderr_fd >= 0)
+    dup2 (self->stderr_fd, 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;
 }
 
-GInputStream *
-g_subprocess_request_standard_error (GSubprocess  *self)
+static void
+internal_error_occurred (GSubprocess   *self,
+			 GError       **error)
 {
+  if (self->internal_error == NULL)
+    g_propagate_error (&self->internal_error, *error);
+  else
+    g_clear_error (error);
 }
+
+static void
+g_subprocess_on_input_splice_finished (GObject      *src,
+				       GAsyncResult *res,
+				       gpointer      user_data)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *self = user_data;
+  gssize bytes_written;
+  
+  bytes_written = g_output_stream_splice_finish (G_OUTPUT_STREAM (src),
+						 res, error);
+  if (bytes_written < 0)
+    {
+      internal_error_occurred (self, error);
+    }
+
+  g_object_unref (self);
+}
+
+
+/**
+ * g_subprocess_start_with_pipes:
+ * @self: a #GSubprocess
+ * @stdout_stream: (out) (allow-none): Return location for standard output pipe
+ * @stderr_stream: (out) (allow-none): Return location for standard error pipe
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * When called, this runs the child process asynchronously using the
+ * current configuration, with pipes connected to the standard output
+ * or error.  You must have at least initialized an argument vector
+ * using g_subprocess_append_args() or a related function.
+ *
+ * See the documentation for g_spawn_async_with_pipes() for more
+ * information about how the child process has been run.
+ *
+ * It invalid to call this function after
+ * g_subprocess_start_with_pipes() has already been called.
+ */
+gboolean
+g_subprocess_start_with_pipes (GSubprocess       *self,
+			       GInputStream     **stdout_stream,
+			       GInputStream     **stderr_stream,
+			       GCancellable      *cancellable,
+			       GError           **error)
+{
+  gboolean ret = FALSE;
+  GPtrArray *tmp_argv = NULL;
+  gchar **real_argv;
+  GSpawnFlags spawn_flags = 0;
+  gint stdin_pipe_fd = -1;
+  gint *stdin_arg = NULL;
+  gint stdout_pipe_fd = -1;
+  gint *stdout_arg = NULL;
+  gint stderr_pipe_fd = -1;
+  gint *stderr_arg = NULL;
+  GInputStream *ret_stdout_stream = NULL;
+  GInputStream *ret_stderr_stream = NULL;
+  GSpawnChildSetupFunc child_setup;
+  gpointer child_setup_user_data;
+
+  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING, FALSE);
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  if (stdout_stream)
+    g_return_val_if_fail (self->stdout_fd == -1
+			  && self->stdout_path == NULL
+			  && self->stdout_to_devnull == FALSE);
+  if (stderr_stream)
+    g_return_val_if_fail (self->stderr_fd == -1
+			  && self->stderr_path == NULL
+			  && self->stderr_to_devnull == FALSE);
+
+  self->state = G_SUBPROCESS_STATE_RUNNING;
+  
+#ifdef G_OS_UNIX
+  if (self->stdin_path)
+    {
+      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));
+	  goto out;
+	}
+      g_free (self->stdin_path);
+      self->stdin_path = NULL;
+    }
+  if (self->stdout_path)
+    {
+      self->stdout_fd = g_open (self->stdout_path, O_CREAT | O_APPEND | O_WRONLY | O_BINARY,
+				0666);
+      if (self->stdout_fd < 0)
+	{
+	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno));
+	  goto out;
+	}
+      g_free (self->stdout_path);
+      self->stdout_path = NULL;
+    }
+  if (self->stderr_path)
+    {
+      self->stderr_fd = g_open (self->stderr_path, O_CREAT | O_APPEND | O_WRONLY | O_BINARY,
+				0666);
+      if (self->stderr_fd < 0)
+	{
+	  g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno));
+	  goto out;
+	}
+      g_free (self->stderr_path);
+      self->stderr_path = NULL;
+    }
+#else
+  if (self->stdin_path)
+    {
+      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);
+      g_object_unref (stdin_file);
+      if (!self->stdin_from_file_stream)
+	goto out;
+      g_free (self->stdin_path);
+      self->stdin_path = NULL;
+    }
+  if (self->stdout_path)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+		   "FIXME not supported yet");
+      goto out;
+    }
+  if (self->stderr_path)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+		   "FIXME not supported yet");
+      goto out;
+    }
+#endif
+
+  g_assert (self->stdin_path == NULL
+	    && self->stdout_path == NULL
+	    && self->stderr_path == NULL);
+
+  if (self->child_argv0)
+    {
+      guint i;
+
+      tmp_argv = g_ptr_array_new ();
+      g_ptr_array_add (tmp_argv, self->child_argv0);
+      for (i = 0; i < self->child_argv->len; i++)
+	g_ptr_array_add (tmp_argv, self->child_argv->pdata[i]);
+
+      real_argv = (char**)tmp_argv->pdata;
+
+      spawn_flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+    }
+  else
+    {
+      g_ptr_array_add (self->child_argv, NULL);
+      real_argv = (char**)self->child_argv->pdata;
+    }
+
+  if (self->leave_descriptors_open)
+    spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
+  if (self->search_path)
+    spawn_flags |= G_SPAWN_SEARCH_PATH;
+  if (self->stdout_to_devnull)
+    spawn_flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+  if (self->stderr_to_devnull)
+    spawn_flags |= G_SPAWN_STDERR_TO_DEV_NULL;
+  if (!self->detached)
+    spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
+
+#ifdef G_OS_UNIX
+  child_setup = g_subprocess_internal_child_setup;
+  child_setup_user_data = self;
+#else
+  child_setup = NULL;
+  child_setup_user_data = NULL;
+#endif
+
+  if (self->stdin_to_devnull || self->stdin_fd != -1)
+    stdin_arg = NULL;
+  else
+    stdin_arg = &stdin_pipe_fd;
+
+  if (self->stdout_fd != -1
+      || self->stdout_to_devnull
+      || stdout_stream == NULL)
+    stdout_arg = NULL;
+  else
+    stdout_arg = &stdout_pipe_fd;
+
+  if (self->stderr_fd != -1
+      || self->stderr_to_devnull
+      || self->stderr_to_stdout
+      || stderr_stream == NULL)
+    stderr_arg = NULL;
+  else
+    stderr_arg = &stderr_pipe_fd;
+
+  if (!g_spawn_async_with_pipes (self->working_directory,
+				 real_argv,
+				 self->child_envp,
+				 child_setup, child_setup_user_data,
+				 &self->pid,
+				 stdin_arg, stdout_arg, stderr_arg,
+				 error))
+    goto out;
+
+  if (stdin_pipe_fd != -1)
+    {
+      g_assert (self->stdin_stream);
+      self->child_input_pipe_stream = g_unix_output_stream_new (stdin_pipe_fd);
+      g_output_stream_splice_async (self->child_input_pipe_stream,
+				    self->stdin_stream,
+				    G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+				    G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+				    G_PRIORITY_DEFAULT,
+				    self->cancellable,
+				    g_subprocess_on_input_splice_finished,
+				    g_object_ref (self));
+    }
+
+  if (stdout_pipe_fd != -1)
+    {
+      g_assert (stdout_stream);
+      ret_stdout_stream = g_unix_input_stream_new (stdout_pipe_fd);
+    }
+
+  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 = TRUE;
+  if (stdout_stream)
+    {
+      *stdout_stream = ret_stdout_stream;
+      ret_stdout_stream = NULL;
+    }
+  if (stderr_stream)
+    {
+      *stderr_stream = ret_stderr_stream;
+      ret_stderr_stream = NULL;
+    }
+ out:
+  g_clear_object (&ret_stdout_stream);
+  g_clear_object (&ret_stderr_stream);
+  if (tmp_argv)
+    g_ptr_array_unref (tmp_argv);
+  return ret;
+}
+
+/**
+ * g_subprocess_get_pid:
+ * @self: a #GSubprocess
+ *
+ * Returns the identifier for this child process.
+ *
+ * <warning>
+ *  Due to the way GLib implements subprocess monitoring works on
+ *  Unix, attempting to use e.g. kill() to send a signal to the
+ *  child process has a race condition where the child process
+ *  may not exist, even if e.g. g_subprocess_wait_async()
+ *  has not yet returned.
+ * <warning>
+ *
+ * This function can only be called when the process has been started
+ * via g_subprocess_start() or a related function.
+ */
+GPid
+g_subprocess_get_pid (GSubprocess     *self)
+{
+  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);
+  
+  return self->pid;
+}
+
+/**
+ * g_subprocess_wait_async:
+ * @self: a #GSubprocess
+ * @cancellable: a #GCancellable
+ * @callback: Function invoked when child has terminated
+ * @user_data: Data for @callback
+ *
+ * This function can only be called when the process has been started
+ * via g_subprocess_start() or a related function.
+ */
+void
+g_subprocess_wait_async (GSubprocess        *self,
+			 GCancellable       *cancellable,
+			 GAsyncReadyCallback callback,
+			 gpointer            user_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);
+
+}
+
+void             g_subprocess_wait_async_finish (GSubprocess        *self,
+						 GAsyncResult       *result,
+						 GError            **error);
diff --git a/gio/gsubprocess.h b/gio/gsubprocess.h
index be9064d..3acd2a0 100644
--- a/gio/gsubprocess.h
+++ b/gio/gsubprocess.h
@@ -64,6 +64,9 @@ void             g_subprocess_append_args_va (GSubprocess       *self,
 
 /**** Envronment control ****/
 
+void             g_subprocess_set_detached (GSubprocess     *self,
+					    gboolean         detached);
+
 void             g_subprocess_set_search_path (GSubprocess     *self,
 					       gboolean         do_search_path);
 
@@ -124,6 +127,9 @@ void             g_subprocess_set_standard_output_unix_fd (GSubprocess       *se
 void             g_subprocess_set_standard_error_to_devnull (GSubprocess       *self,
 							     gboolean           to_devnull);
 
+void             g_subprocess_set_standard_error_to_stdout (GSubprocess       *self,
+							    gboolean           to_stdout);
+
 void             g_subprocess_set_standard_error_file_path (GSubprocess       *self,
 							    const gchar       *file_path);
 
@@ -132,14 +138,18 @@ void             g_subprocess_set_standard_error_unix_fd (GSubprocess       *sel
 							  gint               fd);
 #endif
 
-GInputStream    *g_subprocess_request_standard_output (GSubprocess  *self);
-GInputStream    *g_subprocess_request_standard_error (GSubprocess  *self);
-
 /**** Running ****/
 
 gboolean         g_subprocess_start (GSubprocess       *self,
+				     GCancellable      *cancellable,
 				     GError           **error);
 
+gboolean         g_subprocess_start_with_pipes (GSubprocess       *self,
+						GInputStream     **stdout_stream,
+						GInputStream     **stderr_stream,
+						GCancellable      *cancellable,
+						GError           **error);
+
 GPid             g_subprocess_get_pid (GSubprocess     *self);
 
 void             g_subprocess_wait_async (GSubprocess        *self,



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