mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
MDEV-14448: Ctrl-C should not exit the client
This patch introduces the following behaviour for Linux while maintaining old behaviour for Windows: Ctrl + C (sigint) clears the current buffer and redraws the prompt. Ctrl-C no longer exits the client if no query is running. Ctrl-C kills the current running query if there is one. If there is an error communicating with the server while trying to issue a KILL QUERY, the client exits. This is in line with the past behaviour of Ctrl-C. On Linux Ctrl-D can be used to close the client. On Windows Ctrl-C and Ctrl-BREAK still exits the client if no query is running. Windows can also exit the client via \q<enter> or exit<enter>. == Implementation details == The Linux implementation has two corner cases, based on which library is used: libreadline or libedit, both are handled in code to achieve the same user experience. Additional code is taken from MySQL, ensuring there is identical behaviour on Windows, to MySQL's mysql client implementation for other CTRL- related signals. * The CTRL_CLOSE, CTRL_LOGOFF, CTRL_SHUTDOWN will issue the equivalent of CTRL-C and "end" the program. This ensures that the query is killed when the client is closed by closing the terminal, logging off the user or shutting down the system. The latter two signals are not sent for interactive applications, but it handles the case when a user has defined a service to use mysql client to issue a command. See https://learn.microsoft.com/en-us/windows/console/handlerroutine This patch is built on top of the initial work done by Anel Husakovic <anel@mariadb.org>. Closes #2815
This commit is contained in:
committed by
Vicențiu Ciorbaru
parent
7e8e51eb3a
commit
3cd8875145
159
client/mysql.cc
159
client/mysql.cc
@@ -89,6 +89,7 @@ extern "C" {
|
|||||||
#undef bcmp // Fix problem with new readline
|
#undef bcmp // Fix problem with new readline
|
||||||
#if defined(__WIN__)
|
#if defined(__WIN__)
|
||||||
#include <conio.h>
|
#include <conio.h>
|
||||||
|
#include <windows.h>
|
||||||
#else
|
#else
|
||||||
# ifdef __APPLE__
|
# ifdef __APPLE__
|
||||||
# include <editline/readline.h>
|
# include <editline/readline.h>
|
||||||
@@ -170,6 +171,9 @@ static int connect_flag=CLIENT_INTERACTIVE;
|
|||||||
static my_bool opt_binary_mode= FALSE;
|
static my_bool opt_binary_mode= FALSE;
|
||||||
static my_bool opt_connect_expired_password= FALSE;
|
static my_bool opt_connect_expired_password= FALSE;
|
||||||
static int interrupted_query= 0;
|
static int interrupted_query= 0;
|
||||||
|
#ifdef USE_LIBEDIT_INTERFACE
|
||||||
|
static int sigint_received= 0;
|
||||||
|
#endif
|
||||||
static char *current_host,*current_db,*current_user=0,*opt_password=0,
|
static char *current_host,*current_db,*current_user=0,*opt_password=0,
|
||||||
*current_prompt=0, *delimiter_str= 0,
|
*current_prompt=0, *delimiter_str= 0,
|
||||||
*default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME,
|
*default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME,
|
||||||
@@ -1072,7 +1076,32 @@ extern "C" sig_handler handle_sigint(int sig);
|
|||||||
static sig_handler window_resize(int sig);
|
static sig_handler window_resize(int sig);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void end_in_sig_handler(int sig);
|
||||||
|
static bool kill_query(const char *reason);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static BOOL WINAPI ctrl_handler_windows(DWORD fdwCtrlType)
|
||||||
|
{
|
||||||
|
switch (fdwCtrlType)
|
||||||
|
{
|
||||||
|
// Handle the CTRL-C signal.
|
||||||
|
case CTRL_C_EVENT:
|
||||||
|
case CTRL_BREAK_EVENT:
|
||||||
|
handle_sigint(SIGINT);
|
||||||
|
return TRUE; // this means that the signal is handled
|
||||||
|
case CTRL_CLOSE_EVENT:
|
||||||
|
case CTRL_LOGOFF_EVENT:
|
||||||
|
case CTRL_SHUTDOWN_EVENT:
|
||||||
|
kill_query("Terminate");
|
||||||
|
end_in_sig_handler(SIGINT + 1);
|
||||||
|
aborted= 1;
|
||||||
|
}
|
||||||
|
// This means to pass the signal to the next handler. This allows
|
||||||
|
// my_cgets and the internal ReadConsole call to exit and gracefully
|
||||||
|
// abort the program.
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
const char DELIMITER_NAME[]= "delimiter";
|
const char DELIMITER_NAME[]= "delimiter";
|
||||||
const uint DELIMITER_NAME_LEN= sizeof(DELIMITER_NAME) - 1;
|
const uint DELIMITER_NAME_LEN= sizeof(DELIMITER_NAME) - 1;
|
||||||
inline bool is_delimiter_command(char *name, ulong len)
|
inline bool is_delimiter_command(char *name, ulong len)
|
||||||
@@ -1205,11 +1234,12 @@ int main(int argc,char *argv[])
|
|||||||
if (!status.batch)
|
if (!status.batch)
|
||||||
ignore_errors=1; // Don't abort monitor
|
ignore_errors=1; // Don't abort monitor
|
||||||
|
|
||||||
if (opt_sigint_ignore)
|
#ifndef _WIN32
|
||||||
signal(SIGINT, SIG_IGN);
|
|
||||||
else
|
|
||||||
signal(SIGINT, handle_sigint); // Catch SIGINT to clean up
|
signal(SIGINT, handle_sigint); // Catch SIGINT to clean up
|
||||||
signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up
|
signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up
|
||||||
|
#else
|
||||||
|
SetConsoleCtrlHandler(ctrl_handler_windows, TRUE);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL)
|
#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL)
|
||||||
/* Readline will call this if it installs a handler */
|
/* Readline will call this if it installs a handler */
|
||||||
@@ -1376,30 +1406,35 @@ static bool do_connect(MYSQL *mysql, const char *host, const char *user,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void end_in_sig_handler(int sig)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
/*
|
/*
|
||||||
This function handles sigint calls
|
When SIGINT is raised on Windows, the OS creates a new thread to handle the
|
||||||
If query is in process, kill query
|
interrupt. Once that thread completes, the main thread continues running
|
||||||
If 'source' is executed, abort source command
|
only to find that it's resources have already been free'd when the sigint
|
||||||
no query in process, terminate like previous behavior
|
handler called mysql_end().
|
||||||
*/
|
*/
|
||||||
|
mysql_thread_end();
|
||||||
|
#else
|
||||||
|
mysql_end(sig);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
sig_handler handle_sigint(int sig)
|
|
||||||
|
/*
|
||||||
|
Kill a running query. Returns true if we were unable to connect to the server.
|
||||||
|
*/
|
||||||
|
bool kill_query(const char *reason)
|
||||||
{
|
{
|
||||||
char kill_buffer[40];
|
char kill_buffer[40];
|
||||||
MYSQL *kill_mysql= NULL;
|
MYSQL *kill_mysql= NULL;
|
||||||
|
|
||||||
/* terminate if no query being executed, or we already tried interrupting */
|
|
||||||
if (!executing_query || (interrupted_query == 2))
|
|
||||||
{
|
|
||||||
tee_fprintf(stdout, "Ctrl-C -- exit!\n");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
kill_mysql= mysql_init(kill_mysql);
|
kill_mysql= mysql_init(kill_mysql);
|
||||||
if (!do_connect(kill_mysql,current_host, current_user, opt_password, "", 0))
|
if (!do_connect(kill_mysql,current_host, current_user, opt_password, "", 0))
|
||||||
{
|
{
|
||||||
tee_fprintf(stdout, "Ctrl-C -- sorry, cannot connect to server to kill query, giving up ...\n");
|
tee_fprintf(stdout, "%s -- sorry, cannot connect to server to kill query, giving up ...\n", reason);
|
||||||
goto err;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* First time try to kill the query, second time the connection */
|
/* First time try to kill the query, second time the connection */
|
||||||
@@ -1414,27 +1449,65 @@ sig_handler handle_sigint(int sig)
|
|||||||
(interrupted_query == 1) ? "QUERY " : "",
|
(interrupted_query == 1) ? "QUERY " : "",
|
||||||
mysql_thread_id(&mysql));
|
mysql_thread_id(&mysql));
|
||||||
if (verbose)
|
if (verbose)
|
||||||
tee_fprintf(stdout, "Ctrl-C -- sending \"%s\" to server ...\n",
|
tee_fprintf(stdout, "%s -- sending \"%s\" to server ...\n", reason,
|
||||||
kill_buffer);
|
kill_buffer);
|
||||||
mysql_real_query(kill_mysql, kill_buffer, (uint) strlen(kill_buffer));
|
mysql_real_query(kill_mysql, kill_buffer, (uint) strlen(kill_buffer));
|
||||||
mysql_close(kill_mysql);
|
mysql_close(kill_mysql);
|
||||||
tee_fprintf(stdout, "Ctrl-C -- query killed. Continuing normally.\n");
|
if (interrupted_query == 1)
|
||||||
|
tee_fprintf(stdout, "%s -- query killed.\n", reason);
|
||||||
|
else
|
||||||
|
tee_fprintf(stdout, "%s -- connection killed.\n", reason);
|
||||||
|
|
||||||
if (in_com_source)
|
if (in_com_source)
|
||||||
aborted= 1; // Abort source command
|
aborted= 1; // Abort source command
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This function handles sigint calls
|
||||||
|
If query is in process, kill query
|
||||||
|
If 'source' is executed, abort source command
|
||||||
|
no query in process, regenerate prompt.
|
||||||
|
*/
|
||||||
|
sig_handler handle_sigint(int sig)
|
||||||
|
{
|
||||||
|
if (opt_sigint_ignore)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
err:
|
|
||||||
#ifdef _WIN32
|
|
||||||
/*
|
/*
|
||||||
When SIGINT is raised on Windows, the OS creates a new thread to handle the
|
On Unix only, if no query is being executed just clear the prompt,
|
||||||
interrupt. Once that thread completes, the main thread continues running
|
don't exit. On Windows we exit.
|
||||||
only to find that it's resources have already been free'd when the sigint
|
|
||||||
handler called mysql_end().
|
|
||||||
*/
|
*/
|
||||||
mysql_thread_end();
|
if (!executing_query)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
tee_fprintf(stdout, "^C\n");
|
||||||
|
#ifdef USE_LIBEDIT_INTERFACE
|
||||||
|
/* Libedit will regenerate it outside of the signal handler. */
|
||||||
|
sigint_received= 1;
|
||||||
#else
|
#else
|
||||||
mysql_end(sig);
|
rl_on_new_line(); // Regenerate the prompt on a newline
|
||||||
|
rl_replace_line("", 0); // Clear the previous text
|
||||||
|
rl_redisplay();
|
||||||
#endif
|
#endif
|
||||||
|
#else // WIN32
|
||||||
|
tee_fprintf(stdout, "Ctrl-C -- exit!\n");
|
||||||
|
end_in_sig_handler(sig);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
When executing a query, this newline makes the prompt look like so:
|
||||||
|
^C
|
||||||
|
Ctrl-C -- query killed.
|
||||||
|
*/
|
||||||
|
tee_fprintf(stdout, "\n");
|
||||||
|
if (kill_query("Ctrl-C"))
|
||||||
|
{
|
||||||
|
aborted= 1;
|
||||||
|
end_in_sig_handler(sig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1973,6 +2046,12 @@ static int get_options(int argc, char **argv)
|
|||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void reset_prompt(char *in_string, bool *ml_comment) {
|
||||||
|
glob_buffer.length(0);
|
||||||
|
*ml_comment = false;
|
||||||
|
*in_string = 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int read_and_execute(bool interactive)
|
static int read_and_execute(bool interactive)
|
||||||
{
|
{
|
||||||
#if defined(__WIN__)
|
#if defined(__WIN__)
|
||||||
@@ -2085,8 +2164,34 @@ static int read_and_execute(bool interactive)
|
|||||||
the readline/libedit library.
|
the readline/libedit library.
|
||||||
*/
|
*/
|
||||||
if (line)
|
if (line)
|
||||||
|
{
|
||||||
free(line);
|
free(line);
|
||||||
|
glob_buffer.length(0);
|
||||||
|
}
|
||||||
line= readline(prompt);
|
line= readline(prompt);
|
||||||
|
#ifdef USE_LIBEDIT_INTERFACE
|
||||||
|
/*
|
||||||
|
libedit handles interrupts different than libreadline.
|
||||||
|
libreadline has its own signal handlers, thus a sigint during readline
|
||||||
|
doesn't force readline to return null string.
|
||||||
|
|
||||||
|
However libedit returns null if the interrupt signal is raised.
|
||||||
|
We can also get an empty string when ctrl+d is pressed (EoF).
|
||||||
|
|
||||||
|
We need this sigint_received flag, to differentiate between the two
|
||||||
|
cases. This flag is only set during our handle_sigint function when
|
||||||
|
LIBEDIT_INTERFACE is used.
|
||||||
|
*/
|
||||||
|
if (!line && sigint_received)
|
||||||
|
{
|
||||||
|
// User asked to clear the input.
|
||||||
|
sigint_received= 0;
|
||||||
|
reset_prompt(&in_string, &ml_comment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// For safety, we always mark this as cleared.
|
||||||
|
sigint_received= 0;
|
||||||
|
#endif
|
||||||
#endif /* defined(__WIN__) */
|
#endif /* defined(__WIN__) */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user