[glib: 5/7] glib/win32: implement fd passing with g_spawn_async_with_pipes_and_fds




commit 2d35c57718fb5dcad8f3ecfdecbbb614198c4f51
Author: Marc-André Lureau <marcandre lureau redhat com>
Date:   Wed Jan 26 19:41:50 2022 +0400

    glib/win32: implement fd passing with g_spawn_async_with_pipes_and_fds
    
    Signed-off-by: Marc-André Lureau <marcandre lureau redhat com>

 glib/gspawn-win32-helper.c | 73 +++++++++++++++++++++++++++++++++++++++++++++-
 glib/gspawn-win32.c        | 39 +++++++++++++++++++------
 glib/gspawn.c              |  2 ++
 3 files changed, 104 insertions(+), 10 deletions(-)
---
diff --git a/glib/gspawn-win32-helper.c b/glib/gspawn-win32-helper.c
index 1e0628558..58fd719fa 100644
--- a/glib/gspawn-win32-helper.c
+++ b/glib/gspawn-win32-helper.c
@@ -203,12 +203,14 @@ int
 main (int ignored_argc, char **ignored_argv)
 #endif
 {
+  GHashTable *fds;  /* (element-type int int) */
   int child_err_report_fd = -1;
   int helper_sync_fd = -1;
   int saved_stderr_fd = -1;
   int i;
   int fd;
   int mode;
+  int maxfd = 2;
   gintptr handle;
   int saved_errno;
   gintptr no_error = CHILD_NO_ERROR;
@@ -244,6 +246,7 @@ main (int ignored_argc, char **ignored_argv)
    * which write error messages.
    */
   child_err_report_fd = atoi (argv[ARG_CHILD_ERR_REPORT]);
+  maxfd = MAX (child_err_report_fd, maxfd);
 
   /* Hack to implement G_SPAWN_FILE_AND_ARGV_ZERO. If
    * argv[ARG_CHILD_ERR_REPORT] is suffixed with a '#' it means we get
@@ -259,6 +262,7 @@ main (int ignored_argc, char **ignored_argv)
    * from another process works only if that other process exists.
    */
   helper_sync_fd = atoi (argv[ARG_HELPER_SYNC]);
+  maxfd = MAX (helper_sync_fd, maxfd);
 
   /* argv[ARG_STDIN..ARG_STDERR] are the file descriptor numbers that
    * should be dup2'd to 0, 1 and 2. '-' if the corresponding fd
@@ -294,6 +298,8 @@ main (int ignored_argc, char **ignored_argv)
   saved_stderr_fd = reopen_noninherited (dup (2), _O_WRONLY);
   if (saved_stderr_fd == -1)
     write_err_and_exit (child_err_report_fd, CHILD_DUP_FAILED);
+
+  maxfd = MAX (saved_stderr_fd, maxfd);
   if (argv[ARG_STDERR][0] == '-')
     ; /* Nothing */
   else if (argv[ARG_STDERR][0] == 'z')
@@ -316,12 +322,76 @@ main (int ignored_argc, char **ignored_argv)
   else if (_wchdir (wargv[ARG_WORKING_DIRECTORY]) < 0)
     write_err_and_exit (child_err_report_fd, CHILD_CHDIR_FAILED);
 
+  fds = g_hash_table_new (NULL, NULL);
+  if (argv[ARG_FDS][0] != '-')
+    {
+      gchar **fdsv = g_strsplit (argv[ARG_FDS], ",", -1);
+      gsize i;
+
+      for (i = 0; fdsv[i]; i++)
+        {
+          char *endptr = NULL;
+          int sourcefd, targetfd;
+          gint64 val;
+
+          val = g_ascii_strtoll (fdsv[i], &endptr, 10);
+          g_assert (val <= G_MAXINT32);
+          sourcefd = val;
+          g_assert (endptr != fdsv[i]);
+          g_assert (*endptr == ':');
+          val = g_ascii_strtoll (endptr + 1, &endptr, 10);
+          targetfd = val;
+          g_assert (val <= G_MAXINT32);
+          g_assert (*endptr == '\0');
+
+          maxfd = MAX (maxfd, sourcefd);
+          maxfd = MAX (maxfd, targetfd);
+
+          g_hash_table_insert (fds, GINT_TO_POINTER (targetfd), GINT_TO_POINTER (sourcefd));
+        }
+
+      g_strfreev (fdsv);
+    }
+
+  maxfd++;
+  child_err_report_fd = checked_dup2 (child_err_report_fd, maxfd, child_err_report_fd, TRUE);
+  maxfd++;
+  helper_sync_fd = checked_dup2 (helper_sync_fd, maxfd, child_err_report_fd, TRUE);
+  maxfd++;
+  saved_stderr_fd = checked_dup2 (saved_stderr_fd, maxfd, child_err_report_fd, TRUE);
+
+  {
+    GHashTableIter iter;
+    gpointer sourcefd, targetfd;
+
+    g_hash_table_iter_init (&iter, fds);
+    while (g_hash_table_iter_next (&iter, &targetfd, &sourcefd))
+      {
+        /* 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 all source fds, taking care to ensure the new
+         * fds are larger than any target fd to avoid introducing new conflicts.
+         */
+        maxfd++;
+        checked_dup2 (GPOINTER_TO_INT (sourcefd), maxfd, child_err_report_fd, TRUE);
+        g_hash_table_iter_replace (&iter, GINT_TO_POINTER (maxfd));
+      }
+
+    g_hash_table_iter_init (&iter, fds);
+    while (g_hash_table_iter_next (&iter, &targetfd, &sourcefd))
+      checked_dup2 (GPOINTER_TO_INT (sourcefd), GPOINTER_TO_INT (targetfd), child_err_report_fd, TRUE);
+  }
+
+  g_hash_table_add (fds, GINT_TO_POINTER (child_err_report_fd));
+  g_hash_table_add (fds, GINT_TO_POINTER (helper_sync_fd));
+  g_hash_table_add (fds, GINT_TO_POINTER (saved_stderr_fd));
+
   /* argv[ARG_CLOSE_DESCRIPTORS] is "y" if file descriptors from 3
    *  upwards should be closed
    */
   if (argv[ARG_CLOSE_DESCRIPTORS][0] == 'y')
     for (i = 3; i < 1000; i++) /* FIXME real limit? */
-      if (i != child_err_report_fd && i != helper_sync_fd && i != saved_stderr_fd)
+      if (!g_hash_table_contains (fds, GINT_TO_POINTER (i)))
         if (_get_osfhandle (i) != -1)
           close (i);
 
@@ -379,6 +449,7 @@ main (int ignored_argc, char **ignored_argv)
 
   LocalFree (wargv);
   g_strfreev (argv);
+  g_hash_table_unref (fds);
 
   return 0;
 }
diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c
index 1c06e7c6a..02eddccf1 100644
--- a/glib/gspawn-win32.c
+++ b/glib/gspawn-win32.c
@@ -102,6 +102,7 @@ enum {
   ARG_CLOSE_DESCRIPTORS,
   ARG_USE_PATH,
   ARG_WAIT,
+  ARG_FDS,
   ARG_PROGRAM,
   ARG_COUNT = ARG_PROGRAM
 };
@@ -575,6 +576,9 @@ fork_exec (gint                  *exit_status,
            gint                   stdin_fd,
            gint                   stdout_fd,
            gint                   stderr_fd,
+           const gint            *source_fds,
+           const gint            *target_fds,
+           gsize                  n_fds,
            gint                  *err_report,
            GError               **error)
 {
@@ -635,7 +639,8 @@ fork_exec (gint                  *exit_status,
       !(flags & G_SPAWN_STDOUT_TO_DEV_NULL) &&
       !(flags & G_SPAWN_STDERR_TO_DEV_NULL) &&
       (working_directory == NULL || !*working_directory) &&
-      (flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN))
+      (flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN) &&
+      n_fds == 0)
     {
       /* We can do without the helper process */
       gboolean retval =
@@ -754,6 +759,21 @@ fork_exec (gint                  *exit_status,
   else
     new_argv[ARG_WAIT] = "w";
 
+  if (n_fds == 0)
+    new_argv[ARG_FDS] = g_strdup ("-");
+  else
+    {
+      GString *fds = g_string_new ("");
+      gsize n;
+
+      for (n = 0; n < n_fds; n++)
+        g_string_append_printf (fds, "%d:%d,", source_fds[n], target_fds[n]);
+
+      /* remove the trailing , */
+      g_string_truncate (fds, fds->len - 1);
+      new_argv[ARG_FDS] = g_string_free (fds, FALSE);
+    }
+
   for (i = 0; i <= argc; i++)
     new_argv[ARG_PROGRAM + i] = protected_argv[i];
 
@@ -780,6 +800,7 @@ fork_exec (gint                  *exit_status,
       g_strfreev (protected_argv);
       g_free (new_argv[0]);
       g_free (new_argv[ARG_WORKING_DIRECTORY]);
+      g_free (new_argv[ARG_FDS]);
       g_free (new_argv);
       g_free (helper_process);
 
@@ -795,6 +816,7 @@ fork_exec (gint                  *exit_status,
       g_strfreev (protected_argv);
       g_free (new_argv[0]);
       g_free (new_argv[ARG_WORKING_DIRECTORY]);
+      g_free (new_argv[ARG_FDS]);
       g_free (new_argv);
       g_free (helper_process);
       g_strfreev ((gchar **) wargv);
@@ -826,6 +848,7 @@ fork_exec (gint                  *exit_status,
 
   g_free (new_argv[0]);
   g_free (new_argv[ARG_WORKING_DIRECTORY]);
+  g_free (new_argv[ARG_FDS]);
   g_free (new_argv);
 
   /* Check if gspawn-win32-helper couldn't be run */
@@ -997,6 +1020,7 @@ g_spawn_sync (const gchar          *working_directory,
                   -1,
                   -1,
                   -1,
+                  NULL, NULL, 0,
                   &reportpipe,
                   error))
     return FALSE;
@@ -1221,6 +1245,7 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
                     -1,
                     -1,
                     -1,
+                    NULL, NULL, 0,
                     NULL,
                     error);
 }
@@ -1262,6 +1287,7 @@ g_spawn_async_with_fds (const gchar          *working_directory,
                     stdin_fd,
                     stdout_fd,
                     stderr_fd,
+                    NULL, NULL, 0,
                     NULL,
                     error);
 
@@ -1299,14 +1325,6 @@ g_spawn_async_with_pipes_and_fds (const gchar           *working_directory,
   g_return_val_if_fail (stdout_pipe_out == NULL || stdout_fd < 0, FALSE);
   g_return_val_if_fail (stderr_pipe_out == NULL || stderr_fd < 0, FALSE);
 
-  /* source_fds/target_fds isn’t supported on Windows at the moment. */
-  if (n_fds != 0)
-    {
-      g_set_error_literal (error, G_SPAWN_ERROR, G_SPAWN_ERROR_INVAL,
-                           "FD redirection is not supported on Windows at the moment");
-      return FALSE;
-    }
-
   return fork_exec (NULL,
                     (flags & G_SPAWN_DO_NOT_REAP_CHILD),
                     working_directory,
@@ -1322,6 +1340,9 @@ g_spawn_async_with_pipes_and_fds (const gchar           *working_directory,
                     stdin_fd,
                     stdout_fd,
                     stderr_fd,
+                    source_fds,
+                    target_fds,
+                    n_fds,
                     NULL,
                     error);
 }
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 7a2618489..60da07198 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -772,6 +772,8 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
  * any target FDs which equal @stdin_fd, @stdout_fd or @stderr_fd will overwrite
  * them in the spawned process.
  *
+ * @source_fds is supported on Windows since 2.72.
+ *
  * %G_SPAWN_FILE_AND_ARGV_ZERO means that the first element of @argv is
  * the file to execute, while the remaining elements are the actual
  * argument vector to pass to the file. Normally g_spawn_async_with_pipes()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]