Commit 0036c503 authored by Christian Hergert's avatar Christian Hergert

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
parent 36483233
......@@ -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);
}
......
/* 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
......@@ -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);
}
......@@ -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
......@@ -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,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment