[gnome-terminal/gsettings] server: Implement FD passing



commit 7ccc102d6a1da08fe3314935795d35687a8fe475
Author: Christian Persch <chpe gnome org>
Date:   Mon Apr 16 12:50:38 2012 +0200

    server: Implement FD passing
    
    https://bugzilla.gnome.org/show_bug.cgi?id=81560

 configure.ac          |    2 +-
 src/client.c          |   11 ++++
 src/terminal-gdbus.c  |   62 ++++++++++++++++++++----
 src/terminal-screen.c |  128 +++++++++++++++++++++++++++++++++++++++++++++----
 src/terminal-screen.h |    2 +
 5 files changed, 185 insertions(+), 20 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 063f916..9002144 100644
--- a/configure.ac
+++ b/configure.ac
@@ -56,7 +56,7 @@ case "$with_gtk" in
   3.0) GTK_API_VERSION=3.0
        GTK_REQUIRED=3.4.0
        VTE_PC_VERSION=-2.90
-       VTE_REQUIRED=0.27.3
+       VTE_REQUIRED=0.32.1
        ;;
 esac
 
diff --git a/src/client.c b/src/client.c
index 3f96b44..4595515 100644
--- a/src/client.c
+++ b/src/client.c
@@ -280,6 +280,17 @@ option_fd_cb (const gchar *option_name,
     g_assert_not_reached ();
   }
 
