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