evdev: On sudden termination, make sure keyboard isn't lost (thanks, Tadek!)

"In release 2.0.6, when Linux evdev keyboard support has been moved to a
separate source file, a feature was added to disable normal keyboard event
processing to prevent "spilling" keystrokes to background virtual console.

This feature has one unpleasant side effect: if application fails to call
`SDL_Exit` before termination or crashes with fatal signal, console is left
in unusable state with keyboard not working and no possibility to switch
virtual console. If user has a chance, he can login remotely and restore
keyboard with `kbd_mode`, otherwise the only option is to reboot the machine.

This patch fixes that problem by intercepting fatal signals (with `sigaction`)
and process termination (with `atexit`), to restore keyboard state, if it
wasn't properly restored with `SDL_Exit`.

The function registered with `atexit` also restores original signal handlers,
to prevent leaving invalid handlers after SDL library is unloaded, if it was
loaded dynamically with `dlopen`.

No signal handlers or `atexit` function are installed if SDL boolean hint
`SDL_HINT_NO_SIGNAL_HANDLERS` is `SDL_TRUE`.

Additionally, if environment variable `SDL_INPUT_LINUX_KEEP_KBD` exists,
keyboard initialization function completely skips disabling keyboard. This
can be useful for debugging."

Fixes Bugzilla #4193.
This commit is contained in:
Ryan C. Gordon 2018-08-07 16:56:46 -04:00
parent 623a6defd3
commit f59b0056d3
1 changed files with 164 additions and 4 deletions

View File

@ -21,6 +21,7 @@
#include "../../SDL_internal.h" #include "../../SDL_internal.h"
#include "SDL_evdev_kbd.h" #include "SDL_evdev_kbd.h"
#include "SDL_hints.h"
#ifdef SDL_INPUT_LINUXKD #ifdef SDL_INPUT_LINUXKD
@ -34,6 +35,8 @@
#include <linux/vt.h> #include <linux/vt.h>
#include <linux/tiocl.h> /* for TIOCL_GETSHIFTSTATE */ #include <linux/tiocl.h> /* for TIOCL_GETSHIFTSTATE */
#include <signal.h>
#include "../../events/SDL_events_c.h" #include "../../events/SDL_events_c.h"
#include "SDL_evdev_kbd_default_accents.h" #include "SDL_evdev_kbd_default_accents.h"
#include "SDL_evdev_kbd_default_keymap.h" #include "SDL_evdev_kbd_default_keymap.h"
@ -191,6 +194,151 @@ static int SDL_EVDEV_kbd_load_keymaps(SDL_EVDEV_keyboard_state *kbd)
return 0; return 0;
} }
static SDL_EVDEV_keyboard_state * kbd_cleanup_state = NULL;
static int kbd_cleanup_sigactions_installed = 0;
static int kbd_cleanup_atexit_installed = 0;
static struct sigaction old_sigaction[NSIG] = { 0 };
static int fatal_signals[] =
{
/* Handlers for SIGTERM and SIGINT are installed in SDL_QuitInit. */
SIGHUP, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGBUS,
SIGSYS
};
static void kbd_cleanup(void)
{
SDL_EVDEV_keyboard_state* kbd = kbd_cleanup_state;
if (kbd == NULL) {
return;
}
kbd_cleanup_state = NULL;
fprintf(stderr, "(SDL restoring keyboard) ");
ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);
}
void
SDL_EVDEV_kbd_reraise_signal(int sig)
{
raise(sig);
}
siginfo_t* SDL_EVDEV_kdb_cleanup_siginfo = NULL;
void* SDL_EVDEV_kdb_cleanup_ucontext = NULL;
static void kbd_cleanup_signal_action(int signum, siginfo_t* info, void* ucontext)
{
struct sigaction* old_action_p = &(old_sigaction[signum]);
sigset_t sigset;
/* Restore original signal handler before going any further. */
sigaction(signum, old_action_p, NULL);
/* Unmask current signal. */
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
/* Save original signal info and context for archeologists. */
SDL_EVDEV_kdb_cleanup_siginfo = info;
SDL_EVDEV_kdb_cleanup_ucontext = ucontext;
/* Restore keyboard. */
kbd_cleanup();
/* Reraise signal. */
SDL_EVDEV_kbd_reraise_signal(signum);
}
static void kbd_unregister_emerg_cleanup()
{
int tabidx, signum;
kbd_cleanup_state = NULL;
if (!kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 0;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction* old_action_p;
struct sigaction cur_action;
signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
/* Examine current signal action */
if (sigaction(signum, NULL, &cur_action))
continue;
/* Check if action installed and not modifed */
if (!(cur_action.sa_flags & SA_SIGINFO)
|| cur_action.sa_sigaction != &kbd_cleanup_signal_action)
continue;
/* Restore original action */
sigaction(signum, old_action_p, NULL);
}
}
static void kbd_cleanup_atexit(void)
{
/* Restore keyboard. */
kbd_cleanup();
/* Try to restore signal handlers in case shared library is being unloaded */
kbd_unregister_emerg_cleanup();
}
static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state * kbd)
{
int tabidx, signum;
if (kbd_cleanup_state != NULL) {
return;
}
kbd_cleanup_state = kbd;
if (!kbd_cleanup_atexit_installed) {
/* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish
* functions that are called when the shared library is unloaded.
* -- man atexit(3)
*/
atexit(kbd_cleanup_atexit);
kbd_cleanup_atexit_installed = 1;
}
if (kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 1;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction* old_action_p;
struct sigaction new_action;
signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
if (sigaction(signum, NULL, old_action_p))
continue;
/* Skip SIGHUP and SIGPIPE if handler is already installed
* - assume the handler will do the cleanup
*/
if ((signum == SIGHUP || signum == SIGPIPE)
&& (old_action_p->sa_handler != SIG_DFL
|| (void (*)(int))old_action_p->sa_sigaction != SIG_DFL))
continue;
new_action = *old_action_p;
new_action.sa_flags |= SA_SIGINFO;
new_action.sa_sigaction = &kbd_cleanup_signal_action;
sigaction(signum, &new_action, NULL);
}
}
SDL_EVDEV_keyboard_state * SDL_EVDEV_keyboard_state *
SDL_EVDEV_kbd_init(void) SDL_EVDEV_kbd_init(void)
{ {
@ -238,10 +386,20 @@ SDL_EVDEV_kbd_init(void)
kbd->key_maps = default_key_maps; kbd->key_maps = default_key_maps;
} }
/* Allow inhibiting keyboard mute with env. variable for debugging etc. */
if (getenv("SDL_INPUT_LINUX_KEEP_KBD") == NULL) {
/* Mute the keyboard so keystrokes only generate evdev events /* Mute the keyboard so keystrokes only generate evdev events
* and do not leak through to the console * and do not leak through to the console
*/ */
ioctl(kbd->console_fd, KDSKBMODE, K_OFF); ioctl(kbd->console_fd, KDSKBMODE, K_OFF);
/* Make sure to restore keyboard if application fails to call
* SDL_Quit before exit or fatal signal is raised.
*/
if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, SDL_FALSE)) {
kbd_register_emerg_cleanup(kbd);
}
}
} }
#ifdef DUMP_ACCENTS #ifdef DUMP_ACCENTS
@ -260,6 +418,8 @@ SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd)
return; return;
} }
kbd_unregister_emerg_cleanup();
if (kbd->console_fd >= 0) { if (kbd->console_fd >= 0) {
/* Restore the original keyboard mode */ /* Restore the original keyboard mode */
ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode); ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);