[glib/wip/gsubprocess: 1/3] GSubprocess: New class for spawning child processes



commit 43006b1cdfd0492718f63a11dfd2345bf26e5dba
Author: Colin Walters <walters verbum org>
Date:   Thu May 17 14:37:17 2012 -0400

    GSubprocess: New class for spawning child processes
    
    There are a number of nice things this class brings:
    
    0) g_subprocess_append_args() is REALLY nice to use from C, as
       opposed to building up an argument array all at once.
    1) Operates in terms of G{Input,Output}Stream, not file descriptors
    2) Sets a GError for you if the child exits with nonzero code; Unix
       consumers don't have to copy the bits to do the WIFEXITED/WEXITCODE
       dance.
    3) Creates the child watch source on the thread-default main context,
       matching modern GIO API.
    4) The g_subprocess_set_standard_output_file_path() is a very handy
       API that can work on both Unix and Windows (but is quite efficient
       on Unix).
    5) Has high-level helper functions; for example "synchronously run this
       process and give me the entire output as a string that's been validated
       as UTF-8"
    
    One major conceptual issue with the code right now is that I'm using
    GUnix{Input,Output}Stream, but this won't work on Windows =(  What
    we really need I guess is a GFd{Input,Output}Stream that implements
    GPollable{Input,Output}Stream.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=672102

 docs/reference/gio/gio-docs.xml     |    4 +
 docs/reference/gio/gio-sections.txt |   58 +
 docs/reference/gio/gio.types        |    1 +
 gio/Makefile.am                     |    2 +
 gio/gio.h                           |    1 +
 gio/gio.symbols                     |   43 +
 gio/gioenums.h                      |    4 +-
 gio/giotypes.h                      |   10 +
 gio/gsubprocess.c                   | 2207 +++++++++++++++++++++++++++++++++++
 gio/gsubprocess.h                   |  253 ++++
 gio/tests/Makefile.am               |    8 +
 gio/tests/gsubprocess-testprog.c    |  112 ++
 gio/tests/gsubprocess.c             |  408 +++++++
 13 files changed, 3110 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index f5c599d..c6e25d3 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -106,6 +106,10 @@
         <xi:include href="xml/ginitable.xml"/>
         <xi:include href="xml/gasyncinitable.xml"/>
     </chapter>
+    <chapter id="subprocesses">
+      <title>Subprocesses</title>
+      <xi:include href="xml/gsubprocess.xml"/>
+    </chapter>
     <chapter id="networking">
       <title>Low-level network support</title>
       <xi:include href="xml/gsocket.xml"/>
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 7c5636d..546b036 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3894,3 +3894,61 @@ g_test_dbus_unset
 g_test_dbus_get_type
 g_test_dbus_flags_get_type
 </SECTION>
+
+<SECTION>
+<FILE>gsubprocess</FILE>
+<TITLE>GSubprocess</TITLE>
+GSubprocess
+g_subprocess_new
+g_subprocess_new_with_args
+<SUBSECTION Environment>
+g_subprocess_append_arg
+g_subprocess_append_args
+g_subprocess_append_args_va
+g_subprocess_set_argv
+g_subprocess_set_argv0
+g_subprocess_set_child_setup
+g_subprocess_set_detached
+g_subprocess_setenv
+g_subprocess_unsetenv
+g_subprocess_override_environ
+g_subprocess_set_io_priority
+g_subprocess_set_leave_descriptors_open
+g_subprocess_set_search_path
+g_subprocess_set_standard_error_file_path
+g_subprocess_set_standard_error_to_devnull
+g_subprocess_set_standard_error_to_stdout
+g_subprocess_set_standard_error_unix_fd
+g_subprocess_set_standard_input_bytes
+g_subprocess_set_standard_input_file_path
+g_subprocess_set_standard_input_str
+g_subprocess_set_standard_input_stream
+g_subprocess_set_standard_input_to_devnull
+g_subprocess_set_standard_input_unix_fd
+g_subprocess_set_standard_output_file_path
+g_subprocess_set_standard_output_to_devnull
+g_subprocess_set_standard_output_unix_fd
+g_subprocess_set_working_directory
+<SUBSECTION Running>
+g_subprocess_start_with_pipes
+g_subprocess_start
+g_subprocess_add_watch
+g_subprocess_add_watch_full
+g_subprocess_run_sync
+g_subprocess_wait_sync
+<SUBSECTION Status>
+g_subprocess_get_exit_code
+g_subprocess_get_pid
+g_subprocess_query_success
+<SUBSECTION Utilities>
+g_subprocess_run_sync_get_output_bytes
+g_subprocess_run_sync_get_stdout_utf8
+g_subprocess_start_and_splice_async
+g_subprocess_start_and_splice_finish
+<SUBSECTION Standard>
+G_IS_SUBPROCESS
+G_TYPE_SUBPROCESS
+G_SUBPROCESS
+<SUBSECTION Private>
+g_subprocess_get_type
+</SECTION>
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types
index 9eec8a9..8d8ed78 100644
--- a/docs/reference/gio/gio.types
+++ b/docs/reference/gio/gio.types
@@ -135,3 +135,4 @@ g_menu_get_type
 g_menu_item_get_type
 g_test_dbus_get_type
 g_test_dbus_flags_get_type
+g_subprocess_get_type
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/gio.symbols b/gio/gio.symbols
index 34c954f..e41ba9c 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1076,6 +1076,49 @@ g_threaded_socket_service_new
 g_tcp_connection_get_type
 g_tcp_connection_set_graceful_disconnect
 g_tcp_connection_get_graceful_disconnect
+g_subprocess_add_watch
+g_subprocess_add_watch_full
+g_subprocess_append_arg
+g_subprocess_append_args
+g_subprocess_append_args_va
+g_subprocess_get_exit_code
+g_subprocess_get_pid
+g_subprocess_get_type
+g_subprocess_new
+g_subprocess_new_with_args
+g_subprocess_override_environ
+g_subprocess_query_success
+g_subprocess_run_sync
+g_subprocess_run_sync_get_output_bytes
+g_subprocess_run_sync_get_stdout_utf8
+g_subprocess_set_argv
+g_subprocess_set_argv0
+g_subprocess_set_child_setup
+g_subprocess_set_detached
+g_subprocess_setenv
+g_subprocess_set_io_priority
+g_subprocess_set_leave_descriptors_open
+g_subprocess_set_search_path
+g_subprocess_set_standard_error_file_path
+g_subprocess_set_standard_error_to_devnull
+g_subprocess_set_standard_error_to_stdout
+g_subprocess_set_standard_error_unix_fd
+g_subprocess_set_standard_input_bytes
+g_subprocess_set_standard_input_file_path
+g_subprocess_set_standard_input_str
+g_subprocess_set_standard_input_stream
+g_subprocess_set_standard_input_to_devnull
+g_subprocess_set_standard_input_unix_fd
+g_subprocess_set_standard_output_file_path
+g_subprocess_set_standard_output_to_devnull
+g_subprocess_set_standard_output_unix_fd
+g_subprocess_set_working_directory
+g_subprocess_start
+g_subprocess_start_and_splice_async
+g_subprocess_start_and_splice_finish
+g_subprocess_start_with_pipes
+g_subprocess_unsetenv
+g_subprocess_wait_sync
 #ifndef G_OS_WIN32
 g_unix_connection_get_type
 g_unix_connection_receive_fd
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/giotypes.h b/gio/giotypes.h
index 876d856..e4f38ec 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -135,6 +135,16 @@ typedef struct _GIOStream                     GIOStream;
 typedef struct _GPollableInputStream          GPollableInputStream; /* Dummy typedef */
 typedef struct _GPollableOutputStream         GPollableOutputStream; /* Dummy typedef */
 typedef struct _GResolver                     GResolver;
