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