diff options
author | Colin Walters <walters@verbum.org> | 2013-01-26 16:57:54 -0500 |
---|---|---|
committer | Colin Walters <walters@verbum.org> | 2013-01-26 16:59:37 -0500 |
commit | 7caa16d62721edb3800481b874613c4f989058f3 (patch) | |
tree | 599592a11739ac7a2e5ec1628f11edf3e6f938f6 | |
parent | 980fb78af5953423042bf9b426caeb72ac2b73a5 (diff) | |
download | libgsystem-7caa16d62721edb3800481b874613c4f989058f3.tar.gz |
GSSubprocess: New API to allocate a separate pipe between parent and child
This is useful when reading structured data from the child.
-rw-r--r-- | gsystem-subprocess-context-private.h | 2 | ||||
-rw-r--r-- | gsystem-subprocess-context.c | 114 | ||||
-rw-r--r-- | gsystem-subprocess-context.h | 12 | ||||
-rw-r--r-- | gsystem-subprocess.c | 41 |
4 files changed, 159 insertions, 10 deletions
diff --git a/gsystem-subprocess-context-private.h b/gsystem-subprocess-context-private.h index 18cb0ca..b51ddb4 100644 --- a/gsystem-subprocess-context-private.h +++ b/gsystem-subprocess-context-private.h @@ -53,6 +53,8 @@ struct _GSSubprocessContext gint stderr_fd; gchar *stderr_path; + GArray *pipefds; + GSpawnChildSetupFunc child_setup_func; gpointer child_setup_data; }; diff --git a/gsystem-subprocess-context.c b/gsystem-subprocess-context.c index 6d894b3..96de25b 100644 --- a/gsystem-subprocess-context.c +++ b/gsystem-subprocess-context.c @@ -22,6 +22,13 @@ #include "libgsystem.h" +#ifdef G_OS_UNIX +#include <gio/gunixoutputstream.h> +#include <gio/gfiledescriptorbased.h> +#include <gio/gunixinputstream.h> +#include <glib-unix.h> +#endif + /** * SECTION:gssubprocesscontext * @title: GSSubprocess Context @@ -153,6 +160,7 @@ gs_subprocess_context_init (GSSubprocessContext *self) self->stderr_fd = -1; self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT; self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT; + self->pipefds = g_array_new (FALSE, FALSE, sizeof (int)); } static void @@ -168,6 +176,8 @@ gs_subprocess_context_finalize (GObject *object) g_free (self->stdout_path); g_free (self->stderr_path); + g_array_unref (self->pipefds); + if (G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize != NULL) G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize (object); } @@ -241,6 +251,30 @@ gs_subprocess_context_class_init (GSSubprocessContextClass *class) g_object_class_install_properties (gobject_class, N_PROPS, gs_subprocess_context_pspecs); } +/** + * gs_subprocess_context_argv_append: + * @self: + * @arg: An argument + * + * Append an argument to the child's argument vector. + */ +void +gs_subprocess_context_argv_append (GSSubprocessContext *self, + gchar *arg) +{ + GPtrArray *new_argv = g_ptr_array_new (); + gchar **iter; + + for (iter = self->argv; *iter; iter++) + g_ptr_array_add (new_argv, *iter); + g_ptr_array_add (new_argv, arg); + g_ptr_array_add (new_argv, NULL); + + /* Don't free elements */ + g_free (self->argv); + self->argv = (char**)g_ptr_array_free (new_argv, FALSE); +} + /* Environment */ /** @@ -377,4 +411,84 @@ gs_subprocess_context_set_child_setup (GSSubprocessContext *self, self->child_setup_func = child_setup; self->child_setup_data = user_data; } + +static gboolean +open_pipe_internal (GSSubprocessContext *self, + gboolean for_read, + void **out_stream, + gint *out_fdno, + GError **error) +{ + int pipefds[2]; + + g_return_val_if_fail (out_stream != NULL, FALSE); + g_return_val_if_fail (out_fdno != NULL, FALSE); + + if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, error)) + return FALSE; + + if (for_read) + { + *out_stream = g_unix_input_stream_new (pipefds[0], TRUE); + *out_fdno = pipefds[1]; + } + else + { + *out_stream = g_unix_output_stream_new (pipefds[1], TRUE); + *out_fdno = pipefds[0]; + } + g_array_append_val (self->pipefds, *out_fdno); + + return TRUE; +} + +/** + * gs_subprocess_context_open_pipe_read: + * @self: + * @out_stream: (out) (transfer full): A newly referenced output stream + * @out_fdno: File descriptor number for the subprocess side of the pipe + * + * This allows you to open a pipe between the parent and child + * processes, independent of the standard streams. For this function, + * the pipe is set up so that the parent can read, and the child can + * write. For the opposite version, see + * gs_subprocess_context_open_pipe_write(). + * + * The returned @out_fdno is the file descriptor number that the child + * will see; you need to communicate this number via a separate + * channel, such as the argument list. For example, if you're using + * this pipe to send a password, provide + * <literal>--password-fd=<fdno string></literal>. + * + * Returns: %TRUE on success, %FALSE on error (and @error will be set) + */ +gboolean +gs_subprocess_context_open_pipe_read (GSSubprocessContext *self, + GInputStream **out_stream, + gint *out_fdno, + GError **error) +{ + return open_pipe_internal (self, TRUE, (void**)out_stream, out_fdno, error); +} + +/** + * gs_subprocess_context_open_pipe_write: + * @self: + * @out_stream: (out) (transfer full): A newly referenced stream + * @out_fdno: File descriptor number for the subprocess side of the pipe + * + * Like gs_subprocess_context_open_pipe_read(), but returns a writable + * channel from which the child process can read. + * + * Returns: %TRUE on success, %FALSE on error (and @error will be set) + */ +gboolean +gs_subprocess_context_open_pipe_write (GSSubprocessContext *self, + GOutputStream **out_stream, + gint *out_fdno, + GError **error) +{ + return open_pipe_internal (self, FALSE, (void**)out_stream, out_fdno, error); +} + #endif diff --git a/gsystem-subprocess-context.h b/gsystem-subprocess-context.h index 1f57e03..c35d491 100644 --- a/gsystem-subprocess-context.h +++ b/gsystem-subprocess-context.h @@ -63,6 +63,9 @@ GSSubprocessContext * gs_subprocess_context_new_argv0 (const gchar *argv0, gchar **argv); #endif +void gs_subprocess_context_argv_append (GSSubprocessContext *self, + gchar *arg); + /* Environment */ void gs_subprocess_context_set_environment (GSSubprocessContext *self, @@ -99,6 +102,15 @@ void gs_subprocess_context_set_stderr_file_path (GSSubprocessContext const gchar *path); void gs_subprocess_context_set_stderr_fd (GSSubprocessContext *self, gint fd); + +gboolean gs_subprocess_context_open_pipe_read (GSSubprocessContext *self, + GInputStream **out_stream, + gint *out_fdno, + GError **error); +gboolean gs_subprocess_context_open_pipe_write (GSSubprocessContext *self, + GOutputStream **out_stream, + gint *out_fdno, + GError **error); #endif /* Child setup, only available on UNIX */ diff --git a/gsystem-subprocess.c b/gsystem-subprocess.c index 383e921..27c9ad8 100644 --- a/gsystem-subprocess.c +++ b/gsystem-subprocess.c @@ -279,9 +279,10 @@ unix_open_file (const char *filename, typedef struct { - gint fds[3]; - GSpawnChildSetupFunc child_setup_func; - gpointer child_setup_data; + gint fds[3]; + GArray *pipefds; + GSpawnChildSetupFunc child_setup_func; + gpointer child_setup_data; } ChildData; static void @@ -289,6 +290,7 @@ child_setup (gpointer user_data) { ChildData *child_data = user_data; gint i; + gint result; /* We're on the child side now. "Rename" the file descriptors in * child_data.fds[] to stdin/stdout/stderr. @@ -298,14 +300,31 @@ child_setup (gpointer user_data) * have been created O_CLOEXEC. */ for (i = 0; i < 3; i++) - if (child_data->fds[i] != -1 && child_data->fds[i] != i) - { - gint result; + { + if (child_data->fds[i] != -1 && child_data->fds[i] != i) + { + do + result = dup2 (child_data->fds[i], i); + while (G_UNLIKELY (result == -1 && errno == EINTR)); + } + } - do - result = dup2 (child_data->fds[i], i); - while (result == -1 && errno == EINTR); - } + /* Unset the CLOEXEC flag on each of our pipes */ + for (i = 0; i < child_data->pipefds->len; i++) + { + int fd = g_array_index (child_data->pipefds, int, i); + int flags; + + do + flags = fcntl (fd, F_GETFL); + while (G_UNLIKELY (flags == -1 && errno == EINTR)); + + flags &= ~FD_CLOEXEC; + + do + result = fcntl (fd, F_SETFD, flags); + while (G_UNLIKELY (result == -1 && errno == EINTR)); + } if (child_data->child_setup_func) child_data->child_setup_func (child_data->child_setup_data); @@ -401,6 +420,8 @@ initable_init (GInitable *initable, else g_assert_not_reached (); + child_data.pipefds = self->context->pipefds; + if (self->context->keep_descriptors) spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN; |