+
+/**
+ * GSubprocess:
+ *
+ * A child process.
+ *
+ * Since: 2.34
+ */
+typedef struct _GSubprocess                   GSubprocess;
+
 /**
  * GResource:
  *
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
new file mode 100644
index 0000000..8cb825c
--- /dev/null
+++ b/gio/gsubprocess.c
@@ -0,0 +1,2207 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright  2012 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ */
+
+/**
+ * SECTION:gsubprocess
+ * @title: GSubprocess
+ * @short_description: Create child processes
+ *
+ * This class is primarily convenience API on top of the lower-level
+ * g_spawn_async_with_pipes() and related functions provided by GLib.
+ * 
+ * Since: 2.34
+ */
+
+#include "config.h"
+#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,
+  G_SUBPROCESS_STATE_TERMINATED
+} GSubprocessState;
+
+struct _GSubprocess
+{
+  GObject parent;
+  GSubprocessState state;
+
+  gchar *child_argv0;
+  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;
+
+  gint io_priority;
+
+  gchar *working_directory;
+
+  GSpawnChildSetupFunc child_setup;
+  gpointer             child_setup_user_data;
+
+  gint stdin_fd;
+  gchar *stdin_path;
+  GInputStream *stdin_stream;
+
+  gchar *stdout_path;
+  gint stdout_fd;
+  gchar *stderr_path;
+  gint stderr_fd;
+
+  GError *internal_error;
+
+  /* Used when we're writing input to the child via a pipe. */
+  GOutputStream *child_input_pipe_stream;
+
+  GPid pid;
+
+  int exit_status;
+};
+
+G_DEFINE_TYPE (GSubprocess, g_subprocess, G_TYPE_OBJECT);
+
+enum
+{
+  PROP_0,
+  PROP_EXECUTABLE
+};
+
+static void
+g_subprocess_init (GSubprocess  *self)
+{
+  self->state = G_SUBPROCESS_STATE_BUILDING;
+  self->child_argv = g_ptr_array_new_with_free_func (g_free);
+  self->stdin_to_devnull = TRUE;
+  self->stdin_fd = -1;
+  self->stdout_fd = -1;
+  self->stderr_fd = -1;
+  self->io_priority = G_PRIORITY_DEFAULT;
+}
+
+static void
+g_subprocess_dispose (GObject *object)
+{
+  GSubprocess *self = G_SUBPROCESS (object);
+
+  g_clear_object (&self->stdin_stream);
+  g_clear_object (&self->child_input_pipe_stream);
+  g_clear_error (&self->internal_error);
+
+  if (G_OBJECT_CLASS (g_subprocess_parent_class)->dispose != NULL)
+    G_OBJECT_CLASS (g_subprocess_parent_class)->dispose (object);
+}
+
+static void
+g_subprocess_finalize (GObject *object)
+{
+  GSubprocess *self = G_SUBPROCESS (object);
+
+  if (self->state == G_SUBPROCESS_STATE_RUNNING
+      && !self->detached)
+    {
+      g_warning ("GSubprocess being finalized without child having been reaped; see g_subprocess_set_detached()");
+#ifdef G_OS_UNIX
+      {
+	int status;
+	/* We're forced to block the caller here to ensure we don't
+	 * accumulate zombies.
+	 */
+	waitpid (self->pid, &status, 0);
+      }
+#endif
+    }
+
+  if (self->child_argv)
+    g_ptr_array_unref (self->child_argv);
+
+  g_strfreev (self->child_envp);
+
+  g_free (self->working_directory);
+
+  if (G_OBJECT_CLASS (g_subprocess_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_subprocess_parent_class)->finalize (object);
+}
+
+static void
+g_subprocess_set_property (GObject      *object,
+			   guint         prop_id,
+			   const GValue *value,
+			   GParamSpec   *pspec)
+{
+  GSubprocess *self = G_SUBPROCESS (object);
+
+  switch (prop_id)
+    {
+    case PROP_EXECUTABLE:
+      g_assert (self->child_argv->len == 0);
+      g_ptr_array_add (self->child_argv, g_value_dup_string (value));
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+g_subprocess_get_property (GObject    *object,
+			   guint       prop_id,
+			   GValue     *value,
+			   GParamSpec *pspec)
+{
+  GSubprocess *self = G_SUBPROCESS (object);
+
+  switch (prop_id)
+    {
+    case PROP_EXECUTABLE:
+      if (self->child_argv->len > 0)
+	g_value_set_string (value,
+			    self->child_argv->pdata[0]);
+      else
+	g_value_set_string (value, NULL);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+g_subprocess_class_init (GSubprocessClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->dispose = g_subprocess_dispose;
+  gobject_class->finalize = g_subprocess_finalize;
+  gobject_class->get_property = g_subprocess_get_property;
+  gobject_class->set_property = g_subprocess_set_property;
+
+  g_object_class_install_property (gobject_class, PROP_EXECUTABLE,
+				   g_param_spec_string ("executable",
+							P_("Executable"),
+							P_("Path to executable"),
+							NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+							G_PARAM_STATIC_STRINGS));
+}
+
+/**** Creation ****/
+
+/**
+ * g_subprocess_new:
+ * @executable: (array zero-terminated=1) (element-type guint8): Executable path
+ *
+ * Creates a new subprocess, with @executable as the initial argument
+ * vector.  You can append further arguments via
+ * e.g. g_subprocess_append_args().
+ * 
+ * Returns: A new #GSubprocess
+ * Since: 2.34
+ */
+GSubprocess *
+g_subprocess_new (const char *executable)
+{
+  return g_object_new (G_TYPE_SUBPROCESS, "executable", executable, NULL);
+}
+
+/**
+ * g_subprocess_new_with_args:
+ * @executable: (array zero-terminated=1) (element-type guint8): Executable path
+ * @...: a %NULL-terminated list of arguments
+ *
+ * See the documentation of g_subprocess_append_args_va() for details
+ * about child process arguments.
+ *
+ * After calling this function, you may append further arguments with
+ * e.g. g_subprocess_append_args().
+ * 
+ * Returns: A new #GSubprocess with the provided arguments
+ * Since: 2.34
+ */
+GSubprocess *
+g_subprocess_new_with_args (const gchar     *executable,
+			    ...)
+{
+  va_list args;
+  GSubprocess *ret;
+
+  ret = g_subprocess_new (executable);
+  
+  va_start (args, executable);
+  g_subprocess_append_args_va (ret, args);
+  va_end (args);
+  
+  return ret;
+}
+
+/**** Argument control ****/
+
+/**
+ * g_subprocess_set_argv:
+ * @self: a #GSubprocess:
+ * @argv: (array zero-terminated=1): Argument array, %NULL-terminated
+ *
+ * Set the arguments to be used by the child process.  This will
+ * overwrite the entire argument vector, including the
+ * #GSubprocess::executable property and any previous calls to
+ * g_subprocess_append_args().
+ *
+ * Due to limitations of gobject-introspection, the provided argument
+ * is annotated as an array of strings, not an array of bytestrings.
+ *
+ * For language bindings, you can use calls to
+ * g_subprocess_append_arg() indivdually to provide non-UTF8
+ * arguments.
+ *
+ * For more information, see g_subprocess_append_args_va().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_argv (GSubprocess          *self,
+		       gchar               **argv)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (argv != NULL);
+  g_return_if_fail (*argv != NULL);
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_ptr_array_set_size (self->child_argv, 0);
+  
+  for (; *argv; argv++)
+    g_ptr_array_add (self->child_argv, g_strdup (*argv));
+}
+
+/**
+ * g_subprocess_set_argv0:
+ * @self: a #GSubprocess:
+ * @argv0: (array zero-terminated=1) (element-type guint8): First argument
+ *
+ * On Unix, child processes receive a bytestring provided by the
+ * parent process as the first argument (i.e. argv[0]).  By default,
+ * GLib will set this to whatever executable is run, but you may override
+ * it with this function.
+ *
+ * For example, some implementations of Unix have just one 'grep'
+ * executable which behaves differently when executed as 'grep' or
+ * 'fgrep'.
+ *
+ * See the discussion of %G_SPAWN_FILE_AND_ARGV_ZERO in the
+ * g_spawn_async_with_pipes() documentation.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_argv0 (GSubprocess         *self,
+			const gchar         *argv0)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_free (self->child_argv0);
+  self->child_argv0 = g_strdup (argv0);
+}
+
+/**
+ * g_subprocess_append_arg:
+ * @self: a #GSubprocess:
+ * @arg: (array zero-terminated=1) (element-type guint8): Argument
+ *
+ * Append an argument to the child process.  On Unix, this argument is
+ * a bytestring.  For more information, see
+ * g_subprocess_append_args_va().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_append_arg (GSubprocess       *self,
+			 const gchar       *arg)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_ptr_array_add (self->child_argv, g_strdup (arg));
+}
+
+
+/**
+ * g_subprocess_append_args:
+ * @self: a #GSubprocess:
+ * @first: (array zero-terminated=1) (element-type guint8): First argument to be appended
+ * @...: a %NULL-terminated list of arguments
+ *
+ * Appends arguments to the child process.  For more information, see
+ * g_subprocess_append_args_va().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_append_args (GSubprocess       *self,
+			  const gchar       *first,
+			  ...) 
+{
+  va_list args;
+
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (first != NULL);
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  
+  g_subprocess_append_arg (self, first);
+
+  va_start (args, first);
+  g_subprocess_append_args_va (self, args);
+  va_end (args);
+}
+
+/**
+ * g_subprocess_append_args_va:
+ * @self: a #GSubprocess
+ * @args: List of %NULL-terminated arguments
+ *
+ * Append the provided @args to the child argument vector. 
+ *
+ * On Unix, the arguments are bytestrings.  Note though that while
+ * it's possible to pass arbitrary bytestrings to subprocesses on
+ * Unix, not all utility programs are written to handle this
+ * correctly.  You should typically only pass strings which are valid
+ * in the current locale encoding; see g_locale_to_utf8().
+ *
+ * On Windows, the arguments are in the GLib filename encoding
+ * (i.e. UTF-8).
+ *
+ * Note that you do not need to include %NULL itself into the argument
+ * array, as required by g_spawn_async_with_pipes(); it is inserted
+ * automatically.
+ *
+ * For more information, about how arguments are processed, see the
+ * documentation of g_spawn_async_with_pipes().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_append_args_va (GSubprocess       *self,
+			     va_list            args)
+{
+  const char *arg;
+
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  while ((arg = va_arg (args, const char *)) != NULL)
+    {
+      g_ptr_array_add (self->child_argv, g_strdup (arg));
+    }
+}
+
+/**** GSpawnFlags wrappers ****/
+
+/**
+ * g_subprocess_set_detached:
+ * @self: a #GSubprocess
+ * @detached: %TRUE if the child shouldn't be watched
+ *
+ * Unlike the g_spawn_async_with_pipes(), the default for #GSubprocess
+ * is to assume that child exit status should be available; this
+ * corresponds to the %G_SPAWN_DO_NOT_REAP_CHILD flag.
+ *
+ * If you don't plan to monitor the child status via a function like
+ * g_subprocess_add_watch() or g_subprocess_wait_sync(), you should
+ * set this flag.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+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
+ * @do_search_path: %TRUE if the system path should be searched
+ *
+ * See the documentation for %G_SPAWN_SEARCH_PATH for details; setting
+ * @do_search_path to %FALSE is equivalent to not providing that flag.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_search_path (GSubprocess     *self,
+			      gboolean         do_search_path)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  
+  self->search_path = do_search_path;
+}
+
+/**
+ * g_subprocess_set_leave_descriptors_open:
+ * @self: a #GSubprocess
+ * @do_leave_descriptors_open: %TRUE if file descriptors should be left open
+ *
+ * See the documentation for %G_SPAWN_LEAVE_DESCRIPTORS_OPEN for
+ * details.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+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);
+  
+  self->leave_descriptors_open = do_leave_descriptors_open;
+}
+
+/**** Envronment control ****/
+
+/**
+ * g_subprocess_setenv:
+ * @self: a #GSubprocess
+ * @variable: the environment variable to set, must not contain '='
+ * @value: the value for to set the variable to
+ * @overwrite: whether to change the variable if it already exists
+ *
+ * By default, the child process will inherit a copy of the
+ * environment of the current process.  This function, upon the first
+ * call, will take a snapshot of the current environment via
+ * g_get_environ(), then update it.  Further calls manipulate the
+ * snapshot.
+ *
+ * This has no effect on the current process.  See the documentation
+ * of g_environ_setenv() for more information about environment
+ * variables.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_setenv (GSubprocess       *self,
+		     gchar             *variable,
+		     gchar             *value,
+		     gboolean           overwrite)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  g_return_if_fail (variable != NULL);
+
+  if (self->child_envp == NULL)
+    self->child_envp = g_get_environ ();
+  
+  self->child_envp = g_environ_setenv (self->child_envp, variable, value,
+				       overwrite);
+}
+
+/**
+ * g_subprocess_unsetenv:
+ * @self: a #GSubprocess
+ * @variable: the environment variable to set, must not contain '='
+ *
+ * This modifies the environment child process will be run in; it has
+ * no effect on the current process.  See the documentation of
+ * g_environ_unsetenv() for more information about environment
+ * variables.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ */
+void
+g_subprocess_unsetenv (GSubprocess       *self,
+		       const gchar       *variable)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  g_return_if_fail (variable != NULL);
+
+  self->child_envp = g_environ_unsetenv (self->child_envp, variable);
+}
+
+/**
+ * g_subprocess_override_environ:
+ * @self: a #GSubprocess
+ * @envp: (array zero-terminated=1): An environment list
+ *
+ * This completely replaces the environment child process will be run
+ * in; it has no effect on the current process.  See the documentation
+ * of g_environ_setenv() for more information about environment
+ * variables.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_override_environ (GSubprocess       *self,
+			       gchar            **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);
+
+  g_strfreev (self->child_envp);
+  self->child_envp = g_strdupv (envp);
+}
+
+/**
+ * g_subprocess_set_working_directory:
+ * @self: a #GSubprocess
+ * @working_directory: (allow-none): Path to working directory, in the GLib file name encoding
+ *
+ * By default, the child process will inherit the working
+ * directory of the current process.  This function allows
+ * overriding that default.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ */
+void
+g_subprocess_set_working_directory (GSubprocess       *self,
+				    const gchar       *working_directory)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_free (self->working_directory);
+  self->working_directory = g_strdup (working_directory);
+}
+
+/**
+ * g_subprocess_set_child_setup:
+ * @self: a #GSubprocess
+ * @child_setup: (allow-none): Function to run in the context of just-forked child
+ * @user_data: (allow-none): User data for @child_setup
+ *
+ * This functionality is only available on Unix.  See the
+ * documentation of g_spawn_async_with_pipes() for more information
+ * about @child_setup.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_child_setup (GSubprocess           *self,
+			      GSpawnChildSetupFunc   child_setup,
+			      gpointer               user_data)
+{
+  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 ****/
+
+/**
+ * g_subprocess_set_io_priority:
+ * @self: a #GSubprocess
+ * @io_priority: I/O priority
+ *
+ * For the cases where #GSubprocess internally uses asynchronous I/O,
+ * this function allows controlling the priority.  The default is
+ * %G_PRIORITY_DEFAULT.
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_io_priority (GSubprocess       *self,
+			      gint               io_priority)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  self->io_priority = io_priority;
+}
+
+/**
+ * g_subprocess_set_standard_input_file_path:
+ * @self: a #GSubprocess
+ * @file_path: String containing path to file to use as standard input
+ *
+ * This function allows providing a file as input to the given
+ * subprocess.  The file will not be opened until g_subprocess_start()
+ * has been called.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_input_unix_fd(),
+ * g_subprocess_set_standard_input_stream(), and
+ * g_subprocess_set_standard_input_to_devnull().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_input_file_path (GSubprocess       *self,
+					   const gchar       *file_path)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  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;
+
+  self->stdin_path = g_strdup (file_path);
+}
+
+/**
+ * g_subprocess_set_standard_input_unix_fd:
+ * @self: a #GSubprocess
+ * @fd: File descriptor to use as standard input
+ *
+ * This function allows providing a file descriptor as input to the
+ * given subprocess.  Only available on Unix.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_input_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_input_unix_fd (GSubprocess       *self,
+					 gint               fd)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  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_fd = fd;
+}
+
+/**
+ * g_subprocess_set_standard_input_to_devnull:
+ * @self: a #GSubprocess
+ * @to_devnull: If %TRUE, redirect input from null stream, if %FALSE, inherit
+ *
+ * The default is for child processes to have their input stream
+ * pointed at a null stream (e.g. on Unix, /dev/null), because having
+ * multiple processes read from an input stream creates race
+ * conditions and is generally nonsensical.  See the documentation of
+ * g_spawn_async_with_pipes() and %G_SPAWN_CHILD_INHERITS_STDIN.
+ *
+ * If @to_devnull is %FALSE, then this function will cause the
+ * standard input of the child process to be inherited.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_input_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_input_to_devnull (GSubprocess       *self,
+					    gboolean           to_devnull)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  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;
+}
+
+/**
+ * g_subprocess_set_standard_input_stream:
+ * @self: a #GSubprocess
+ * @stream: an input stream
+ *
+ * Use the provided stream as input to the child process.  When the
+ * process is started, such as via g_subprocess_start(), the stream
+ * will be asynchronously provided to the child, via a function
+ * equivalent to g_output_stream_splice_async().  This operation may
+ * or may not occur in a separate thread; if your program modifies the
+ * given @stream after the process has started, the given @stream must
+ * be threadsafe.
+ * 
+ * Because of the fact that input is asynchronous, it is safe to use
+ * g_subprocess_set_standard_input_stream(), then synchronously wait
+ * for the child to complete via e.g. g_subprocess_run_sync().  The
+ * high level utility function g_subprocess_run_sync_get_stdout_utf8()
+ * is built this way.
+ *
+ * <note>If your input stream is from a file, such as that returned
+ * by g_file_read(), it is significantly more efficient (on Unix) to
+ * cause the child process to read the file directly via
+ * g_subprocess_set_standard_input_file_path().</note>
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_input_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_input_stream (GSubprocess       *self,
+					GInputStream      *stream)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (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_to_devnull = FALSE;
+  self->stdin_fd = -1;
+
+  self->stdin_stream = g_object_ref (stream);
+}
+
+/**
+ * g_subprocess_set_standard_input_bytes:
+ * @self: a #GSubprocess
+ * @buf: Buffer to use as input
+ *
+ * Use the provided data as input to the child process.  This
+ * function simply wraps g_subprocess_set_standard_input_stream()
+ * using g_memory_input_stream_new_from_bytes().
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_input_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_input_bytes (GSubprocess       *self,
+				       GBytes            *buf)
+{
+  GInputStream *stream;
+
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  g_return_if_fail (buf != NULL);
+
+  stream = g_memory_input_stream_new_from_bytes (buf);
+  g_subprocess_set_standard_input_stream (self, stream);
+  g_object_unref (stream);
+}
+
+/**
+ * g_subprocess_set_standard_input_str:
+ * @self: a #GSubprocess
+ * @str: (array zero-terminated=1) (element-type guint8): Buffer to use as input
+ *
+ * Use the provided data as input to the child process.  This function
+ * simply wraps g_subprocess_set_standard_input_bytes().
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_input_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_input_str (GSubprocess       *self,
+				     const gchar       *str)
+{
+  GBytes *bytes;
+
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+  g_return_if_fail (str != NULL);
+
+  bytes = g_bytes_new (str, strlen (str));
+  g_subprocess_set_standard_input_bytes (self, bytes);
+  g_bytes_unref (bytes);
+}
+
+/**
+ * g_subprocess_set_standard_output_to_devnull:
+ * @self: a #GSubprocess
+ * @to_devnull: If %TRUE, redirect process output to null stream
+ *
+ * The default is for the child process to inherit the standard output
+ * of the current process.  Specify %TRUE for @to_devnull to redirect it
+ * to the operating system's null stream.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_output_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_output_to_devnull (GSubprocess       *self,
+					     gboolean           to_devnull)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_free (self->stdout_path);
+  self->stdout_path = NULL;
+  self->stdout_to_devnull = FALSE;
+  self->stdout_fd = -1;
+
+  self->stdout_to_devnull = to_devnull;
+}
+
+/**
+ * g_subprocess_set_standard_output_file_path:
+ * @self: a #GSubprocess
+ * @file_path: String containing path to file to use as standard input
+ *
+ * This function allows providing a file as standard output for the
+ * 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().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_output_file_path (GSubprocess       *self,
+					    const gchar       *file_path)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_free (self->stdout_path);
+  self->stdout_path = NULL;
+  self->stdout_to_devnull = FALSE;
+  self->stdout_fd = -1;
+
+  self->stdout_path = g_strdup (file_path);
+}
+
+/**
+ * g_subprocess_set_standard_output_unix_fd:
+ * @self: a #GSubprocess
+ * @fd: File descriptor
+ *
+ * This function allows providing a file descriptor as standard output
+ * for the given subprocess.  Only available on Unix.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_output_file_path().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_output_unix_fd (GSubprocess       *self,
+					  gint               fd)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (self->state == G_SUBPROCESS_STATE_BUILDING);
+
+  g_free (self->stdout_path);
+  self->stdout_path = NULL;
+  self->stdout_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
+  self->stdout_fd = -1;
+
+  self->stdout_fd = fd;
+}
+
+/**
+ * g_subprocess_set_standard_error_to_devnull:
+ * @self: a #GSubprocess
+ * @to_devnull: If %TRUE, redirect process error output to null stream
+ *
+ * The default is for the child process to inherit the standard error
+ * of the current process.  Specify %TRUE for @to_devnull to redirect
+ * it to the operating system's null stream.
+ *
+ * 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.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_error_to_devnull (GSubprocess       *self,
+					    gboolean           to_devnull)
+{
+  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_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
+  self->stderr_fd = -1;
+
+  self->stderr_to_devnull = to_devnull;
+}
+
+/**
+ * g_subprocess_set_standard_error_to_stdout:
+ * @self: a #GSubprocess
+ * @to_stdout: 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.
+ *
+ * Since: 2.34
+ */
+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_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
+  self->stderr_fd = -1;
+
+  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
+ *
+ * This function allows providing a file as standard error for the
+ * given subprocess.  The file will not be opened until
+ * g_subprocess_start() has been called.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_error_to_devnull().
+ *
+ * It invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_error_file_path (GSubprocess       *self,
+					    const gchar       *file_path)
+{
+  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_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
+  self->stderr_fd = -1;
+
+  self->stderr_path = g_strdup (file_path);
+}
+
+/**
+ * g_subprocess_set_standard_error_unix_fd:
+ * @self: a #GSubprocess
+ * @fd: File descriptor
+ *
+ * This function allows providing a file descriptor as standard error
+ * for the given subprocess.  Only available on Unix.
+ *
+ * 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.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_standard_error_unix_fd (GSubprocess       *self,
+					  gint               fd)
+{
+  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_to_devnull = FALSE;
+  self->stderr_to_stdout = FALSE;
+  self->stderr_fd = -1;
+
+  self->stderr_fd = fd;
+}
+
+/**
+ * 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.
+ *
+ * Since: 2.34
+ */
+gboolean
+g_subprocess_start (GSubprocess       *self,
+		    GCancellable      *cancellable,
+		    GError           **error)
+{
+  return g_subprocess_start_with_pipes (self, NULL, NULL, NULL,
+					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.
+ *
+ * Since: 2.34
+ */
+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)
+{
+  int ecode;
+  do
+    {
+      ecode = dup2 (a, b);
+    } while (ecode == -1 && errno == EINTR);
+}
+
+static void
+g_subprocess_internal_child_setup (gpointer        user_data)
+{
+  GSubprocess *self = user_data;
+
+  if (self->stdin_fd >= 0)
+    safe_dup2 (self->stdin_fd, 0);
+  if (self->stdout_fd >= 0)
+    safe_dup2 (self->stdout_fd, 1);
+  if (self->stderr_fd >= 0)
+    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);
+}
+
+#endif
+
+static void
+internal_error_occurred (GSubprocess   *self,
+			 GError       **error)
+{
+  if (self->internal_error == NULL)
+    {
+      g_prefix_error (error, _("While writing input to child process: "));
+      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)
+{
+  GSubprocess *self = user_data;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  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
+ * @out_stdin_stream: (out) (allow-none): Return location for standard input pipe
+ * @out_stdout_stream: (out) (allow-none): Return location for standard output pipe
+ * @out_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 any of standard
+ * input, standard output and standard 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.
+ *
+ * <warning>If the process is not in detached mode via
+ * g_subprocess_set_detached(), you must create a watch for it using
+ * e.g. g_subprocess_add_watch(), or later query its status
+ * synchronously via g_subprocess_wait_sync().  Failing to do so leads
+ * to suboptimal behavior.</warning>
+ *
+ * If both input and output streams are given, using synchronous I/O
+ * on both sides risks deadlock.  Use asynchronous I/O such as
+ * g_input_stream_read_async() or higher level wrappers.
+ *
+ * It invalid to call this function after
+ * g_subprocess_start_with_pipes() has already been called.
+ *
+ * Since: 2.34
+ */
+gboolean
+g_subprocess_start_with_pipes (GSubprocess       *self,
+			       GOutputStream    **out_stdin_stream,
+			       GInputStream     **out_stdout_stream,
+			       GInputStream     **out_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;
+  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);
+  g_return_val_if_fail (self->child_argv->len > 0, FALSE);
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  if (out_stdin_stream)
+    g_return_val_if_fail (self->stdin_fd == -1
+			  && self->stdin_path == NULL
+			  && self->stdin_stream == NULL,
+			  FALSE);
+  if (out_stdout_stream)
+    g_return_val_if_fail (self->stdout_fd == -1
+			  && self->stdout_path == NULL
+			  && self->stdout_to_devnull == FALSE,
+			  FALSE);
+  if (out_stderr_stream)
+    g_return_val_if_fail (self->stderr_fd == -1
+			  && self->stderr_path == NULL
+			  && self->stderr_to_devnull == FALSE
+			  && self->stderr_to_stdout == FALSE,
+			  FALSE);
+
+  self->state = G_SUBPROCESS_STATE_RUNNING;
+  
+#ifdef G_OS_UNIX
+  if (self->stdin_path)
+    {
+      self->stdin_fd = g_open (self->stdin_path, O_RDONLY);
+      if (self->stdin_fd < 0)
+	{
+	  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);
+      self->stdin_path = NULL;
+    }
+  if (self->stdout_path)
+    {
+      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),
+		       _("Failed to open file '%s'"), self->stdout_path);
+	  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,
+				0666);
+      if (self->stderr_fd < 0)
+	{
+	  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);
+      self->stderr_path = NULL;
+    }
+#else
+  if (self->stdin_path)
+    {
+      GFile *stdin_file;
+
+      stdin_file = g_file_new_for_path (self->stdin_path);
+      self->stdin_stream = g_file_read (stdin_file, cancellable, error);
+      g_object_unref (stdin_file);
+      if (!self->stdin_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_assert (self->child_argv->len > 0);
+      g_ptr_array_add (tmp_argv, self->child_argv->pdata[0]);
+      g_ptr_array_add (tmp_argv, self->child_argv0);
+      for (i = 1; i < self->child_argv->len; i++)
+	g_ptr_array_add (tmp_argv, self->child_argv->pdata[i]);
+      g_ptr_array_add (tmp_argv, NULL);
+
+      real_argv = (char**)tmp_argv->pdata;
+
+      spawn_flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+    }
+  else
+    {
+      /* Now add the trailing NULL */
+      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->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 (out_stdin_stream != NULL
+      || self->stdin_stream != NULL)
+    stdin_arg = &stdin_pipe_fd;
+  else
+    {
+      g_assert (self->stdin_fd != -1 || self->stdin_to_devnull);
+      stdin_arg = NULL;
+      if (!self->stdin_to_devnull)
+	spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
+    }
+
+  if (out_stdout_stream != NULL)
+    stdout_arg = &stdout_pipe_fd;
+  else
+    {
+      g_assert (self->stdout_fd == -1 || self->stdout_to_devnull);
+      stdout_arg = NULL;
+      if (self->stdout_to_devnull)
+	spawn_flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+    }
+
+  if (out_stderr_stream != NULL)
+    stderr_arg = &stderr_pipe_fd;
+  else
+    {
+      g_assert (self->stderr_fd == -1
+		|| self->stderr_to_devnull
+		|| self->stderr_to_stdout);
+      stderr_arg = NULL;
+      if (self->stderr_to_devnull)
+	spawn_flags |= G_SPAWN_STDERR_TO_DEV_NULL;
+    }
+
+  if (!g_spawn_async_with_pipes (self->working_directory,
+				 real_argv,
+				 self->child_envp,
+				 spawn_flags,
+				 child_setup, child_setup_user_data,
+				 self->detached ? NULL : &self->pid,
+				 stdin_arg, stdout_arg, stderr_arg,
+				 error))
+    goto out;
+
+  ret = TRUE;
+
+  if (stdin_pipe_fd != -1)
+    {
+      GOutputStream *child_stdout = g_unix_output_stream_new (stdin_pipe_fd, TRUE);
+      if (self->stdin_stream)
+	{
+	  self->child_input_pipe_stream = child_stdout;
+	  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,
+					self->io_priority,
+					cancellable,
+					g_subprocess_on_input_splice_finished,
+					g_object_ref (self));
+	}
+      else
+	{
+	  g_assert (out_stdin_stream);
+	  *out_stdin_stream = child_stdout;
+	}
+    }
+  if (stdout_pipe_fd != -1)
+    {
+      g_assert (out_stdout_stream);
+      *out_stdout_stream = g_unix_input_stream_new (stdout_pipe_fd, TRUE);
+    }
+  if (stderr_pipe_fd != -1)
+    {
+      g_assert (out_stderr_stream);
+      *out_stderr_stream = g_unix_input_stream_new (stderr_pipe_fd, TRUE);
+    }
+ out:
+  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; this function may
+ * not be used if g_subprocess_set_detached() has been called.
+ *
+ * <warning>Due to the way GLib presently 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.</warning>
+ *
+ * This function can only be called when the process has been started
+ * via g_subprocess_start() or a related function.
+ *
+ * Since: 2.34
+ */
+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);
+  g_return_val_if_fail (!self->detached, 0);
+  
+  return self->pid;
+}
+
+/**
+ * g_subprocess_add_watch:
+ * @self: a #GSubprocess
+ * @function: Callback invoked when child has exited
+ * @user_data: Data for @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
+ * Since: 2.34
+ */
+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;
+  if (data->self->state != G_SUBPROCESS_STATE_TERMINATED)
+    {
+      g_assert (data->self->state == G_SUBPROCESS_STATE_RUNNING);
+      g_spawn_close_pid (data->self->pid);
+      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.
+ *
+ * This function may not be used if g_subprocess_set_detached() has
+ * been called.
+ *
+ * Returns: (transfer full): A newly-created #GSource for this process
+ * Since: 2.34
+ */
+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, 0);
+  g_return_val_if_fail (!self->detached, 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
+ * Since: 2.34
+ */
+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);
+
+  if (self->internal_error)
+    {
+      if (error)
+	*error = g_error_copy (self->internal_error);
+      return 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
+ * Since: 2.34
+ */
+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;
+}
+
+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
+ * Since: 2.34
+ */
+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;
+}
+
+/**** High level wrapers ****/
+
+typedef struct {
+  GSubprocess *self;
+  GSimpleAsyncResult *res;
+  gboolean caught_error;
+  GError *error;
+  guint events_needed;
+} GSubprocessSpliceData;
+
+static void
+g_subprocess_on_splice_finished (GObject       *obj,
+				 GAsyncResult  *res,
+				 gpointer       user_data)
+{
+  GSubprocessSpliceData *data = user_data;
+
+  if (!data->caught_error)
+    {
+      if (g_output_stream_splice_finish ((GOutputStream*)obj, res, &data->error) < 0)
+	data->caught_error = TRUE;
+    }
+
+  data->events_needed--;
+  if (data->events_needed == 0)
+    {
+      if (data->caught_error)
+	g_simple_async_result_take_error (data->res, data->error);
+      else
+	g_simple_async_result_set_op_res_gboolean (data->res, TRUE);
+      
+      g_simple_async_result_complete (data->res);
+      
+      g_object_unref (data->res);
+
+      g_object_unref (data->self);
+      g_free (data);
+    }
+}
+
+/**
+ * g_subprocess_start_and_splice_async:
+ * @self: a #GSubprocess
+ * @flags: Flags for splicing
+ * @stdout_splice: Transfer data from stdout into this stream
+ * @stderr_splice: Transfer data from stderr into this stream
+ * @cancellable: a #GCancellable
+ * @callback: Callback invoked when all data has been transferred
+ * @user_data: User data for @callback
+ *
+ * Start the process, asynchronously transferring data from child
+ * standard output and/or standard error into the provided streams.
+ * The @callback will be invoked when the child closes the streams,
+ * not when it terminates.
+ *
+ * If you want to query the child exit status, you need to use a
+ * function like g_subprocess_add_watch() to be notified when it exits
+ * as well.  Or, if you just want the output, use
+ * g_subprocess_set_detached().
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_start_and_splice_async (GSubprocess               *self,
+				     GOutputStreamSpliceFlags   flags,
+				     GOutputStream             *stdout_splice,
+				     GOutputStream             *stderr_splice,
+				     GCancellable              *cancellable,
+				     GAsyncReadyCallback        callback,
+				     gpointer                   user_data)
+{
+  GSubprocessSpliceData *op_data;
+  GSimpleAsyncResult *res;
+  GError *local_error = NULL;
+  GInputStream *subproc_stdout = NULL;
+  GInputStream *subproc_stderr = NULL;
+
+  if (!g_subprocess_start_with_pipes (self, NULL, stdout_splice ? &subproc_stdout : NULL,
+				      stderr_splice ? &subproc_stderr : NULL,
+				      cancellable, &local_error))
+    {
+      res = g_simple_async_result_new_take_error ((GObject*)self, callback, user_data,
+						  local_error);
+      g_simple_async_result_complete_in_idle (res);
+      return;
+    }
+
+  op_data = g_new0 (GSubprocessSpliceData, 1);
+  op_data->self = g_object_ref (self);
+  op_data->res = g_simple_async_result_new ((GObject*)self,
+					    callback, user_data,
+					    g_subprocess_start_and_splice_async);
+
+  if (stdout_splice)
+    {
+      g_output_stream_splice_async (stdout_splice, subproc_stdout, flags,
+				    self->io_priority, cancellable,
+				    g_subprocess_on_splice_finished, op_data);
+      g_object_unref (subproc_stdout);
+      op_data->events_needed++;
+    }
+
+  if (stderr_splice)
+    {
+      g_output_stream_splice_async (stderr_splice, subproc_stderr, flags,
+				    self->io_priority, cancellable,
+				    g_subprocess_on_splice_finished, op_data);
+      g_object_unref (subproc_stderr);
+      op_data->events_needed++;
+    }
+}
+
+/**
+ * g_subprocess_start_and_splice_finish:
+ * @self: a #GSubprocess
+ * @result: a #GAsyncResult
+ * @error: a #GError
+ *
+ * Complete an asynchronous splice operation.  There is at present no
+ * way to query the number of bytes written.
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE otherwise
+ * Since: 2.34
+ */
+gboolean
+g_subprocess_start_and_splice_finish (GSubprocess               *self,
+				      GAsyncResult              *result,
+				      GError                   **error)
+{
+  GSimpleAsyncResult *simple;
+
+  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  if (G_IS_SIMPLE_ASYNC_RESULT (result))
+    {
+      simple = (GSimpleAsyncResult*) result;
+      if (g_simple_async_result_propagate_error (simple, error))
+	return FALSE;
+    }
+  return TRUE;
+}
+
+typedef struct {
+  GSubprocess *self;
+  GError **error;
+  GMainLoop *loop;
+} GSubprocessRunSyncGetOutputData;
+
+static void
+g_subprocess_on_get_output_splice_done (GObject      *obj,
+					GAsyncResult *res,
+					gpointer      user_data)
+{
+  GSubprocessRunSyncGetOutputData *data = user_data;
+
+  g_subprocess_start_and_splice_finish (data->self, res, data->error);
+
+  g_main_loop_quit (data->loop);
+}
+
+static gboolean
+run_sync_get_output_membufs (GSubprocess               *self,
+			     GOutputStreamSpliceFlags   flags,
+			     GMemoryOutputStream      **out_stdout_buf,
+			     GMemoryOutputStream      **out_stderr_buf,
+			     GCancellable              *cancellable,
+			     GError                   **error)
+{
+  gboolean ret = FALSE;
+  GSubprocessRunSyncGetOutputData data;
+  gboolean pushed_thread_default;
+  GMainContext *context = NULL;
+  GOutputStream *stdout_membuf = NULL;
+  GOutputStream *stderr_membuf = NULL;
+
+  if (out_stdout_buf)
+    stdout_membuf = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+  if (out_stderr_buf)
+    stderr_membuf = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+
+  memset (&data, 0, sizeof (data));
+  data.error = error;
+  data.self = self;
+  
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  pushed_thread_default = TRUE;
+  data.loop = g_main_loop_new (context, TRUE);
+
+  g_subprocess_start_and_splice_async (self, flags, stdout_membuf,
+				       stderr_membuf, cancellable,
+				       g_subprocess_on_get_output_splice_done,
+				       &data);
+
+  g_main_loop_run (data.loop);
+
+  /* Note minor optimization opportunity: we create two main loops
+   * presently */
+  if (!g_subprocess_wait_sync (self, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+  if (out_stdout_buf)
+    {
+      *out_stdout_buf = (GMemoryOutputStream*)stdout_membuf;
+      stdout_membuf = NULL;
+    }
+  if (out_stderr_buf)
+    {
+      *out_stderr_buf = (GMemoryOutputStream*)stderr_membuf;
+      stderr_membuf = NULL;
+    }
+ out:
+  g_clear_object (&stdout_membuf);
+  g_clear_object (&stderr_membuf);
+  if (pushed_thread_default)
+    g_main_context_pop_thread_default (context);
+  if (context)
+    g_main_context_unref (context);
+  if (data.loop)
+    g_main_loop_unref (data.loop);
+
+  return ret;
+}
+
+/**
+ * g_subprocess_run_sync_get_output_bytes:
+ * @self: a #GSubprocess
+ * @out_stdout_bytes: (out) (transfer full) (allow-none): Standard output from child process
+ * @out_stderr_bytes: (out) (transfer full) (allow-none): Standard error from child process
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Synchronously run the child process, gathering output from standard
+ * output and/or standard error into the returned buffers.  To get them
+ * both into one stream, use g_subprocess_set_standard_error_to_stdout().
+ *
+ * If @error is set for any reason (including the child exiting
+ * unsuccessfully), then @out_stdout_bytes and @out_stderr_bytes will
+ * be left uninitialized!  If you want to run the child process,
+ * gathering output regardless of exit status, you should use the
+ * lower-level function g_subprocess_start_and_splice_async().
+ *
+ * Returns: %TRUE if child process exited, successfully %FALSE otherwise
+ * Since: 2.34
+ */
+gboolean
+g_subprocess_run_sync_get_output_bytes (GSubprocess          *self,
+					GBytes              **out_stdout_bytes,
+					GBytes              **out_stderr_bytes,
+					GCancellable         *cancellable,
+					GError              **error)
+{
+  gboolean ret = FALSE;
+  GMemoryOutputStream *stdout_membuf = NULL;
+  GMemoryOutputStream *stderr_membuf = NULL;
+  const gint flags = G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET | G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE;
+
+  if (!run_sync_get_output_membufs (self, flags,
+				    out_stdout_bytes ? &stdout_membuf : NULL,
+				    out_stderr_bytes ? &stderr_membuf : NULL,
+				    cancellable, error))
+    goto out;
+
+  if (stdout_membuf && !g_output_stream_close ((GOutputStream*) stdout_membuf,
+					       cancellable, error))
+    goto out;
+  if (stderr_membuf && !g_output_stream_close ((GOutputStream*) stderr_membuf,
+					       cancellable, error))
+    goto out;
+
+  ret = TRUE;
+  if (out_stdout_bytes)
+    *out_stdout_bytes = g_memory_output_stream_steal_as_bytes (stdout_membuf);
+  if (out_stderr_bytes)
+    *out_stderr_bytes = g_memory_output_stream_steal_as_bytes (stderr_membuf);
+ out:
+  g_clear_object (&stdout_membuf);
+  g_clear_object (&stderr_membuf);
+  return ret;
+}
+
+/**
+ * g_subprocess_run_sync_get_stdout_utf8:
+ * @self: A #GSubprocess
+ * @output_utf8: (out) (transfer full) (allow-none): Output string
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Synchronously run the child process, gathering complete output into
+ * the returned @output_utf8 string.  By default, stderr will go to
+ * the current process' stderr, but you may redirect it normally via
+ * g_subprocess_set_standard_error_to_devnull().
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ * Since: 2.34
+ */
+gboolean
+g_subprocess_run_sync_get_stdout_utf8 (GSubprocess   *self,
+				       gchar        **output_utf8,
+				       GCancellable  *cancellable,
+				       GError       **error)
+{
+  gboolean ret = FALSE;
+  gsize bytes_written;
+  GMemoryOutputStream *buf = NULL;
+
+  if (!run_sync_get_output_membufs (self, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+				    &buf, NULL, cancellable, error))
+    goto out;
+
+  /* Ensure it's NUL terminated */
+  if (!g_output_stream_write_all (G_OUTPUT_STREAM (buf), "", 1, &bytes_written,
+				  cancellable, error))
+    goto out;
+
+  if (!g_output_stream_close (G_OUTPUT_STREAM (buf), cancellable, error))
+    goto out;
+
+  if (!g_utf8_validate (g_memory_output_stream_get_data (buf),
+			-1, NULL))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+		   _("Subprocess output was invalid UTF-8"));
+      goto out;
+    }
+
+  ret = TRUE;
+  if (output_utf8)
+    *output_utf8 = g_memory_output_stream_steal_data (buf);
+ out:
+  g_clear_object (&buf);
+  return ret;
+}
diff --git a/gio/gsubprocess.h b/gio/gsubprocess.h
new file mode 100644
index 0000000..82545cb
--- /dev/null
+++ b/gio/gsubprocess.h
@@ -0,0 +1,253 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2012 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_SUBPROCESS_H__
+#define __G_SUBPROCESS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_SUBPROCESS         (g_subprocess_get_type ())
+#define G_SUBPROCESS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_SUBPROCESS, GSubprocess))
+#define G_IS_SUBPROCESS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SUBPROCESS))
+
+GType            g_subprocess_get_type (void) G_GNUC_CONST;
+
+/**** Creation ****/
+
+GLIB_AVAILABLE_IN_2_34
+GSubprocess *    g_subprocess_new (const gchar *executable);
+
+GLIB_AVAILABLE_IN_2_34
+GSubprocess *    g_subprocess_new_with_args (const gchar     *executable,
+					     ...) G_GNUC_NULL_TERMINATED;
+
+/**** Argument control ****/
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_argv (GSubprocess          *self,
+					gchar               **argv);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_argv0 (GSubprocess         *self,
+					 const gchar         *argv0);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_append_arg (GSubprocess       *self,
+					  const gchar       *arg);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_append_args (GSubprocess       *self,
+					   const gchar       *first,
+					   ...) G_GNUC_NULL_TERMINATED;
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_append_args_va (GSubprocess       *self,
+					      va_list            args);
+
+/**** Envronment control ****/
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_detached (GSubprocess     *self,
+					    gboolean         detached);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_search_path (GSubprocess     *self,
+					       gboolean         do_search_path);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_leave_descriptors_open (GSubprocess     *self,
+							  gboolean         do_leave_descriptors_open);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_setenv (GSubprocess       *self,
+				      gchar             *variable,
+				      gchar             *value,
+				      gboolean           overwrite);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_unsetenv (GSubprocess       *self,
+					const gchar       *variable);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_override_environ (GSubprocess       *self,
+						gchar            **envp);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_working_directory (GSubprocess       *self,
+						     const gchar       *working_directory);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_child_setup (GSubprocess           *self,
+					       GSpawnChildSetupFunc   child_setup,
+					       gpointer               user_data);
+
+/**** Input and Output ****/
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_io_priority (GSubprocess       *self,
+					       gint               io_priority);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_input_file_path (GSubprocess       *self,
+							    const gchar       *file_path);
+
+#ifdef G_OS_UNIX
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_input_unix_fd (GSubprocess       *self,
+							  gint               fd);
+#endif
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_input_to_devnull (GSubprocess       *self,
+							     gboolean           to_devnull);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_input_stream (GSubprocess       *self,
+							 GInputStream      *stream);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_input_bytes (GSubprocess       *self,
+							GBytes            *buf);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_input_str (GSubprocess       *self,
+						      const gchar       *str);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_output_to_devnull (GSubprocess       *self,
+							      gboolean           to_devnull);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_output_file_path (GSubprocess       *self,
+							     const gchar       *file_path);
+
+#ifdef G_OS_UNIX
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_output_unix_fd (GSubprocess       *self,
+							   gint               fd);
+#endif
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_error_to_devnull (GSubprocess       *self,
+							     gboolean           to_devnull);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_error_to_stdout (GSubprocess       *self,
+							    gboolean           to_stdout);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_standard_error_file_path (GSubprocess       *self,
+							    const gchar       *file_path);
+
+#ifdef G_OS_UNIX
+void             g_subprocess_set_standard_error_unix_fd (GSubprocess       *self,
+							  gint               fd);
+#endif
+
+/**** Running ****/
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_start (GSubprocess       *self,
+				     GCancellable      *cancellable,
+				     GError           **error);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_start_with_pipes (GSubprocess       *self,
+						GOutputStream    **out_stdin_stream,
+						GInputStream     **out_stdout_stream,
+						GInputStream     **out_stderr_stream,
+						GCancellable      *cancellable,
+						GError           **error);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_run_sync (GSubprocess   *self,
+					GCancellable  *cancellable,
+					GError       **error);
+
+GLIB_AVAILABLE_IN_2_34
+GPid             g_subprocess_get_pid (GSubprocess     *self);
+
+typedef void  (GSubprocessWatchFunc)    (GSubprocess      *subprocess,
+					 gpointer          user_data);
+
+GLIB_AVAILABLE_IN_2_34
+GSource *        g_subprocess_add_watch (GSubprocess                  *self,
+					 GSubprocessWatchFunc          function,
+					 gpointer                      user_data);
+
+GLIB_AVAILABLE_IN_2_34
+GSource *        g_subprocess_add_watch_full (GSubprocess                  *self,
+					      gint                          priority,
+					      GSubprocessWatchFunc          function,
+					      gpointer                      user_data,
+					      GDestroyNotify                notify);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_query_success (GSubprocess   *self,
+					     GError       **error);
+
+GLIB_AVAILABLE_IN_2_34
+gint             g_subprocess_get_exit_code (GSubprocess   *self);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_wait_sync (GSubprocess   *self,
+					 GCancellable  *cancellable,
+					 GError       **error);
+
+/**** High level wrapers ****/
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_start_and_splice_async (GSubprocess               *self,
+						      GOutputStreamSpliceFlags   flags,
+						      GOutputStream             *stdout_splice,
+						      GOutputStream             *stderr_splice,
+						      GCancellable              *cancellable,
+						      GAsyncReadyCallback        callback,
+						      gpointer                   user_data);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_start_and_splice_finish (GSubprocess               *self,
+						       GAsyncResult              *result,
+						       GError                   **error);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_run_sync_get_output_bytes (GSubprocess          *self,
+							 GBytes              **out_stdout_bytes,
+							 GBytes              **out_stderr_bytes,
+							 GCancellable         *cancellable,
+							 GError              **error);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_run_sync_get_stdout_utf8 (GSubprocess   *self,
+							gchar        **output_utf8,
+							GCancellable  *cancellable,
+							GError       **error);
+
+G_END_DECLS
+
+#endif /* __G_SUBPROCESS_H__ */
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..63f5f4c
--- /dev/null
+++ b/gio/tests/gsubprocess-testprog.c
@@ -0,0 +1,112 @@
+#include <gio/gio.h>
+#include <string.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#endif
+
+static GOptionEntry options[] = {
+  {NULL}
+};
+
+static int
+echo_mode (int argc,
+	   char **argv)
+{
+  int i;
+
+  for (i = 2; i < argc; i++)
+    g_print ("%s\n", argv[i]);
+
+  return 0;
+}
+
+static int
+echo_stdout_and_stderr_mode (int argc,
+			     char **argv)
+{
+  int i;
+
+  for (i = 2; i < argc; i++)
+    {
+      g_print ("%s\n", argv[i]);
+      g_printerr ("%s\n", argv[i]);
+    }
+
+  return 0;
+}
+
+static int
+cat_mode (int argc,
+	  char **argv)
+{
+#ifdef G_OS_UNIX
+  GInputStream *stdin;
+  GOutputStream *stdout;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  
+  stdin = g_unix_input_stream_new (0, FALSE);
+  stdout = g_unix_output_stream_new (1, FALSE);
+
+  if (g_output_stream_splice (stdout, stdin, 0, NULL, error) < 0)
+    {
+      g_printerr ("%s\n", local_error->message);
+      return 1;
+    }
+
+  return 0;
+#else
+  return 1;
+#endif
+}
+
+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, "assert-argv0") == 0)
+    {
+      if (strcmp (argv[0], "moocow") == 0)
+	return 0;
+      g_printerr ("argv0=%s != moocow\n", argv[0]);
+      return 1;
+    }
+  else if (strcmp (mode, "echo") == 0)
+    return echo_mode (argc, argv);
+  else if (strcmp (mode, "echo-stdout-and-stderr") == 0)
+    return echo_stdout_and_stderr_mode (argc, argv);
+  else if (strcmp (mode, "cat") == 0)
+    return cat_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..1ee0684
--- /dev/null
+++ b/gio/tests/gsubprocess.c
@@ -0,0 +1,408 @@
+#include <gio/gio.h>
+#include <string.h>
+
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#endif
+
+static GSubprocess *
+get_test_subprocess (const char *mode)
+{
+  char *cwd;
+  char *cwd_path;
+  GSubprocess *ret;
+
+  cwd = g_get_current_dir ();
+  cwd_path = g_build_filename (cwd, "gsubprocess-testprog", NULL);
+  ret = g_subprocess_new_with_args (cwd_path, mode, NULL);
+  g_free (cwd);
+  g_free (cwd_path);
+
+  return ret;
+}
+
+static void
+test_noop (void)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *proc;
+
+  proc = get_test_subprocess ("noop");
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_no_error (local_error);
+  
+  g_object_unref (proc);
+}
+
+static void
+test_noop_all_to_null (void)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *proc;
+
+  proc = get_test_subprocess ("noop");
+
+  g_subprocess_set_standard_input_to_devnull (proc, TRUE);
+  g_subprocess_set_standard_output_to_devnull (proc, TRUE);
+  g_subprocess_set_standard_error_to_devnull (proc, TRUE);
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_no_error (local_error);
+  
+  g_object_unref (proc);
+}
+
+static void
+test_noop_detached (void)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *proc;
+
+  proc = get_test_subprocess ("noop");
+
+  g_subprocess_set_detached (proc, TRUE);
+
+  (void)g_subprocess_start (proc, NULL, error);
+  g_assert_no_error (local_error);
+  
+  g_object_unref (proc);
+}
+
+#ifdef G_OS_UNIX
+static void
+test_search_path (void)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *proc;
+
+  proc = g_subprocess_new ("true");
+
+  g_subprocess_set_search_path (proc, TRUE);
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_no_error (local_error);
+  
+  g_object_unref (proc);
+}
+#endif
+
+static void
+test_exit1 (void)
+{
+  GSubprocess *proc;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+
+  proc = get_test_subprocess ("exit1");
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL);
+  g_clear_error (error);
+
+#ifdef G_OS_UNIX
+  {
+    int ecode = g_subprocess_get_exit_code (proc);
+    g_assert (WIFEXITED (ecode) && WEXITSTATUS (ecode) == 1);
+  }
+#endif
+
+  g_object_unref (proc);
+}
+
+static void
+test_echo1 (void)
+{
+  GSubprocess *proc;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  gchar *result;
+
+  proc = get_test_subprocess ("echo");
+  
+  g_subprocess_append_args (proc, "hello", "world!", NULL);
+
+  g_subprocess_run_sync_get_stdout_utf8 (proc, &result, NULL, error);
+  g_assert_no_error (local_error);
+
+  g_assert_cmpstr (result, ==, "hello\nworld!\n");
+
+  g_object_unref (proc);
+}
+
+static void
+test_echo_merged (void)
+{
+  GSubprocess *proc;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  gchar *result;
+
+  proc = get_test_subprocess ("echo-stdout-and-stderr");
+  
+  g_subprocess_append_args (proc, "merge", "this", NULL);
+  g_subprocess_set_standard_error_to_stdout (proc, TRUE);
+
+  g_subprocess_run_sync_get_stdout_utf8 (proc, &result, NULL, error);
+  g_assert_no_error (local_error);
+
+  g_assert_cmpstr (result, ==, "merge\nmerge\nthis\nthis\n");
+
+  g_free (result);
+  g_object_unref (proc);
+}
+
+static void
+test_cat_utf8 (void)
+{
+  GSubprocess *proc;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  gchar *result = NULL;
+
+  proc = get_test_subprocess ("cat");
+
+  g_subprocess_set_standard_input_str (proc, "hello, world!");
+  g_subprocess_run_sync_get_stdout_utf8 (proc, &result, NULL, error);
+  g_assert_no_error (local_error);
+
+  g_assert_cmpstr (result, ==, "hello, world!");
+
+  g_free (result);
+  g_object_unref (proc);
+}
+
+static void
+test_cat_non_utf8 (void)
+{
+  GSubprocess *proc;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  gchar *result;
+
+  proc = get_test_subprocess ("cat");
+
+  g_subprocess_set_standard_input_str (proc, "\xFE\xFE\xFF\xFF");
+  g_subprocess_run_sync_get_stdout_utf8 (proc, &result, NULL, error);
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
+  g_clear_error (&local_error);
+
+  g_object_unref (proc);
+}
+
+typedef struct {
+  guint events_pending;
+  gboolean caught_error;
+  GError *error;
+  GMainLoop *loop;
+
+  gint counter;
+  GOutputStream *first_stdin;
+} TestMultiSpliceData;
+
+static void
+on_one_multi_splice_done (GObject       *obj,
+			  GAsyncResult  *res,
+			  gpointer       user_data)
+{
+  TestMultiSpliceData *data = user_data;
+
+  if (!data->caught_error)
+    {
+      if (g_output_stream_splice_finish ((GOutputStream*)obj, res, &data->error) < 0)
+	data->caught_error = TRUE;
+    }
+
+  data->events_pending--;
+  if (data->events_pending == 0)
+    g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_idle_multisplice (gpointer     user_data)
+{
+  TestMultiSpliceData *data = user_data;
+
+  /* We write 2^1 + 2^2 ... + 2^10 or 2047 copies of "Hello World!\n"
+   * ultimately
+   */
+  if (data->counter >= 2047 || data->caught_error)
+    {
+      if (!g_output_stream_close (data->first_stdin, NULL, &data->error))
+	data->caught_error = TRUE;
+      data->events_pending--;
+      if (data->events_pending == 0)
+	{
+	  g_main_loop_quit (data->loop);
+	}
+      return FALSE;
+    }
+  else
+    {
+      int i;
+      for (i = 0; i < data->counter; i++)
+	{
+	  gsize bytes_written;
+	  if (!g_output_stream_write_all (data->first_stdin, "hello world!\n",
+					  strlen ("hello world!\n"), &bytes_written,
+					  NULL, &data->error))
+	    {
+	      data->caught_error = TRUE;
+	      return FALSE;
+	    }
+	}
+      data->counter *= 2;
+      return TRUE;
+    }
+}
+
+static void
+on_subprocess_exited (GSubprocess     *proc,
+		      gpointer         user_data)
+{
+  TestMultiSpliceData *data = user_data;
+
+  if (!data->caught_error)
+    {
+      if (!g_subprocess_query_success (proc, &data->error))
+	data->caught_error = TRUE;
+    }
+  data->events_pending--;
+  if (data->events_pending == 0)
+    g_main_loop_quit (data->loop);
+}
+
+static void
+test_multi_1 (void)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *first;
+  GSubprocess *second;
+  GSubprocess *third;
+  GOutputStream *first_stdin;
+  GInputStream *first_stdout;
+  GOutputStream *second_stdin;
+  GInputStream *second_stdout;
+  GOutputStream *third_stdin;
+  GInputStream *third_stdout;
+  GOutputStream *membuf;
+  GSource *proc_watch_source;
+  TestMultiSpliceData data;
+  int splice_flags = G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET;
+
+  first = get_test_subprocess ("cat");
+  second = get_test_subprocess ("cat");
+  third = get_test_subprocess ("cat");
+
+  membuf = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+
+  g_subprocess_start_with_pipes (first, &first_stdin, &first_stdout, NULL,
+				 NULL, error);
+  g_assert_no_error (local_error);
+  g_subprocess_start_with_pipes (second, &second_stdin, &second_stdout, NULL,
+				 NULL, error);
+  g_assert_no_error (local_error);
+  g_subprocess_start_with_pipes (third, &third_stdin, &third_stdout, NULL,
+				 NULL, error);
+  g_assert_no_error (local_error);
+
+  memset (&data, 0, sizeof (data));
+  data.loop = g_main_loop_new (NULL, TRUE);
+  data.counter = 1;
+  data.first_stdin = first_stdin;
+
+  data.events_pending++;
+  g_output_stream_splice_async (second_stdin, first_stdout, splice_flags, G_PRIORITY_DEFAULT,
+				NULL, on_one_multi_splice_done, &data);
+  data.events_pending++;
+  g_output_stream_splice_async (third_stdin, second_stdout, splice_flags, G_PRIORITY_DEFAULT,
+				NULL, on_one_multi_splice_done, &data);
+  data.events_pending++;
+  g_output_stream_splice_async (membuf, third_stdout, splice_flags, G_PRIORITY_DEFAULT,
+				NULL, on_one_multi_splice_done, &data);
+
+  data.events_pending++;
+  g_timeout_add (250, on_idle_multisplice, &data);
+
+  data.events_pending++;
+  proc_watch_source = g_subprocess_add_watch (first, on_subprocess_exited, &data);
+  g_source_unref (proc_watch_source);
+  data.events_pending++;
+  proc_watch_source = g_subprocess_add_watch (second, on_subprocess_exited, &data);
+  g_source_unref (proc_watch_source);
+  data.events_pending++;
+  proc_watch_source = g_subprocess_add_watch (third, on_subprocess_exited, &data);
+  g_source_unref (proc_watch_source);
+
+  g_main_loop_run (data.loop);
+
+  g_assert (!data.caught_error);
+  g_assert_no_error (data.error);
+
+  g_assert_cmpint (g_memory_output_stream_get_data_size ((GMemoryOutputStream*)membuf), ==, 26611);
+
+  g_main_loop_unref (data.loop);
+  g_object_unref (first_stdin);
+  g_object_unref (first_stdout);
+  g_object_unref (second_stdin);
+  g_object_unref (second_stdout);
+  g_object_unref (third_stdin);
+  g_object_unref (third_stdout);
+  g_object_unref (membuf);
+  g_object_unref (first);
+  g_object_unref (second);
+  g_object_unref (third);
+}
+
+static void
+test_argv0 (void)
+{
+  /*
+    TODO -
+    This doesn't work because libtool eats our argv0 trick =/
+  */
+
+  /*
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *proc;
+
+  proc = get_test_subprocess ("assert-argv0");
+
+  g_subprocess_set_argv0 (proc, "moocow");
+
+  (void)g_subprocess_run_sync (proc, NULL, error);
+  g_assert_no_error (local_error);
+  
+  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/noop-all-to-null", test_noop_all_to_null);
+  g_test_add_func ("/gsubprocess/noop-detached", test_noop_detached);
+#ifdef G_OS_UNIX
+  g_test_add_func ("/gsubprocess/search-path", test_search_path);
+#endif
+  g_test_add_func ("/gsubprocess/exit1", test_exit1);
+  g_test_add_func ("/gsubprocess/echo1", test_echo1);
+  g_test_add_func ("/gsubprocess/echo-merged", test_echo_merged);
+  g_test_add_func ("/gsubprocess/cat-utf8", test_cat_utf8);
+  g_test_add_func ("/gsubprocess/cat-non-utf8", test_cat_non_utf8);
+  g_test_add_func ("/gsubprocess/multi1", test_multi_1);
+  /* g_test_add_func ("/gsubprocess/argv0", test_argv0); */
+
+  return g_test_run ();
+}



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