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



commit 8080060fe1c553b4df7df18d0ed982d715a82bfe
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) On Unix, has race-free termination API (we don't reap the child
       until the GSubprocess is finalized, so the GPid is always valid)
    2) Operates in terms of G{Input,Output}Stream, not file descriptors
    3) 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.
    4) Creates the child watch source on the thread-default main context,
       matching modern GIO API.
    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"
    
    https://bugzilla.gnome.org/show_bug.cgi?id=672102

 docs/reference/gio/gio-docs.xml     |    4 +
 docs/reference/gio/gio-sections.txt |   56 +
 docs/reference/gio/gio.types        |    1 +
 gio/Makefile.am                     |    2 +
 gio/gio.h                           |    1 +
 gio/gio.symbols                     |   47 +
 gio/gioenums.h                      |    4 +-
 gio/giotypes.h                      |   10 +
 gio/gsubprocess.c                   | 2180 +++++++++++++++++++++++++++++++++++
 gio/gsubprocess.h                   |  251 ++++
 gio/tests/Makefile.am               |    8 +
 gio/tests/gsubprocess-testprog.c    |  151 +++
 gio/tests/gsubprocess.c             |  491 ++++++++
 13 files changed, 3205 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..bb13fdd 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3894,3 +3894,59 @@ 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_set_environment
+g_subprocess_set_io_priority
+g_subprocess_set_leave_descriptors_open
+g_subprocess_set_use_search_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_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
+g_subprocess_request_exit
+g_subprocess_force_exit
+<SUBSECTION Status>
+g_subprocess_get_status_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
+<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 d48d156..0c703d5 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -425,6 +425,7 @@ libgio_2_0_la_SOURCES =		\
 	gsocketlistener.c	\
 	gsocketoutputstream.c	\
 	gsocketoutputstream.h	\
+	gsubprocess.c		\
 	gproxy.c		\
 	gproxyaddress.c         \
 	gproxyaddressenumerator.c \
@@ -599,6 +600,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..ba8b92c 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1076,6 +1076,53 @@ 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_force_exit
+g_subprocess_get_status_code
+g_subprocess_get_pid
+g_subprocess_get_type
+g_subprocess_new
+g_subprocess_new_with_args
+g_subprocess_set_environment
+g_subprocess_query_success
+g_subprocess_request_exit
+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_use_search_path
+g_subprocess_set_standard_error_to_devnull
+g_subprocess_set_standard_error_to_stdout
+#ifdef G_OS_UNIX
+g_subprocess_set_standard_error_unix_fd
+#endif
+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
+#ifdef G_OS_UNIX
+g_subprocess_set_standard_input_unix_fd
+#endif
+g_subprocess_set_standard_output_to_devnull
+#ifdef G_OS_UNIX
+g_subprocess_set_standard_output_unix_fd
+#endif
+g_subprocess_set_working_directory
+g_subprocess_start
+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..5c841b8 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -135,6 +135,7 @@ typedef struct _GIOStream                     GIOStream;
 typedef struct _GPollableInputStream          GPollableInputStream; /* Dummy typedef */
 typedef struct _GPollableOutputStream         GPollableOutputStream; /* Dummy typedef */
 typedef struct _GResolver                     GResolver;
