cli: merge branch 'bg/cli-readline-async-rh1368353'

https://bugzilla.gnome.org/show_bug.cgi?id=732097
https://bugzilla.redhat.com/show_bug.cgi?id=1368353
This commit is contained in:
Beniamino Galvani 2016-09-23 09:47:13 +02:00
commit 36f561ead0
4 changed files with 98 additions and 219 deletions

View file

@ -33,6 +33,8 @@
#include "common.h"
#include "utils.h"
extern GMainLoop *loop;
/* Available fields for IPv4 group */
NmcOutputField nmc_fields_ip4_config[] = {
{"GROUP", N_("GROUP")}, /* 0 */
@ -1159,6 +1161,11 @@ nmc_unique_connection_name (const GPtrArray *connections, const char *try_name)
return new_name;
}
/* readline state variables */
static gboolean nmcli_in_readline = FALSE;
static gboolean rl_got_line;
static char *rl_string;
/**
* nmc_cleanup_readline:
*
@ -1172,88 +1179,94 @@ nmc_cleanup_readline (void)
rl_cleanup_after_signal ();
}
static gboolean nmcli_in_readline = FALSE;
static pthread_mutex_t readline_mutex = PTHREAD_MUTEX_INITIALIZER;
gboolean
nmc_get_in_readline (void)
{
gboolean in_readline;
pthread_mutex_lock (&readline_mutex);
in_readline = nmcli_in_readline;
pthread_mutex_unlock (&readline_mutex);
return in_readline;
return nmcli_in_readline;
}
void
nmc_set_in_readline (gboolean in_readline)
{
pthread_mutex_lock (&readline_mutex);
nmcli_in_readline = in_readline;
pthread_mutex_unlock (&readline_mutex);
}
static void
readline_cb (char *line)
{
rl_got_line = TRUE;
rl_string = line;
rl_callback_handler_remove ();
}
static gboolean
stdin_ready_cb (GIOChannel * io, GIOCondition condition, gpointer data)
{
rl_callback_read_char ();
return TRUE;
}
static char *
nmc_readline_helper (const char *prompt)
{
char *str;
int b;
GIOChannel *io = NULL;
guint io_watch_id;
readline_mark:
/* We are in readline -> Ctrl-C should not quit nmcli */
nmc_set_in_readline (TRUE);
str = readline (prompt);
/* We are outside readline -> Ctrl-C should quit nmcli */
nmc_set_in_readline (FALSE);
/* Check for an I/O error by attempting to peek into the input buffer.
* Readline just inserts newlines when errors occur so we need to check ourselves. */
if (ioctl (0, FIONREAD, &b) == -1) {
g_free (str);
str = NULL;
io = g_io_channel_unix_new (STDIN_FILENO);
io_watch_id = g_io_add_watch (io, G_IO_IN, stdin_ready_cb, NULL);
g_io_channel_unref (io);
read_again:
rl_string = NULL;
rl_got_line = FALSE;
rl_callback_handler_install (prompt, readline_cb);
while ( !rl_got_line
&& g_main_loop_is_running (loop)
&& !nmc_seen_sigint ())
g_main_context_iteration (NULL, TRUE);
/* If Ctrl-C was detected, complete the line */
if (nmc_seen_sigint ()) {
rl_echo_signal_char (SIGINT);
rl_stuff_char ('\n');
rl_callback_read_char ();
}
/* Add string to the history */
if (str && *str)
add_history (str);
if (rl_string && *rl_string)
add_history (rl_string);
/*-- React on Ctrl-C and Ctrl-D --*/
/* We quit on Ctrl-D when line is empty */
if (str == NULL) {
/* Send SIGQUIT to itself */
nmc_set_sigquit_internal ();
kill (getpid (), SIGQUIT);
/* Sleep in this thread so that we don't do anything else until exit */
for (;;)
sleep (3);
}
/* Ctrl-C */
if (nmc_seen_sigint ()) {
/* Ctrl-C */
nmc_clear_sigint ();
if (nm_cli.in_editor || *str) {
if ( nm_cli.in_editor
|| (rl_string && *rl_string)) {
/* In editor, or the line is not empty */
/* Call readline again to get new prompt (repeat) */
g_free (str);
goto readline_mark;
g_free (rl_string);
goto read_again;
} else {
/* Not in editor and line is empty */
/* Send SIGQUIT to itself */
nmc_set_sigquit_internal ();
kill (getpid (), SIGQUIT);
/* Sleep in this thread so that we don't do anything else until exit */
for (;;)
sleep (3);
/* Not in editor and line is empty, exit */
nmc_exit ();
}
} else if (!rl_string) {
/* Ctrl-D, exit */
nmc_exit ();
}
/* Return NULL, not empty string */
if (str && *str == '\0') {
g_free (str);
str = NULL;
if (rl_string && *rl_string == '\0') {
g_free (rl_string);
rl_string = NULL;
}
return str;
g_source_remove (io_watch_id);
nmc_set_in_readline (FALSE);
return rl_string;
}
/**

View file

@ -6278,8 +6278,6 @@ typedef struct {
static gboolean nmc_editor_cb_called;
static GError *nmc_editor_error;
static MonitorACInfo *nmc_editor_monitor_ac;
static GMutex nmc_editor_mutex;
static GCond nmc_editor_cond;
/*
* Store 'error' to shared 'nmc_editor_error' and monitoring info to
@ -6289,12 +6287,9 @@ static GCond nmc_editor_cond;
static void
set_info_and_signal_editor_thread (GError *error, MonitorACInfo *monitor_ac_info)
{
g_mutex_lock (&nmc_editor_mutex);
nmc_editor_cb_called = TRUE;
nmc_editor_error = error ? g_error_copy (error) : NULL;
nmc_editor_monitor_ac = monitor_ac_info;
g_cond_signal (&nmc_editor_cond);
g_mutex_unlock (&nmc_editor_mutex);
}
static void
@ -6358,6 +6353,7 @@ progress_activation_editor_cb (gpointer user_data)
return TRUE;
finish:
info->monitor_id = 0;
if (device)
g_object_unref (device);
if (ac)
@ -6385,7 +6381,7 @@ activate_connection_editor_cb (GObject *client,
device = ac_devs->len > 0 ? g_ptr_array_index (ac_devs, 0) : NULL;
}
if (device) {
monitor_ac_info = g_malloc0 (sizeof (AddConnectionInfo));
monitor_ac_info = g_malloc0 (sizeof (MonitorACInfo));
monitor_ac_info->device = g_object_ref (device);
monitor_ac_info->ac = active;
monitor_ac_info->monitor_id = g_timeout_add (120, progress_activation_editor_cb, monitor_ac_info);
@ -7437,10 +7433,9 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
update_connection (persistent, rem_con, update_connection_editor_cb, NULL);
}
g_mutex_lock (&nmc_editor_mutex);
//FIXME: add also a timeout for cases the callback is not called
while (!nmc_editor_cb_called)
g_cond_wait (&nmc_editor_cond, &nmc_editor_mutex);
g_main_context_iteration (NULL, TRUE);
if (nmc_editor_error) {
g_print (_("Error: Failed to save '%s' (%s) connection: %s\n"),
@ -7482,7 +7477,6 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
nmc_editor_cb_called = FALSE;
nmc_editor_error = NULL;
g_mutex_unlock (&nmc_editor_mutex);
} else {
g_print (_("Error: connection verification failed: %s\n"),
err1 ? err1->message : _("(unknown error)"));
@ -7527,9 +7521,8 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
break;
}
g_mutex_lock (&nmc_editor_mutex);
while (!nmc_editor_cb_called)
g_cond_wait (&nmc_editor_cond, &nmc_editor_mutex);
g_main_context_iteration (NULL, TRUE);
if (nmc_editor_error) {
g_print (_("Error: Failed to activate '%s' (%s) connection: %s\n"),
@ -7538,8 +7531,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
nmc_editor_error->message);
g_error_free (nmc_editor_error);
} else {
g_print (_("Monitoring connection activation (press any key to continue)\n"));
nmc_get_user_input ("");
nmc_readline (_("Monitoring connection activation (press any key to continue)\n"));
}
if (nmc_editor_monitor_ac) {
@ -7550,7 +7542,6 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
nmc_editor_cb_called = FALSE;
nmc_editor_error = NULL;
nmc_editor_monitor_ac = NULL;
g_mutex_unlock (&nmc_editor_mutex);
/* Update timestamp in local connection */
update_connection_timestamp (NM_CONNECTION (rem_con), connection);
@ -7768,7 +7759,7 @@ editor_init_existing_connection (NMConnection *connection)
}
static NMCResultCode
do_connection_edit_func (NmCli *nmc, int argc, char **argv)
do_connection_edit (NmCli *nmc, int argc, char **argv)
{
NMConnection *connection = NULL;
NMSettingConnection *s_con;
@ -7942,55 +7933,12 @@ do_connection_edit_func (NmCli *nmc, int argc, char **argv)
g_object_unref (connection);
g_free (nmc_tab_completion.con_type);
nmc->should_wait++;
return nmc->return_value;
error:
g_assert (!connection);
g_free (type_ask);
nmc->should_wait++;
return nmc->return_value;
}
typedef struct {
NmCli *nmc;
int argc;
char **argv;
} NmcEditorThreadData;
static GThread *editor_thread;
static NmcEditorThreadData editor_thread_data;
/*
* We need to run do_connection_edit_func() in a thread so that
* glib main loop is not blocked and could receive and process D-Bus
* return messages.
*/
static gpointer
connection_editor_thread_func (gpointer data)
{
NmcEditorThreadData *td = (NmcEditorThreadData *) data;
/* run editor for editing/adding connections */
td->nmc->return_value = do_connection_edit_func (td->nmc, td->argc, td->argv);
/* quit glib main loop now that we are done with this thread */
quit ();
return NULL;
}
static NMCResultCode
do_connection_edit (NmCli *nmc, int argc, char **argv)
{
nmc->should_wait++;
editor_thread_data.nmc = nmc;
editor_thread_data.argc = argc;
editor_thread_data.argv = argv;
editor_thread = g_thread_new ("editor-thread", connection_editor_thread_func, &editor_thread_data);
g_thread_unref (editor_thread);
return nmc->return_value;
}

View file

@ -25,10 +25,10 @@
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <termios.h>
#include <unistd.h>
#include <locale.h>
#include <glib-unix.h>
#include <readline/readline.h>
#include <readline/history.h>
@ -61,7 +61,6 @@ typedef struct {
/* --- Global variables --- */
GMainLoop *loop = NULL;
static sigset_t signal_set;
struct termios termios_orig;
static void
@ -385,124 +384,50 @@ parse_command_line (NmCli *nmc, int argc, char **argv)
}
static gboolean nmcli_sigint = FALSE;
static pthread_mutex_t sigint_mutex = PTHREAD_MUTEX_INITIALIZER;
static gboolean nmcli_sigquit_internal = FALSE;
gboolean
nmc_seen_sigint (void)
{
gboolean sigint;
pthread_mutex_lock (&sigint_mutex);
sigint = nmcli_sigint;
pthread_mutex_unlock (&sigint_mutex);
return sigint;
return nmcli_sigint;
}
void
nmc_clear_sigint (void)
{
pthread_mutex_lock (&sigint_mutex);
nmcli_sigint = FALSE;
pthread_mutex_unlock (&sigint_mutex);
}
void
nmc_set_sigquit_internal (void)
void nmc_exit (void)
{
nmcli_sigquit_internal = TRUE;
tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig);
nmc_cleanup_readline ();
exit (1);
}
static int
event_hook_for_readline (void)
{
/* Make readline() exit on SIGINT */
if (nmc_seen_sigint ()) {
rl_echo_signal_char (SIGINT);
rl_stuff_char ('\n');
}
return 0;
}
void *signal_handling_thread (void *arg);
/*
* Thread function waiting for signals and processing them.
* Wait for signals in signal set. The semantics of sigwait() require that all
* threads (including the thread calling sigwait()) have the signal masked, for
* reliable operation. Otherwise, a signal that arrives while this thread is
* not blocked in sigwait() might be delivered to another thread.
*/
void *
signal_handling_thread (void *arg) {
int signo;
while (1) {
sigwait (&signal_set, &signo);
switch (signo) {
case SIGINT:
if (nmc_get_in_readline ()) {
/* Don't quit when in readline, only signal we received SIGINT */
pthread_mutex_lock (&sigint_mutex);
nmcli_sigint = TRUE;
pthread_mutex_unlock (&sigint_mutex);
} else {
/* We can quit nmcli */
tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig);
nmc_cleanup_readline ();
g_print (_("\nError: nmcli terminated by signal %s (%d)\n"),
strsignal (signo), signo);
exit (1);
}
break;
case SIGQUIT:
case SIGTERM:
tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig);
nmc_cleanup_readline ();
if (!nmcli_sigquit_internal)
g_print (_("\nError: nmcli terminated by signal %s (%d)\n"),
strsignal (signo), signo);
exit (1);
break;
default:
break;
}
}
return NULL;
}
/*
* Mask the signals we are interested in and create a signal handling thread.
* Because all threads inherit the signal mask from their creator, all threads
* in the process will have the signals masked. That's why setup_signals() has
* to be called before creating other threads.
*/
static gboolean
setup_signals (void)
signal_handler (gpointer user_data)
{
pthread_t signal_thread_id;
int status;
int signo = GPOINTER_TO_INT (user_data);
sigemptyset (&signal_set);
sigaddset (&signal_set, SIGINT);
sigaddset (&signal_set, SIGQUIT);
sigaddset (&signal_set, SIGTERM);
/* Block all signals of interest. */
status = pthread_sigmask (SIG_BLOCK, &signal_set, NULL);
if (status != 0) {
g_printerr (_("Failed to set signal mask: %d\n"), status);
return FALSE;
switch (signo) {
case SIGINT:
if (nmc_get_in_readline ()) {
nmcli_sigint = TRUE;
} else {
g_print (_("Error: nmcli terminated by signal %s (%d)\n"),
strsignal (signo),
signo);
g_main_loop_quit (loop);
}
break;
case SIGTERM:
g_print (_("Error: nmcli terminated by signal %s (%d)\n"),
strsignal (signo), signo);
nmc_exit ();
break;
}
/* Create the signal handling thread. */
status = pthread_create (&signal_thread_id, NULL, signal_handling_thread, NULL);
if (status != 0) {
g_printerr (_("Failed to create signal handling thread: %d\n"), status);
return FALSE;
}
return TRUE;
return G_SOURCE_CONTINUE;
}
static void
@ -682,10 +607,6 @@ main (int argc, char *argv[])
{
ArgsInfo args_info = { &nm_cli, argc, argv };
/* Set up unix signal handling */
if (!setup_signals ())
exit (NMC_RESULT_ERROR_UNKNOWN);
/* Set locale to use environment variables */
setlocale (LC_ALL, "");
@ -701,12 +622,8 @@ main (int argc, char *argv[])
/* Save terminal settings */
tcgetattr (STDIN_FILENO, &termios_orig);
/* readline init */
rl_event_hook = event_hook_for_readline;
/* Set 0.01s timeout to mitigate slowness in readline when a broken version is used.
* See https://bugzilla.redhat.com/show_bug.cgi?id=1109946
*/
rl_set_keyboard_input_timeout (10000);
g_unix_signal_add (SIGTERM, signal_handler, GINT_TO_POINTER (SIGTERM));
g_unix_signal_add (SIGINT, signal_handler, GINT_TO_POINTER (SIGINT));
nmc_value_transforms_register ();

View file

@ -174,5 +174,6 @@ GQuark nmcli_error_quark (void);
gboolean nmc_seen_sigint (void);
void nmc_clear_sigint (void);
void nmc_set_sigquit_internal (void);
void nmc_exit (void);
#endif /* NMC_NMCLI_H */