[glib/wip/le-gsubprocess] gsubprocess: Support passing arbitrary fds



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]