[glib/wip/le-gsubprocess] gsubprocess: Support passing arbitrary fds
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/le-gsubprocess] gsubprocess: Support passing arbitrary fds
- Date: Mon, 14 Oct 2013 15:24:09 +0000 (UTC)
commit f49e04133d2675781532e9f8501bf8d1e9397b6a
Author: Colin Walters <walters verbum org>
Date: Sun Oct 13 20:38:37 2013 +0100
gsubprocess: Support passing arbitrary fds
gio/gsubprocess.c | 86 +++++++++++++++++++++++++++++++++++-
gio/gsubprocesslauncher-private.h | 3 +
gio/gsubprocesslauncher.c | 39 +++++++++++++++++
gio/gsubprocesslauncher.h | 5 ++
gio/tests/gsubprocess-testprog.c | 27 ++++++++++++
gio/tests/gsubprocess.c | 64 +++++++++++++++++++++++++++
6 files changed, 221 insertions(+), 3 deletions(-)
---
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
index 56ae921..176aba7 100644
--- a/gio/gsubprocess.c
+++ b/gio/gsubprocess.c
@@ -178,13 +178,38 @@ typedef struct
gint fds[3];
GSpawnChildSetupFunc child_setup_func;
gpointer child_setup_data;
+ GArray *basic_fd_assignments;
+ GArray *needdup_fd_assignments;
} ChildData;
static void
+unset_cloexec (int fd)
+{
+ int flags;
+ int result;
+
+ flags = fcntl (fd, F_GETFD, 0);
+
+ if (flags != -1)
+ {
+ flags &= (~FD_CLOEXEC);
+ do
+ result = fcntl (fd, F_SETFD, flags);
+ while (result == -1 && errno == EINTR);
+ }
+}
+
+/**
+ * Based on code derived from
+ * gnome-terminal:src/terminal-screen.c:terminal_screen_child_setup(),
+ * used under the LGPLv2+ with permission from author.
+ */
+static void
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.
@@ -196,13 +221,60 @@ child_setup (gpointer user_data)
for (i = 0; i < 3; i++)
if (child_data->fds[i] != -1 && child_data->fds[i] != i)
{
- gint result;
-
do
result = dup2 (child_data->fds[i], i);
while (result == -1 && errno == EINTR);
}
+ /* Basic fd assignments we can just unset FD_CLOEXEC */
+ if (child_data->basic_fd_assignments)
+ {
+ for (i = 0; i < child_data->basic_fd_assignments->len; i++)
+ {
+ gint fd = g_array_index (child_data->basic_fd_assignments, int, i);
+
+ unset_cloexec (fd);
+ }
+ }
+
+ /* If we're doing remapping fd assignments, we need to handle
+ * the case where the user has specified e.g.:
+ * 5 -> 4, 4 -> 6
+ *
+ * We do this by duping the source fds temporarily.
+ */
+ if (child_data->needdup_fd_assignments)
+ {
+ for (i = 0; i < child_data->needdup_fd_assignments->len; i += 2)
+ {
+ gint parent_fd = g_array_index (child_data->needdup_fd_assignments, int, i);
+ gint new_parent_fd;
+
+ do
+ new_parent_fd = fcntl (parent_fd, F_DUPFD_CLOEXEC, 3);
+ while (parent_fd == -1 && errno == EINTR);
+
+ g_array_index (child_data->needdup_fd_assignments, int, i) = new_parent_fd;
+ }
+ for (i = 0; i < child_data->needdup_fd_assignments->len; i += 2)
+ {
+ gint parent_fd = g_array_index (child_data->needdup_fd_assignments, int, i);
+ gint child_fd = g_array_index (child_data->needdup_fd_assignments, int, i+1);
+
+ if (parent_fd == child_fd)
+ {
+ unset_cloexec (parent_fd);
+ }
+ else
+ {
+ do
+ result = dup2 (parent_fd, child_fd);
+ while (result == -1 && errno == EINTR);
+ (void) close (parent_fd);
+ }
+ }
+ }
+
if (child_data->child_setup_func)
child_data->child_setup_func (child_data->child_setup_data);
}
@@ -319,7 +391,7 @@ initable_init (GInitable *initable,
GError **error)
{
GSubprocess *self = G_SUBPROCESS (initable);
- ChildData child_data = { { -1, -1, -1 } };
+ ChildData child_data = { { -1, -1, -1 }, 0 };
gint *pipe_ptrs[3] = { NULL, NULL, NULL };
gint pipe_fds[3] = { -1, -1, -1 };
gint close_fds[3] = { -1, -1, -1 };
@@ -398,6 +470,14 @@ initable_init (GInitable *initable,
}
#endif
+#ifdef G_OS_UNIX
+ if (self->launcher)
+ {
+ child_data.basic_fd_assignments = self->launcher->basic_fd_assignments;
+ child_data.needdup_fd_assignments = self->launcher->needdup_fd_assignments;
+ }
+#endif
+
/* argv0 has no '/' in it? We better do a PATH lookup. */
if (strchr (self->argv[0], G_DIR_SEPARATOR) == NULL)
{
diff --git a/gio/gsubprocesslauncher-private.h b/gio/gsubprocesslauncher-private.h
index 55c608e..538bf52 100644
--- a/gio/gsubprocesslauncher-private.h
+++ b/gio/gsubprocesslauncher-private.h
@@ -44,6 +44,9 @@ struct _GSubprocessLauncher
gint stderr_fd;
gchar *stderr_path;
+ GArray *basic_fd_assignments;
+ GArray *needdup_fd_assignments;
+
GSpawnChildSetupFunc child_setup_func;
gpointer child_setup_user_data;
GDestroyNotify child_setup_destroy_notify;
diff --git a/gio/gsubprocesslauncher.c b/gio/gsubprocesslauncher.c
index af12578..e8e2ad2 100644
--- a/gio/gsubprocesslauncher.c
+++ b/gio/gsubprocesslauncher.c
@@ -147,6 +147,9 @@ g_subprocess_launcher_finalize (GObject *object)
if (self->stderr_fd != -1)
close (self->stderr_fd);
+
+ g_clear_pointer (&self->basic_fd_assignments, g_array_unref);
+ g_clear_pointer (&self->needdup_fd_assignments, g_array_unref);
#endif
if (self->child_setup_destroy_notify)
@@ -163,6 +166,10 @@ g_subprocess_launcher_init (GSubprocessLauncher *self)
self->stdin_fd = -1;
self->stdout_fd = -1;
self->stderr_fd = -1;
+#ifdef G_OS_UNIX
+ self->basic_fd_assignments = g_array_new (FALSE, 0, sizeof (int));
+ self->needdup_fd_assignments = g_array_new (FALSE, 0, sizeof (int));
+#endif
}
static void
@@ -563,6 +570,38 @@ g_subprocess_launcher_take_stderr_fd (GSubprocessLauncher *self,
}
/**
+ * g_subprocess_launcher_pass_fd:
+ * @self: a #GSubprocessLauncher
+ * @source_fd: File descriptor in parent process
+ * @target_fd: Target descriptor for child process
+ *
+ * Pass an arbitrary file descriptor from parent process to
+ * the child. By default, all file descriptors from the parent
+ * will be closed. This function allows you to create (for example)
+ * a custom pipe() or socketpair() before launching the process, and
+ * choose the target descriptor in the child.
+ *
+ * An example use case is GNUPG, which has a command line argument
+ * --passphrase-fd providing a file descriptor number where it expects
+ * the passphrase to be written.
+ */
+void
+g_subprocess_launcher_pass_fd (GSubprocessLauncher *self,
+ gint source_fd,
+ gint target_fd)
+{
+ if (source_fd == target_fd)
+ {
+ g_array_append_val (self->basic_fd_assignments, source_fd);
+ }
+ else
+ {
+ g_array_append_val (self->needdup_fd_assignments, source_fd);
+ g_array_append_val (self->needdup_fd_assignments, target_fd);
+ }
+}
+
+/**
* g_subprocess_launcher_set_child_setup:
* @self: a #GSubprocessLauncher
* @child_setup: a #GSpawnChildSetupFunc to use as the child setup function
diff --git a/gio/gsubprocesslauncher.h b/gio/gsubprocesslauncher.h
index 2ba878c..9a11b3c 100644
--- a/gio/gsubprocesslauncher.h
+++ b/gio/gsubprocesslauncher.h
@@ -100,6 +100,11 @@ GLIB_AVAILABLE_IN_2_40
void g_subprocess_launcher_take_stderr_fd (GSubprocessLauncher *self,
gint fd);
+GLIB_AVAILABLE_IN_2_40
+void g_subprocess_launcher_pass_fd (GSubprocessLauncher *self,
+ gint source_fd,
+ gint target_fd);
+
/* Child setup, only available on UNIX */
GLIB_AVAILABLE_IN_2_40
void g_subprocess_launcher_set_child_setup (GSubprocessLauncher *self,
diff --git a/gio/tests/gsubprocess-testprog.c b/gio/tests/gsubprocess-testprog.c
index 3d8e5cc..e0903dd 100644
--- a/gio/tests/gsubprocess-testprog.c
+++ b/gio/tests/gsubprocess-testprog.c
@@ -1,6 +1,7 @@
#include <gio/gio.h>
#include <string.h>
#include <stdio.h>
+#include <stdlib.h>
#include <errno.h>
#ifdef G_OS_UNIX
#include <gio/gunixinputstream.h>
@@ -121,6 +122,30 @@ sleep_forever_mode (int argc,
return 0;
}
+static int
+write_to_fds (int argc, char **argv)
+{
+ int i;
+
+ for (i = 2; i < argc; i++)
+ {
+ int fd = atoi (argv[i]);
+ FILE *f = fdopen (fd, "w");
+ const char buf[] = "hello world\n";
+ size_t bytes_written;
+
+ g_assert (f != NULL);
+
+ bytes_written = fwrite (buf, 1, sizeof (buf), f);
+ g_assert (bytes_written == sizeof (buf));
+
+ if (fclose (f) == -1)
+ g_assert_not_reached ();
+ }
+
+ return 0;
+}
+
int
main (int argc, char **argv)
{
@@ -162,6 +187,8 @@ main (int argc, char **argv)
return cat_mode (argc, argv);
else if (strcmp (mode, "sleep-forever") == 0)
return sleep_forever_mode (argc, argv);
+ else if (strcmp (mode, "write-to-fds") == 0)
+ return write_to_fds (argc, argv);
else
{
g_printerr ("Unknown MODE %s\n", argv[1]);
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
index b881fcc..63cda10 100644
--- a/gio/tests/gsubprocess.c
+++ b/gio/tests/gsubprocess.c
@@ -3,6 +3,8 @@
#ifdef G_OS_UNIX
#include <sys/wait.h>
+#include <glib-unix.h>
+#include <gio/gunixinputstream.h>
#include <gio/gfiledescriptorbased.h>
#endif
@@ -758,6 +760,67 @@ test_child_setup (void)
(void) g_file_delete (tmpfile, NULL, NULL);
}
+
+static void
+test_pass_fd (void)
+{
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ GInputStream *child_input;
+ GDataInputStream *child_datainput;
+ GSubprocessLauncher *launcher;
+ GSubprocess *proc;
+ GPtrArray *args;
+ int basic_pipefds[2];
+ int needdup_pipefds[2];
+ char *buf;
+ gsize len;
+ char *basic_fd_str;
+ char *needdup_fd_str;
+
+ g_unix_open_pipe (basic_pipefds, FD_CLOEXEC, error);
+ g_assert_no_error (local_error);
+ g_unix_open_pipe (needdup_pipefds, FD_CLOEXEC, error);
+ g_assert_no_error (local_error);
+
+ basic_fd_str = g_strdup_printf ("%d", basic_pipefds[1]);
+ needdup_fd_str = g_strdup_printf ("%d", needdup_pipefds[1] + 1);
+
+ args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL);
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+ g_subprocess_launcher_pass_fd (launcher, basic_pipefds[1], basic_pipefds[1]);
+ g_subprocess_launcher_pass_fd (launcher, needdup_pipefds[1], needdup_pipefds[1] + 1);
+ proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error);
+ g_ptr_array_free (args, TRUE);
+ g_assert_no_error (local_error);
+
+ (void) close (basic_pipefds[1]);
+ (void) close (needdup_pipefds[1]);
+ g_free (basic_fd_str);
+ g_free (needdup_fd_str);
+
+ child_input = g_unix_input_stream_new (basic_pipefds[0], TRUE);
+ child_datainput = g_data_input_stream_new (child_input);
+ buf = g_data_input_stream_read_line_utf8 (child_datainput, &len, NULL, error);
+ g_assert_no_error (local_error);
+ g_assert_cmpstr (buf, ==, "hello world");
+ g_object_unref (child_datainput);
+ g_object_unref (child_input);
+ g_free (buf);
+
+ child_input = g_unix_input_stream_new (needdup_pipefds[0], TRUE);
+ child_datainput = g_data_input_stream_new (child_input);
+ buf = g_data_input_stream_read_line_utf8 (child_datainput, &len, NULL, error);
+ g_assert_no_error (local_error);
+ g_assert_cmpstr (buf, ==, "hello world");
+ g_free (buf);
+ g_object_unref (child_datainput);
+ g_object_unref (child_input);
+
+ g_object_unref (launcher);
+ g_object_unref (proc);
+}
+
#endif
int
@@ -785,6 +848,7 @@ main (int argc, char **argv)
g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file);
g_test_add_func ("/gsubprocess/stdout-fd", test_stdout_fd);
g_test_add_func ("/gsubprocess/child-setup", test_child_setup);
+ g_test_add_func ("/gsubprocess/pass-fd", test_pass_fd);
#endif
return g_test_run ();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]