[gnome-builder] terminal: asynchronously determine user shell



commit 0036c50328a91fbd4a03c34131542ef9666ff805
Author: Christian Hergert <chergert redhat com>
Date:   Wed Mar 21 14:33:35 2018 -0700

    terminal: asynchronously determine user shell
    
    This moves blocking portions of shell discovery out of the UI thread by
    setting a sensible default and asynchronously querying the host system
    during application startup.
    
    Fixes #439

 src/libide/application/ide-application.c   |   3 +
 src/libide/terminal/ide-terminal-private.h |  27 ++++++++
 src/libide/terminal/ide-terminal-util.c    | 101 +++++++++++++++++++++++++++++
 src/libide/terminal/ide-terminal-util.h    |   3 +-
 src/plugins/terminal/gb-terminal-view.c    |  73 +--------------------
 5 files changed, 134 insertions(+), 73 deletions(-)
---
diff --git a/src/libide/application/ide-application.c b/src/libide/application/ide-application.c
index 0d0716e3e..8f6380c91 100644
--- a/src/libide/application/ide-application.c
+++ b/src/libide/application/ide-application.c
@@ -37,6 +37,7 @@
 #include "application/ide-application-tests.h"
 #include "application/ide-application-tool.h"
 #include "modelines/modeline-parser.h"
+#include "terminal/ide-terminal-private.h"
 #include "threading/ide-thread-pool.h"
 #include "threading/ide-thread-private.h"
 #include "util/ide-battery-monitor.h"
@@ -531,6 +532,8 @@ ide_application_startup (GApplication *application)
   if (self->mode == IDE_APPLICATION_MODE_PRIMARY)
     ide_application_register_plugin_accessories (self);
 
+  _ide_guess_shell ();
+
   ide_application_load_addins (self);
 }
 
diff --git a/src/libide/terminal/ide-terminal-private.h b/src/libide/terminal/ide-terminal-private.h
new file mode 100644
index 000000000..4552e648f
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-private.h
@@ -0,0 +1,27 @@
+/* ide-terminal-private.h
+ *
+ * Copyright 2018 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void _ide_guess_shell (void);
+
+G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-util.c b/src/libide/terminal/ide-terminal-util.c
index 2446da967..9bb0e0ed1 100644
--- a/src/libide/terminal/ide-terminal-util.c
+++ b/src/libide/terminal/ide-terminal-util.c
@@ -23,10 +23,16 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <vte/vte.h>
 
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
+#include "terminal/ide-terminal-private.h"
 #include "terminal/ide-terminal-util.h"
 #include "util/ptyintercept.h"
 
+static const gchar *user_shell = "/bin/sh";
+
 gint
 ide_vte_pty_create_slave (VtePty *pty)
 {
@@ -40,3 +46,98 @@ ide_vte_pty_create_slave (VtePty *pty)
 
   return pty_intercept_create_slave (master_fd, TRUE);
 }
+
+/**
+ * ide_get_user_shell:
+ *
+ * Gets the user preferred shell on the host.
+ *
+ * If the background shell discovery has not yet finished due to
+ * slow or misconfigured getent on the host, this will provide a
+ * sensible fallback.
+ *
+ * Returns: (not nullable): a shell such as "/bin/sh"
+ */
+const gchar *
+ide_get_user_shell (void)
+{
+  return user_shell;
+}
+
+static void
+ide_guess_shell_communicate_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  IdeSubprocess *subprocess = (IdeSubprocess *)object;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *stdout_buf = NULL;
+
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (user_data == NULL);
+
+  if (!ide_subprocess_communicate_utf8_finish (subprocess, result, &stdout_buf, NULL, &error))
+    {
+      g_warning ("Failed to parse result from getent: %s", error->message);
+      return;
+    }
+
+  if (stdout_buf != NULL)
+    {
+      g_strstrip (stdout_buf);
+
+      if (stdout_buf[0] == '/')
+        user_shell = g_steal_pointer (&stdout_buf);
+    }
+}
+
+void
+_ide_guess_shell (void)
+{
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
+  g_autofree gchar *command = NULL;
+  g_autoptr(GError) error = NULL;
+  g_auto(GStrv) argv = NULL;
+  const gchar *shell;
+
+  /*
+   * First ask VTE to guess, so we can use that while we discover
+   * the real shell asynchronously (and possibly outside the container).
+   */
+  if ((shell = vte_get_user_shell ()))
+    user_shell = g_strdup (shell);
+
+  command = g_strdup_printf ("sh -c 'getent passwd | grep ^%s: | head -n1 | cut -f 7 -d :'",
+                             g_get_user_name ());
+
+  if (!g_shell_parse_argv (command, NULL, &argv, &error))
+    {
+      g_warning ("Failed to parse command into argv: %s", error->message);
+      return;
+    }
+
+  /*
+   * We don't use the runtime shell here, because we want to know
+   * what the host thinks the user shell should be.
+   */
+  launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+
+  ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
+  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
+  ide_subprocess_launcher_set_cwd (launcher, g_get_home_dir ());
+  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
+
+  if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
+    {
+      g_warning ("Failed to spawn getent: %s", error->message);
+      return;
+    }
+
+  ide_subprocess_communicate_utf8_async (subprocess,
+                                         NULL,
+                                         NULL,
+                                         ide_guess_shell_communicate_cb,
+                                         NULL);
+}
diff --git a/src/libide/terminal/ide-terminal-util.h b/src/libide/terminal/ide-terminal-util.h
index 47fefdfb0..c94b40d12 100644
--- a/src/libide/terminal/ide-terminal-util.h
+++ b/src/libide/terminal/ide-terminal-util.h
@@ -24,6 +24,7 @@
 
 G_BEGIN_DECLS
 
