Merge changes for Ctrl-C and Ctrl-D handling in nmcli/readline (bgo #706118)

https://bugzilla.gnome.org/show_bug.cgi?id=706118
This commit is contained in:
Jiří Klimeš 2014-06-24 11:00:19 +02:00
commit c8902e8171
5 changed files with 150 additions and 10 deletions

View file

@ -1101,6 +1101,32 @@ 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;
}
void
nmc_set_in_readline (gboolean in_readline)
{
pthread_mutex_lock (&readline_mutex);
nmcli_in_readline = in_readline;
pthread_mutex_unlock (&readline_mutex);
}
/* Global variable defined in nmcli.c */
extern NmCli nm_cli;
/**
* nmc_readline:
* @prompt_fmt: prompt to print (telling user what to enter). It is standard
@ -1108,6 +1134,9 @@ nmc_cleanup_readline (void)
* @...: a list of arguments according to the @prompt_fmt format string
*
* Wrapper around libreadline's readline() function.
* If user pressed Ctrl-C, readline() is called again (if not in editor and
* line is empty, nmcli will quit).
* If user pressed Ctrl-D on empty line, nmcli will quit.
*
* Returns: the user provided string. In case the user entered empty string,
* this function returns NULL.
@ -1122,17 +1151,52 @@ nmc_readline (const char *prompt_fmt, ...)
prompt = g_strdup_vprintf (prompt_fmt, args);
va_end (args);
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);
/* Add string to the history */
if (str && *str)
add_history (str);
/*-- 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 ()) {
nmc_clear_sigint ();
if (nm_cli.in_editor || *str) {
/* In editor, or the line is not empty */
/* Call readline again to get new prompt (repeat) */
g_free (str);
goto readline_mark;
} 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);
}
}
g_free (prompt);
/* Return NULL, not empty string */
if (str && *str == '\0') {
g_free (str);
str = NULL;
}
if (str && *str)
add_history (str);
g_free (prompt);
return str;
}

View file

@ -62,5 +62,7 @@ NMConnection *nmc_find_connection (GSList *list,
void nmc_cleanup_readline (void);
char *nmc_readline (const char *prompt_fmt, ...) G_GNUC_PRINTF (1, 2);
char *nmc_rl_gen_func_basic (const char *text, int state, const char **words);
gboolean nmc_get_in_readline (void);
void nmc_set_in_readline (gboolean in_readline);
#endif /* NMC_COMMON_H */

View file

@ -7982,6 +7982,9 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
editor_init_new_connection (nmc, connection);
}
/* nmcli runs the editor */
nmc->in_editor = TRUE;
printf ("\n");
printf (_("===| nmcli interactive connection editor |==="));
printf ("\n\n");

View file

@ -28,6 +28,8 @@
#include <signal.h>
#include <pthread.h>
#include <locale.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <glib.h>
#include <glib/gi18n.h>
@ -270,6 +272,46 @@ parse_command_line (NmCli *nmc, int argc, char **argv)
return nmc->return_value;
}
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;
}
void
nmc_clear_sigint (void)
{
pthread_mutex_lock (&sigint_mutex);
nmcli_sigint = FALSE;
pthread_mutex_unlock (&sigint_mutex);
}
void
nmc_set_sigquit_internal (void)
{
nmcli_sigquit_internal = TRUE;
}
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.
@ -287,10 +329,25 @@ signal_handling_thread (void *arg) {
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 */
nmc_cleanup_readline ();
printf (_("\nError: nmcli terminated by signal %s (%d)\n"),
strsignal (signo), signo);
exit (1);
}
break;
case SIGQUIT:
case SIGTERM:
nmc_cleanup_readline ();
printf (_("\nError: nmcli terminated by signal %d."), signo);
if (!nmcli_sigquit_internal)
printf (_("\nError: nmcli terminated by signal %s (%d)\n"),
strsignal (signo), signo);
exit (1);
break;
default:
@ -320,14 +377,14 @@ setup_signals (void)
/* Block all signals of interest. */
status = pthread_sigmask (SIG_BLOCK, &signal_set, NULL);
if (status != 0) {
fprintf (stderr, _("Failed to set signal mask: %d"), status);
fprintf (stderr, _("Failed to set signal mask: %d\n"), status);
return FALSE;
}
/* Create the signal handling thread. */
/* Create the signal handling thread. */
status = pthread_create (&signal_thread_id, NULL, signal_handling_thread, NULL);
if (status != 0) {
fprintf (stderr, _("Failed to create signal handling thread: %d"), status);
fprintf (stderr, _("Failed to create signal handling thread: %d\n"), status);
return FALSE;
}
@ -375,6 +432,7 @@ nmc_init (NmCli *nmc)
memset (&nmc->print_fields, '\0', sizeof (NmcPrintFields));
nmc->nocheck_ver = FALSE;
nmc->ask = FALSE;
nmc->in_editor = FALSE;
nmc->editor_status_line = FALSE;
nmc->editor_save_confirmation = TRUE;
nmc->editor_prompt_color = NMC_TERM_COLOR_NORMAL;
@ -431,6 +489,13 @@ main (int argc, char *argv[])
g_type_init ();
#endif
/* 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);
nmc_init (&nm_cli);
g_idle_add (start, &args_info);

View file

@ -14,7 +14,7 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* (C) Copyright 2010 - 2012 Red Hat, Inc.
* (C) Copyright 2010 - 2014 Red Hat, Inc.
*/
#ifndef NMC_NMCLI_H
@ -128,6 +128,7 @@ typedef struct _NmCli {
NmcPrintFields print_fields; /* Structure with field indices to print */
gboolean nocheck_ver; /* Don't check nmcli and NM versions: option '--nocheck' */
gboolean ask; /* Ask for missing parameters: option '--ask' */
gboolean in_editor; /* Whether running the editor - nmcli con edit' */
gboolean editor_status_line; /* Whether to display status line in connection editor */
gboolean editor_save_confirmation; /* Whether to ask for confirmation on saving connections with 'autoconnect=yes' */
NmcTermColor editor_prompt_color; /* Color of prompt in connection editor */
@ -137,4 +138,9 @@ typedef struct _NmCli {
#define NMCLI_ERROR (nmcli_error_quark ())
GQuark nmcli_error_quark (void);
gboolean nmc_seen_sigint (void);
void nmc_clear_sigint (void);
void nmc_set_sigquit_internal (void);
#endif /* NMC_NMCLI_H */