+
 /**
  * GResource:
  *
@@ -467,6 +468,15 @@ typedef GType (*GDBusProxyTypeFunc) (GDBusObjectManagerClient   *manager,
 
 typedef struct _GTestDBus GTestDBus;
 
+/**
+ * GSubprocess:
+ *
+ * A child process.
+ *
+ * Since: 2.34
+ */
+typedef struct _GSubprocess                   GSubprocess;
+
 G_END_DECLS
 
 #endif /* __GIO_TYPES_H__ */
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
new file mode 100644
index 0000000..d610e71
--- /dev/null
+++ b/gio/gsubprocess.c
@@ -0,0 +1,2180 @@
+/* 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 "glib-private.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
+#ifdef G_OS_WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#include "giowin32-priv.h"
+#endif
+
+typedef struct _GSubprocessClass GSubprocessClass;
+
+#ifdef G_OS_UNIX
+static void
+g_subprocess_unix_queue_waitpid (GSubprocess  *self);
+#endif
+
+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;
+
+  guint detached : 1;
+  guint search_path : 1;
+  guint leave_descriptors_open : 1;
+  guint stdin_to_devnull : 1;
+  guint stdout_to_devnull : 1;
+  guint stderr_to_devnull : 1;
+  guint stderr_to_stdout : 1;
+
+  guint reaped_child : 1;
+
+  gint io_priority;
+
+  gchar *working_directory;
+
+  GSpawnChildSetupFunc child_setup;
+  gpointer             child_setup_user_data;
+
+  gint stdin_fd;
+  gint internal_stdin_fd;
+  gchar *stdin_path;
+  GInputStream *stdin_stream;
+
+  gint stdout_fd;
+  gint internal_stdout_fd;
+  gint stderr_fd;
+  gint internal_stderr_fd;
+
+  GError *internal_error;
+
+  /* Used when we're writing input to the child via a pipe. */
+  GOutputStream *child_input_pipe_stream;
+
+  GPid pid;
+
+  gint status_code;
+};
+
+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->internal_stdin_fd = -1;
+  self->stdout_fd = -1;
+  self->internal_stdout_fd = -1;
+  self->stderr_fd = -1;
+  self->internal_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_BUILDING
+      && !self->detached
+      && !self->reaped_child)
+    {
+#ifdef G_OS_UNIX
+      /* Here we need to actually call waitpid() to clean up the
+       * zombie.  In case the child hasn't actually exited, defer this
+       * cleanup to the worker thread.
+       */
+      g_subprocess_unix_queue_waitpid (self);
+#endif
+      g_spawn_close_pid (self->pid);
+    }
+
+  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: (type filename): Executable path, in GLib filename encoding
+ *
+ * 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: (type filename): Executable path
+ * @...: a %NULL-terminated list of arguments, in the GLib filename encoding
+ *
+ * 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) (element-type filename): 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, any previous calls to
+ * g_subprocess_append_args(), as well as g_subprocess_set_argv0().
+ *
+ * For more information, see g_subprocess_append_args_va().
+ *
+ * It is 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_subprocess_set_argv0 (self, NULL);
+
+  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: (type filename): 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'.
+ *
+ * It is 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: (type filename): 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 is 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: (type filename): 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 is 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: %NULL-terminated list of arguments, in GLib filename encoding
+ *
+ * Append the provided @args to the child argument vector.  Each
+ * element should be in the GLib filename encoding.
+ *
+ * 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.
+ *
+ * On Windows, the filename encoding is UTF-8, so the arguments here
+ * should be as well.  For more information about how arguments are
+ * processed on Windows, see the documentation of
+ * g_spawn_async_with_pipes().
+ *
+ * It is 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 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 is 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_use_search_path:
+ * @self: a #GSubprocess
+ * @do_search_path: %TRUE if the system path should be searched
+ *
+ * By default, the name of the program must be a full path; the
+ * <envar>PATH</envar> shell variable will only be searched if you set
+ * @do_search_path to %TRUE.  If the program name is not a full path
+ * and this flag is not set, then the program will be run from the
+ * current directory (or @working_directory, if specified); this might
+ * be unexpected or even dangerous in some cases when the current
+ * directory is world-writable.
+ *
+ * This option corresponds to the %G_SPAWN_SEARCH_PATH flag.
+ *
+ * It is invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_use_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
+ *
+ * Setting @do_leave_descriptors_open to %TRUE means that the parent's
+ * open file descriptors will be inherited by the child; otherwise all
+ * descriptors except stdin/stdout/stderr will be closed before
+ * calling exec() in the child.  The default is %FALSE.
+ *
+ * Note that on Unix, this option is completely independent of file
+ * descriptors which have the close-on-exec flag set.  In other words,
+ * if you set @do_leave_descriptors_open to %FALSE, descriptors which
+ * are close-on-exec will still be closed by the operating system.
+ *
+ * This option corresponds to the %G_SPAWN_LEAVE_DESCRIPTORS_OPEN flag.
+ *
+ * It is 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;
+}
+
+/**** Environment 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 is 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 is 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);
+
+  if (self->child_envp == NULL)
+    self->child_envp = g_get_environ ();
+
+  self->child_envp = g_environ_unsetenv (self->child_envp, variable);
+}
+
+/**
+ * g_subprocess_set_environment:
+ * @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 is invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+void
+g_subprocess_set_environment (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 is 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 is 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 is 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 is 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_clear_pointer (&self->stdin_path, g_free);
+  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 is invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+#ifdef G_OS_UNIX
+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_clear_pointer (&self->stdin_path, g_free);
+  self->stdin_to_devnull = FALSE;
+  self->stdin_fd = -1;
+
+  self->stdin_fd = fd;
+}
+#endif
+
+/**
+ * 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 is 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_clear_pointer (&self->stdin_path, g_free);
+  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 is 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_clear_pointer (&self->stdin_path, g_free);
+  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 is 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 is 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_unix_fd().
+ *
+ * It is 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);
+
+  self->stdout_to_devnull = FALSE;
+  self->stdout_fd = -1;
+
+  self->stdout_to_devnull = to_devnull;
+}
+
+/**
+ * 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 is invalid to call this function after g_subprocess_start() has
+ * been called.
+ *
+ * Since: 2.34
+ */
+#ifdef G_OS_UNIX
+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);
+
+  self->stdout_to_devnull = FALSE;
+  self->stdout_fd = -1;
+
+  self->stdout_fd = fd;
+}
+#endif
+
+/**
+ * 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_to_stdout().
+ *
+ * It is 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);
+
+  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.  In this case, the disposition of standard output
+ * controls the merged stream; for example, if you combine
+ * g_subprocess_set_standard_error_to_stdout() and
+ * g_subprocess_set_standard_output_file_path(), the merged stream
+ * will be sent to the specified file.
+ *
+ * Calling this function overrides any previous calls, as well as
+ * other related functions such as
+ * g_subprocess_set_standard_error_to_devnull().
+ *
+ * It is 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);
+
+  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_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_to_devnull().
+ *
+ * It is 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);
+
+  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.  This function is equivalent to calling
+ * g_subprocess_start_with_pipes() with %NULL streams.  See the
+ * documentation of that function for more information.
+ *
+ * It is invalid to call this function after g_subprocess_start() has
+ * already been called.
+ *
+ * Returns: %TRUE if subprocess was started, %FALSE on error (and @error will be set)
+ * 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.
+ *
+ * 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 is invalid to call this function after g_subprocess_start() has
+ * already been called.
+ *
+ * Since: 2.34
+ * Returns: %TRUE if subprocess was executed successfully, %FALSE on error (and @error will be set)
+ */
+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 gboolean
+g_subprocess_unix_waitpid_dummy (gpointer data)
+{
+  return FALSE;
+}
+
+static void
+g_subprocess_unix_queue_waitpid (GSubprocess  *self)
+{
+  GMainContext *worker_context;
+  GSource *waitpid_source;
+  
+  worker_context = GLIB_PRIVATE_CALL (g_get_worker_context) ();
+  waitpid_source = g_child_watch_source_new (self->pid); 
+  g_source_set_callback (waitpid_source, g_subprocess_unix_waitpid_dummy, NULL, NULL);
+  g_source_attach (waitpid_source, worker_context);
+  g_source_unref (waitpid_source);
+}
+
+static inline void
+safe_dup2 (gint   a,
+	   gint   b)
+{
+  gint ecode;
+
+  if (a == b)
+    return;
+
+  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);
+  else if (self->internal_stdin_fd >= 0)
+    safe_dup2 (self->internal_stdin_fd, 0);
+
+  if (self->stdout_fd >= 0)
+    safe_dup2 (self->stdout_fd, 1);
+  else if (self->internal_stdout_fd >= 0)
+    safe_dup2 (self->internal_stdout_fd, 1);
+
+  if (self->stderr_fd >= 0)
+    safe_dup2 (self->stderr_fd, 2);
+  else if (self->internal_stderr_fd >= 0)
+    safe_dup2 (self->internal_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);
+}
+
+static GInputStream *
+platform_input_stream_from_spawn_fd (gint         fd)
+{
+#ifdef G_OS_UNIX
+  return g_unix_input_stream_new (fd, TRUE);
+#else
+  return g_win32_input_stream_new_from_fd (fd, TRUE);
+#endif  
+}
+
+static GOutputStream *
+platform_output_stream_from_spawn_fd (gint         fd)
+{
+#ifdef G_OS_UNIX
+  return g_unix_output_stream_new (fd, TRUE);
+#else
+  return g_win32_output_stream_new_from_fd (fd, TRUE);
+#endif  
+}
+
+/**
+ * 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.
+ *
+ * See the documentation for g_spawn_async_with_pipes() for more
+ * information about how the child process will be run.
+ *
+ * If both input and output streams are given, using synchronous I/O
+ * from the same thread on both sides risks deadlock.  Use
+ * asynchronous I/O such as g_input_stream_read_async() or higher
+ * level wrappers.
+ *
+ * The provided @cancellable controls internal input splicing.
+ * Specifically, if an input stream has been provided to the process
+ * via g_subprocess_set_standard_input_stream() or a wrapper such as
+ * g_subprocess_set_standard_input_bytes(), then cancelling
+ * @cancellable will stop I/O.  Because input I/O errors will be
+ * returned from g_subprocess_query_success(), the returned error in
+ * that case will be %G_IO_ERROR_CANCELLED.
+ *
+ * It is invalid to call this function after
+ * g_subprocess_start() has already been called.
+ *
+ * Since: 2.34
+ * Returns: %TRUE if subprocess was started, %FALSE on error (and @error will be set)
+ */
+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_to_devnull == FALSE,
+			  FALSE);
+  if (out_stderr_stream)
+    g_return_val_if_fail (self->stderr_fd == -1
+			  && self->stderr_to_devnull == FALSE
+			  && self->stderr_to_stdout == FALSE,
+			  FALSE);
+
+#ifdef G_OS_UNIX
+  if (self->stdin_path)
+    {
+      self->internal_stdin_fd = g_open (self->stdin_path, O_RDONLY);
+      if (self->internal_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;
+    }
+#else
+  if (self->stdin_path)
+    {
+      GFile *stdin_file;
+
+      stdin_file = g_file_new_for_path (self->stdin_path);
+      self->stdin_stream = (GInputStream*)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;
+    }
+#endif
+
+  g_assert (self->stdin_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;
+  self->state = G_SUBPROCESS_STATE_RUNNING;
+
+  if (stdin_pipe_fd != -1)
+    {
+      GOutputStream *child_stdout = platform_output_stream_from_spawn_fd (stdin_pipe_fd);
+      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 = platform_input_stream_from_spawn_fd (stdout_pipe_fd);
+    }
+  if (stderr_pipe_fd != -1)
+    {
+      g_assert (out_stderr_stream);
+      *out_stderr_stream = platform_input_stream_from_spawn_fd (stderr_pipe_fd);
+    }
+ out:
+  if (tmp_argv)
+    g_ptr_array_unref (tmp_argv);
+#ifdef G_OS_UNIX
+  if (self->internal_stdin_fd >= 0)
+    (void) close (self->internal_stdin_fd);
+  if (self->internal_stdout_fd >= 0)
+    (void) close (self->internal_stdout_fd);
+  if (self->internal_stderr_fd >= 0)
+    (void) close (self->internal_stderr_fd);
+#endif
+  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.
+ *
+ * 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->detached, 0);
+  g_return_val_if_fail (self->state > G_SUBPROCESS_STATE_BUILDING, 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 creates a source via g_subprocess_create_source() and
+ * attaches it the to the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link>.
+ *
+ * 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;
+  gboolean have_wnowait;
+} GSubprocessWatchTrampolineData;
+
+static void
+g_subprocess_child_watch_func (GPid       pid,
+			       gint       status_code,
+			       gpointer   user_data)
+{
+  GSubprocessWatchTrampolineData *data = user_data;
+
+  data->self->status_code = status_code;
+  data->self->state = G_SUBPROCESS_STATE_TERMINATED;
+
+  if (!data->have_wnowait)
+    data->self->reaped_child = TRUE;
+  
+  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 creates a source via g_subprocess_create_source() and
+ * attaches it the to the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link>.
+ *
+ * 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;
+
+  source = g_subprocess_create_source (self, priority, function, user_data, notify);
+  g_source_attach (source, g_main_context_get_thread_default ());
+
+  return source;
+}
+
+/**
+ * g_subprocess_create_source:
+ * @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 g_child_watch_source_new(), except the
+ * callback signature includes the subprocess @self, and status is
+ * accessed via g_subprocess_query_success().
+ *
+ * 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_create_source (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 = GLIB_PRIVATE_CALL (g_child_watch_source_new_with_flags) (self->pid, _G_CHILD_WATCH_FLAGS_WNOWAIT);
+  if (source == NULL)
+    {
+      source = g_child_watch_source_new (self->pid);
+      trampoline_data->have_wnowait = FALSE;
+    }
+  else
+    {
+      trampoline_data->have_wnowait = TRUE;
+    }
+  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);
+
+  return source;
+}
+
+/**
+ * g_subprocess_query_success:
+ * @self: a #GSubprocess
+ * @error: a #GError
+ *
+ * This function sets @error based on the exit code if the child exits
+ * abnormally (e.g. with a nonzero exit code, or via a fatal signal).
+ * This contrasts with the lower-level GLib API of g_child_watch_add()
+ * where callers must use platform-specific macros such as the Unix
+ * WIFEXITED() macro on the exit code.
+ *
+ * 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->status_code))
+    {
+      if (WEXITSTATUS (self->status_code) != 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->status_code);
+	  goto out;
+	}
+    }
+  else if (WIFSIGNALED (self->status_code))
+    {
+      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->status_code));
+      goto out;
+    }
+  else if (WIFSTOPPED (self->status_code))
+    {
+      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->status_code));
+      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->status_code != 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_status_code:
+ * @self: a #GSubprocess
+ *
+ * Returns an integer with platform-specific semantics representing
+ * the process status, in the same form as provided by
+ * g_spawn_async_with_pipes().  In the typical case where you simply
+ * want an error set 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_status_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->status_code;
+}
+
+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 abnormally.
+ * 
+ * Returns: %TRUE if child exited successfully, %FALSE on
+ *   non-successful status or @cancellable was cancelled
+ * 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;
+  GSource *cancellable_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);
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return 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);
+  cancellable_source = g_cancellable_source_new (cancellable);
+  g_source_add_child_source (source, cancellable_source);
+  g_source_unref (cancellable_source);
+
+  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;
+}
+
+/**
+ * g_subprocess_request_exit:
+ * @self: a #GSubprocess
+ *
+ * This API uses an operating-system specific mechanism to request
+ * that the subprocess gracefully exit.  This API is not available on
+ * all operating systems; for those not supported, it will do nothing
+ * and return %FALSE.  Portable code should handle this situation
+ * gracefully.  For example, if you are communicating via input or
+ * output pipe with the child, many programs will automatically exit
+ * when one of their standard input or output are closed.
+ *
+ * On Unix, this API sends %SIGTERM.
+ *
+ * A %TRUE return value does <emphasis>not</emphasis> mean the
+ * subprocess has exited, merely that an exit request was initiated.
+ * You can use g_subprocess_add_watch() to monitor the status of the
+ * process after calling this function.
+ *
+ * This function may not be used if g_subprocess_set_detached() has
+ * been called.
+ *
+ * Returns: %TRUE if the operation is supported, %FALSE otherwise.
+ */
+gboolean
+g_subprocess_request_exit (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_BUILDING, FALSE);
+
+  if (self->state == G_SUBPROCESS_STATE_TERMINATED)
+    return TRUE;
+
+#ifdef G_OS_UNIX
+  (void) kill (self->pid, SIGTERM);
+  return TRUE;
+#else
+  return FALSE;
+#endif
+}
+
+/**
+ * g_subprocess_force_exit:
+ * @self: a #GSubprocess
+ *
+ * Use an operating-system specific method to attempt an immediate,
+ * forceful termination of the process.
+ *
+ * On Unix, this function sends %SIGKILL.
+ *
+ * You can use g_subprocess_add_watch() to monitor the status of the
+ * process after calling this function.
+ *
+ * This function may not be used if g_subprocess_set_detached() has
+ * been called.
+ */
+void             
+g_subprocess_force_exit (GSubprocess       *self)
+{
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+  g_return_if_fail (!self->detached);
+  g_return_if_fail (self->state > G_SUBPROCESS_STATE_BUILDING);
+
+  if (self->state == G_SUBPROCESS_STATE_TERMINATED)
+    return;
+
+#ifdef G_OS_UNIX
+  (void) kill (self->pid, SIGKILL);
+#else
+  TerminateProcess (self->pid, 1);
+#endif
+}
+
+/**** High level wrapers ****/
+
+typedef struct {
+  GSubprocess *self;
+  gboolean caught_error;
+  GError *error;
+  GMainLoop *loop;
+  guint events_needed;
+} GSubprocessRunSyncGetOutputData;
+
+static void
+g_subprocess_on_get_output_splice_done (GObject      *obj,
+					GAsyncResult *res,
+					gpointer      user_data)
+{
+  GSubprocessRunSyncGetOutputData *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)
+    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;
+  GInputStream *subproc_stdout = NULL;
+  GInputStream *subproc_stderr = 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 = NULL;
+  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);
+
+  if (!g_subprocess_start_with_pipes (self, NULL, out_stdout_buf ? &subproc_stdout : NULL,
+				      out_stderr_buf ? &subproc_stderr : NULL,
+				      cancellable, error))
+    goto out;
+
+  if (subproc_stdout)
+    {
+      g_output_stream_splice_async (stdout_membuf, subproc_stdout, flags,
+				    self->io_priority, cancellable,
+				    g_subprocess_on_get_output_splice_done, &data);
+      g_object_unref (subproc_stdout);
+      data.events_needed++;
+    }
+
+  if (subproc_stderr)
+    {
+      g_output_stream_splice_async (stderr_membuf, subproc_stderr, flags,
+				    self->io_priority, cancellable,
+				    g_subprocess_on_get_output_splice_done, &data);
+      g_object_unref (subproc_stderr);
+      data.events_needed++;
+    }
+
+  g_main_loop_run (data.loop);
+
+  if (data.caught_error)
+    {
+      g_propagate_error (error, data.error);
+      goto out;
+    }
+
+  /* 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 while
+ * gathering output regardless of exit status, you should use the
+ * lower-level function g_subprocess_start_with_pipes().
+ *
+ * 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..e8d1cd4
--- /dev/null
+++ b/gio/gsubprocess.h
@@ -0,0 +1,251 @@
+/* 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))
+
+GLIB_AVAILABLE_IN_2_34
+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);
+
+/**** Environment control ****/
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_detached (GSubprocess     *self,
+					    gboolean         detached);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_set_use_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_set_environment (GSubprocess       *self,
+					       gchar            **new_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);
+
+#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);
+
+#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
+GSource *        g_subprocess_create_source (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_status_code (GSubprocess   *self);
+
+GLIB_AVAILABLE_IN_2_34
+gint             g_subprocess_get_unix_exit_status (GSubprocess   *self,
+						    gboolean      *out_exited,
+						    gboolean      *out_signaled,
+						    gint          *out_code);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_wait_sync (GSubprocess   *self,
+					 GCancellable  *cancellable,
+					 GError       **error);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean         g_subprocess_request_exit (GSubprocess       *self);
+
+GLIB_AVAILABLE_IN_2_34
+void             g_subprocess_force_exit (GSubprocess       *self);
+
+/**** High level wrapers ****/
+
+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..cf37991
--- /dev/null
+++ b/gio/tests/gsubprocess-testprog.c
@@ -0,0 +1,151 @@
+#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)
+{
+  GIOChannel *chan_stdin;
+  GIOChannel *chan_stdout;
+  GIOStatus status;
+  char buf[1024];
+  gsize bytes_read, bytes_written;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+
+  chan_stdin = g_io_channel_unix_new (0);
+  g_io_channel_set_encoding (chan_stdin, NULL, error);
+  g_assert_no_error (local_error);
+  chan_stdout = g_io_channel_unix_new (1);
+  g_io_channel_set_encoding (chan_stdout, NULL, error);
+  g_assert_no_error (local_error);
+
+  while (TRUE)
+    {
+      do
+	status = g_io_channel_read_chars (chan_stdin, buf, sizeof (buf),
+					  &bytes_read, error);
+      while (status == G_IO_STATUS_AGAIN);
+
+      if (status == G_IO_STATUS_EOF || status == G_IO_STATUS_ERROR)
+	break;
+
+      do
+	status = g_io_channel_write_chars (chan_stdout, buf, bytes_read,
+					   &bytes_written, error);
+      while (status == G_IO_STATUS_AGAIN);
+
+      if (status == G_IO_STATUS_EOF || status == G_IO_STATUS_ERROR)
+	break;
+    }
+
+  g_io_channel_unref (chan_stdin);
+  g_io_channel_unref (chan_stdout);
+
+  if (local_error)
+    {
+      g_printerr ("I/O error: %s\n", local_error->message);
+      g_clear_error (&local_error);
+      return 1;
+    }
+  return 0;
+}
+
+static gint
+sleep_forever_mode (int argc,
+		    char **argv)
+{
+  GMainLoop *loop;
+  
+  loop = g_main_loop_new (NULL, TRUE);
+  g_main_loop_run (loop);
+
+  return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+  GOptionContext *context;
+  GError *error = NULL;
+  const char *mode;
+
+  g_type_init ();
+
+  context = g_option_context_new ("MODE - Test GSubprocess stuff");
+  g_option_context_add_main_entries (context, options, NULL);
+  if (!g_option_context_parse (context, &argc, &argv, &error))
+    {
+      g_printerr ("%s: %s\n", argv[0], error->message);
+      return 1;
+    }
+
+  if (argc < 2)
+    {
+      g_printerr ("MODE argument required\n");
+      return 1;
+    }
+
+  mode = argv[1];
+  if (strcmp (mode, "noop") == 0)
+    return 0;
+  else if (strcmp (mode, "exit1") == 0)
+    return 1;
+  else if (strcmp (mode, "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 if (strcmp (mode, "sleep-forever") == 0)
+    return sleep_forever_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..da2d015
--- /dev/null
+++ b/gio/tests/gsubprocess.c
@@ -0,0 +1,491 @@
+#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;
+  const char *binname;
+
+  cwd = g_get_current_dir ();
+
+#ifdef G_OS_WIN32
+  binname = "gsubprocess-testprog.exe";
+#else
+  binname = "gsubprocess-testprog";
+#endif
+
+  cwd_path = g_build_filename (cwd, binname, 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);
+}
+
+static void
+test_noop_non_detached (void)
+{
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GSubprocess *proc;
+
+  proc = get_test_subprocess ("noop");
+
+  (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_use_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 scode = g_subprocess_get_status_code (proc);
+    g_assert (WIFEXITED (scode) && WEXITSTATUS (scode) == 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);
+}
+
+#ifdef G_OS_UNIX
+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);
+}
+#endif
+
+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);
+}
+
+/*
+  TODO -
+  This doesn't work because libtool eats our argv0 trick =/
+*/
+#if 0
+static void
+test_argv0 (void)
+{
+  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);
+}
+#endif
+
+static gboolean
+send_terminate (gpointer   user_data)
+{
+  GSubprocess *proc = user_data;
+
+  g_subprocess_force_exit (proc);
+
+  return FALSE;
+}
+
+static void
+on_request_quit_exited (GSubprocess   *self,
+			gpointer       user_data)
+{
+  g_main_loop_quit ((GMainLoop*)user_data);
+}
+
+static void
+test_terminate (void)
+{
+  GSubprocess *proc;
+  GError *local_error = NULL;
+  GError **error = &local_error;
+  GMainLoop *loop;
+  GSource *source;
+
+  proc = get_test_subprocess ("sleep-forever");
+
+  g_subprocess_start (proc, NULL, error);
+  g_assert_no_error (local_error);
+
+  loop = g_main_loop_new (NULL, TRUE);
+
+  source = g_subprocess_add_watch (proc, on_request_quit_exited, loop);
+  g_source_unref (source);
+
+  g_timeout_add_seconds (3, send_terminate, proc);
+
+  g_main_loop_run (loop);
+
+  (void)g_subprocess_query_success (proc, error);
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL);
+  g_clear_error (error);
+
+#ifdef G_OS_UNIX
+  {
+    int scode = g_subprocess_get_status_code (proc);
+    g_assert (WIFSIGNALED (scode) && WTERMSIG (scode) == 9);
+  }
+#endif
+
+  g_object_unref (proc);
+}
+
+int
+main (int argc, char **argv)
+{
+  g_type_init ();
+
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/gsubprocess/noop", test_noop);
+  g_test_add_func ("/gsubprocess/noop-all-to-null", test_noop_all_to_null);
+  g_test_add_func ("/gsubprocess/noop-detached", test_noop_detached);
+  g_test_add_func ("/gsubprocess/noop-nondetached", test_noop_non_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);
+#ifdef G_OS_UNIX
+  g_test_add_func ("/gsubprocess/echo-merged", test_echo_merged);
+#endif
+  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/terminate", test_terminate);
+  /* 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]