-int ide_vte_pty_create_slave (VtePty *pty);
+int          ide_vte_pty_create_slave (VtePty *pty);
+const gchar *ide_get_user_shell       (void);
 
 G_END_DECLS
diff --git a/src/plugins/terminal/gb-terminal-view.c b/src/plugins/terminal/gb-terminal-view.c
index aa69bd8d2..d3e8c8a1e 100644
--- a/src/plugins/terminal/gb-terminal-view.c
+++ b/src/plugins/terminal/gb-terminal-view.c
@@ -47,70 +47,12 @@ enum {
 };
 
 static GParamSpec *properties[LAST_PROP];
-static gchar *cached_shell;
 
 static void gb_terminal_view_connect_terminal (GbTerminalView *self,
                                                VteTerminal    *terminal);
 static void gb_terminal_respawn               (GbTerminalView *self,
                                                VteTerminal    *terminal);
 
-static gchar *
-gb_terminal_view_discover_shell (GbTerminalView  *self,
-                                 GCancellable    *cancellable,
-                                 GError         **error)
-{
-  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(IdeSubprocess) subprocess = NULL;
-  g_autofree gchar *command = NULL;
-  g_autofree gchar *stdout_buf = NULL;
-  g_auto(GStrv) argv = NULL;
-
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  if (cached_shell != NULL)
-    return g_strdup (cached_shell);
-
-  command = g_strdup_printf ("sh -c 'getent passwd | grep ^%s: | head -n1 | cut -f 7 -d :'",
-                             g_get_user_name ());
-
-  if (!g_shell_parse_argv (command, NULL, &argv, error))
-    return NULL;
-
-  /*
-   * We don't use the runtime shell here, because we want to know
-   * what the host thinks the user shell should be.
-   */
-  launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
-
-  ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
-  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
-  ide_subprocess_launcher_set_cwd (launcher, g_get_home_dir ());
-  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
-
-  subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, error);
-
-  if (subprocess == NULL)
-    return NULL;
-
-  if (!ide_subprocess_communicate_utf8 (subprocess, NULL, cancellable, &stdout_buf, NULL, error))
-    return NULL;
-
-  if (stdout_buf != NULL)
-    {
-      g_strstrip (stdout_buf);
-      if (stdout_buf[0] == '/')
-        cached_shell = g_steal_pointer (&stdout_buf);
-    }
-
-  if (cached_shell == NULL)
-    g_set_error_literal (error,
-                         G_IO_ERROR,
-                         G_IO_ERROR_FAILED,
-                         "Unknown error when discovering user shell");
-
-  return g_strdup (cached_shell);
-}
-
 static void
 gb_terminal_view_wait_cb (GObject      *object,
                           GAsyncResult *result,
@@ -245,20 +187,7 @@ gb_terminal_respawn (GbTerminalView *self,
   build_manager = ide_context_get_build_manager (context);
   pipeline = ide_build_manager_get_pipeline (build_manager);
 
-  shell = gb_terminal_view_discover_shell (self, NULL, &error);
-
-  if (shell == NULL)
-    {
-      g_warning ("Failed to discover user shell: %s", error->message);
-
-      /* We prefer bash in flatpak over sh */
-      if (ide_is_flatpak ())
-        shell = g_strdup ("/bin/bash");
-      else
-        shell = vte_get_user_shell ();
-
-      g_clear_error (&error);
-    }
+  shell = g_strdup (ide_get_user_shell ());
 
   pty = vte_terminal_pty_new_sync (terminal,
                                    VTE_PTY_DEFAULT | VTE_PTY_NO_LASTLOG | VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP,


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