+#if 1
+  if (fd == STDIN_FILENO ||
+      fd == STDOUT_FILENO ||
+      fd == STDERR_FILENO) {
+    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                 "FD passing of std%s is not supported",
+                 fd == STDIN_FILENO ? "in" : fd == STDOUT_FILENO ? "out" : "err");
+    return FALSE;
+  }
+#endif
+
   if (data->fd_list == NULL) {
     data->fd_list = g_unix_fd_list_new ();
     data->fd_array = g_array_new (FALSE, FALSE, sizeof (PassFdElement));
diff --git a/src/terminal-gdbus.c b/src/terminal-gdbus.c
index f7f1966..9e1ecf0 100644
--- a/src/terminal-gdbus.c
+++ b/src/terminal-gdbus.c
@@ -20,6 +20,9 @@
 
 #include "terminal-gdbus.h"
 
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
 #include "terminal-app.h"
 #include "terminal-debug.h"
 #include "terminal-defines.h"
@@ -88,17 +91,17 @@ terminal_receiver_impl_set_screen (TerminalReceiverImpl *impl,
 
 static gboolean 
 terminal_receiver_impl_exec (TerminalReceiver *receiver,
-                          GDBusMethodInvocation *invocation,
-                          GUnixFDList *fd_list,
-                          GVariant *options,
-                          GVariant *arguments)
+                             GDBusMethodInvocation *invocation,
+                             GUnixFDList *fd_list,
+                             GVariant *options,
+                             GVariant *arguments)
 {
   TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (receiver);
   TerminalReceiverImplPrivate *priv = impl->priv;
   const char *working_directory;
   char **exec_argv, **envv;
   gsize exec_argc;
-  GVariantIter *fd_iter;
+  GVariant *fd_array;
   GError *error;
 
   if (priv->screen == NULL) {
@@ -113,8 +116,48 @@ terminal_receiver_impl_exec (TerminalReceiver *receiver,
     working_directory = NULL;
   if (!g_variant_lookup (options, "environ", "^a&ay", &envv))
     envv = NULL;
-  if (!g_variant_lookup (options, "fd-set", "a(ih)", &fd_iter))
-    fd_iter = NULL;
+
+  if (!g_variant_lookup (options, "fd-set", "@a(ih)", &fd_array))
+    fd_array = NULL;
+
+  /* Check FD passing */
+  if ((fd_list != NULL) ^ (fd_array != NULL)) {
+    g_dbus_method_invocation_return_error_literal (invocation,
+                                                   G_DBUS_ERROR,
+                                                   G_DBUS_ERROR_INVALID_ARGS,
+                                                   "Must pass both fd-set options and a FD list");
+    goto out;
+  }
+  if (fd_list != NULL && fd_array != NULL) {
+    const int *fd_array_data;
+    gsize fd_array_data_len, i;
+    int n_fds;
+
+    fd_array_data = g_variant_get_fixed_array (fd_array, &fd_array_data_len, 2 * sizeof (int));
+    n_fds = g_unix_fd_list_get_length (fd_list);
+    for (i = 0; i < fd_array_data_len; i++) {
+      const int fd = fd_array_data[2 * i];
+      const int idx = fd_array_data[2 * i + 1];
+
+      if (fd == STDIN_FILENO ||
+          fd == STDOUT_FILENO ||
+          fd == STDERR_FILENO) {
+        g_dbus_method_invocation_return_error (invocation,
+                                               G_DBUS_ERROR,
+                                               G_DBUS_ERROR_INVALID_ARGS,
+                                               "Passing of std%s not supported",
+                                               fd == STDIN_FILENO ? "in" : fd == STDOUT_FILENO ? "out" : "err");
+        goto out;
+      }
+      if (idx < 0 || idx >= n_fds) {
+        g_dbus_method_invocation_return_error_literal (invocation,
+                                                       G_DBUS_ERROR,
+                                                       G_DBUS_ERROR_INVALID_ARGS,
+                                                       "Handle out of range");
+        goto out;
+      }
+    }
+  }
 
   if (working_directory != NULL)
     _terminal_debug_print (TERMINAL_DEBUG_FACTORY,
@@ -127,6 +170,7 @@ terminal_receiver_impl_exec (TerminalReceiver *receiver,
                              exec_argc > 0 ? exec_argv : NULL,
                              envv,
                              working_directory,
+                             fd_list, fd_array,
                              &error)) {
     g_dbus_method_invocation_take_error (invocation, error);
   } else {
@@ -135,8 +179,8 @@ terminal_receiver_impl_exec (TerminalReceiver *receiver,
 
   g_free (exec_argv);
   g_free (envv);
-  if (fd_iter)
-    g_variant_iter_free (fd_iter);
+  if (fd_array)
+    g_variant_unref (fd_array);
 
 out:
 
diff --git a/src/terminal-screen.c b/src/terminal-screen.c
index 9afad16..09050be 100644
--- a/src/terminal-screen.c
+++ b/src/terminal-screen.c
@@ -17,11 +17,20 @@
  */
 
 #include <config.h>
+#define _GNU_SOURCE /* for dup3 */
 
+#include "terminal-screen.h"
+
+#include <errno.h>
 #include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/wait.h>
+#include <fcntl.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
 
 #include <gtk/gtk.h>
 
@@ -45,6 +54,12 @@
 
 #define URL_MATCH_CURSOR  (GDK_HAND2)
 
+typedef struct {
+  GUnixFDList *fd_list;
+  int *fd_array;
+  gsize fd_array_len;
+} FDSetupData;
+
 typedef struct
 {
   int tag;
@@ -116,7 +131,9 @@ static void terminal_screen_change_font (TerminalScreen *screen);
 static gboolean terminal_screen_popup_menu (GtkWidget *widget);
 static gboolean terminal_screen_button_press (GtkWidget *widget,
                                               GdkEventButton *event);
-static gboolean terminal_screen_do_exec (TerminalScreen *screen, GError **error);
+static gboolean terminal_screen_do_exec (TerminalScreen *screen,
+                                         FDSetupData    *data,
+                                         GError **error);
 static void terminal_screen_child_exited  (VteTerminal *terminal);
 
 static void terminal_screen_window_title_changed      (VteTerminal *vte_terminal,
@@ -684,9 +701,12 @@ terminal_screen_exec (TerminalScreen *screen,
                       char          **argv,
                       char          **envv,
                       const char     *cwd,
+                      GUnixFDList    *fd_list,
+                      GVariant       *fd_array,
                       GError        **error)
 {
   TerminalScreenPrivate *priv;
+  FDSetupData *data;
 
   g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE);
   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
@@ -701,7 +721,17 @@ terminal_screen_exec (TerminalScreen *screen,
   g_free (priv->initial_working_directory);
   priv->initial_working_directory = g_strdup (cwd);
 
-  return terminal_screen_do_exec (screen, error);
+  if (fd_list) {
+    const int *fd_array_data;
+
+    data = g_new (FDSetupData, 1);
+    data->fd_list = fd_list;
+    fd_array_data = g_variant_get_fixed_array (fd_array, &data->fd_array_len, 2 * sizeof (int));
+    data->fd_array = g_memdup (fd_array_data, data->fd_array_len * 2 * sizeof (int));
+  } else
+    data = NULL;
+
+  return terminal_screen_do_exec (screen, data, error);
 }
 
 const char*
@@ -1344,9 +1374,83 @@ info_bar_response_cb (GtkWidget *info_bar,
   }
 }
 
+static void
+free_fd_setup_data (FDSetupData *data)
+{
+  g_free (data->fd_array);
+  g_free (data);
+}
+
+static void
+terminal_screen_child_setup (FDSetupData *data)
+{
+  int *fd_array = data->fd_array;
+  gsize fd_array_len = data->fd_array_len;
+  gsize i;
+  int *fds, n_fds, j, r;
+
+  /* At this point, vte_pty_child_setup() has been called,
+   * so all FDs are FD_CLOEXEC.
+   */
+
+  if (fd_array_len == 0)
+    return;
+
+  fds = g_unix_fd_list_steal_fds (data->fd_list, &n_fds);
+
+  for (i = 0; i < fd_array_len; i++) {
+    int target_fd = fd_array[2 * i];
+    int idx = fd_array[2 * i + 1];
+    int fd;
+
+    /* We want to move fds[idx] to target_fd */
+
+    if (target_fd == fds[idx])
+      goto next; /* noting to do */
+
+    /* First need to check if @target_fd is one of the FDs in the FD list! */
+    for (j = 0; j < n_fds; j++) {
+      if (fds[j] == target_fd) {
+        do {
+          fd = dup (fds[j]);
+        } while (fd == -1 && errno == EINTR);
+        if (fd == -1)
+          _exit (127);
+        fds[j] = fd;
+        
+        /* Need to mark the dup'd FD as FD_CLOEXEC */
+        do {
+          r = fcntl (fd, F_SETFD, FD_CLOEXEC);
+        } while (r == -1 && errno == EINTR);
+        if (r == -1)
+          _exit (127);
+
+        break;
+      }
+    }
+
+    /* Check again */
+    if (target_fd == fds[idx])
+      goto next; /* noting to do */
+
+    /* Now we know that target_fd can be safely overwritten. */
+    errno = 0;
+    do {
+      fd = dup3 (fds[idx], target_fd, 0 /* no FD_CLOEXEC */);
+    } while (fd == -1 && errno == EINTR);
+    if (fd != target_fd)
+      _exit (127);
+
+  next:
+    /* Don't need to close it here since it's FD_CLOEXEC. */
+    fds[idx] = -1;
+  }
+}
+
 static gboolean
 terminal_screen_do_exec (TerminalScreen *screen,
-                         GError **error)
+                         FDSetupData    *data /* adopting */,
+                         GError        **error)
 {
   TerminalScreenPrivate *priv = screen->priv;
   VteTerminal *terminal = VTE_TERMINAL (screen);
@@ -1358,6 +1462,7 @@ terminal_screen_do_exec (TerminalScreen *screen,
   VtePtyFlags pty_flags = VTE_PTY_DEFAULT;
   GSpawnFlags spawn_flags = 0;
   GPid pid;
+  gboolean result = FALSE;
 
   if (priv->child_pid != -1) {
     g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
@@ -1385,6 +1490,7 @@ terminal_screen_do_exec (TerminalScreen *screen,
   if (!g_settings_get_boolean (profile, TERMINAL_PROFILE_UPDATE_RECORDS_KEY))
     pty_flags |= VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP;
 
+  argv = NULL;
   if (!get_child_command (screen, shell, &spawn_flags, &argv, &err) ||
       !vte_terminal_fork_command_full (terminal,
                                        pty_flags,
@@ -1392,7 +1498,8 @@ terminal_screen_do_exec (TerminalScreen *screen,
                                        argv,
                                        env,
                                        spawn_flags,
-                                       NULL, NULL,
+                                       (GSpawnChildSetupFunc) (data ? terminal_screen_child_setup : NULL), 
+                                       data,
                                        &pid,
                                        &err)) {
     GtkWidget *info_bar;
@@ -1413,27 +1520,28 @@ terminal_screen_do_exec (TerminalScreen *screen,
     gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL);
     gtk_widget_show (info_bar);
 
-    g_strfreev (env);
-    g_free (shell);
-
     g_propagate_error (error, err);
-    return FALSE;
+    goto out;
   }
 
   priv->child_pid = pid;
   priv->pty_fd = vte_terminal_get_pty (terminal);
 
+  result = TRUE;
+
+out:
   g_free (shell);
   g_strfreev (argv);
   g_strfreev (env);
+  free_fd_setup_data (data);
 
-  return TRUE;
+  return result;
 }
 
 static gboolean
 terminal_screen_launch_child_cb (TerminalScreen *screen)
 {
-  terminal_screen_do_exec (screen, NULL /* don't care */);
+  terminal_screen_do_exec (screen, NULL, NULL /* don't care */);
   return FALSE; /* don't run again */
 }
 
diff --git a/src/terminal-screen.h b/src/terminal-screen.h
index bba7d8a..fd2b56f 100644
--- a/src/terminal-screen.h
+++ b/src/terminal-screen.h
@@ -85,6 +85,8 @@ gboolean terminal_screen_exec (TerminalScreen *screen,
                                char          **argv,
                                char          **envv,
                                const char     *cwd,
+                               GUnixFDList    *fd_list,
+                               GVariant       *fd_array,
                                GError        **error);
 
 void _terminal_screen_launch_child_on_idle (TerminalScreen *screen);



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