From f59b0056d30c6db928268703da8492a0aaed3608 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 7 Aug 2018 16:56:46 -0400 Subject: [PATCH] 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. --- src/core/linux/SDL_evdev_kbd.c | 168 ++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 4 deletions(-) diff --git a/src/core/linux/SDL_evdev_kbd.c b/src/core/linux/SDL_evdev_kbd.c index 8b1d5243e6299..4359136d98bcc 100644 --- a/src/core/linux/SDL_evdev_kbd.c +++ b/src/core/linux/SDL_evdev_kbd.c @@ -21,6 +21,7 @@ #include "../../SDL_internal.h" #include "SDL_evdev_kbd.h" +#include "SDL_hints.h" #ifdef SDL_INPUT_LINUXKD @@ -34,6 +35,8 @@ #include #include /* for TIOCL_GETSHIFTSTATE */ +#include + #include "../../events/SDL_events_c.h" #include "SDL_evdev_kbd_default_accents.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; } +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_kbd_init(void) { @@ -238,10 +386,20 @@ SDL_EVDEV_kbd_init(void) kbd->key_maps = default_key_maps; } - /* Mute the keyboard so keystrokes only generate evdev events - * and do not leak through to the console - */ - ioctl(kbd->console_fd, KDSKBMODE, K_OFF); + /* 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 + * and do not leak through to the console + */ + 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 @@ -260,6 +418,8 @@ SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) return; } + kbd_unregister_emerg_cleanup(); + if (kbd->console_fd >= 0) { /* Restore the original keyboard mode */ ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);