evdev: On sudden termination, make sure keyboard isn't lost (thanks, Tadek!)
authorRyan C. Gordon <icculus@icculus.org>
Tue, 07 Aug 2018 16:56:46 -0400
changeset 1208018ea018e00a0
parent 12079 8ffd9de57415
child 12081 277e57c09ff4
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
     1.1 --- a/src/core/linux/SDL_evdev_kbd.c	Tue Aug 07 16:49:18 2018 -0400
     1.2 +++ b/src/core/linux/SDL_evdev_kbd.c	Tue Aug 07 16:56:46 2018 -0400
     1.3 @@ -21,6 +21,7 @@
     1.4  #include "../../SDL_internal.h"
     1.5  
     1.6  #include "SDL_evdev_kbd.h"
     1.7 +#include "SDL_hints.h"
     1.8  
     1.9  #ifdef SDL_INPUT_LINUXKD
    1.10  
    1.11 @@ -34,6 +35,8 @@
    1.12  #include <linux/vt.h>
    1.13  #include <linux/tiocl.h> /* for TIOCL_GETSHIFTSTATE */
    1.14  
    1.15 +#include <signal.h>
    1.16 +
    1.17  #include "../../events/SDL_events_c.h"
    1.18  #include "SDL_evdev_kbd_default_accents.h"
    1.19  #include "SDL_evdev_kbd_default_keymap.h"
    1.20 @@ -191,6 +194,151 @@
    1.21      return 0;
    1.22  }
    1.23  
    1.24 +static SDL_EVDEV_keyboard_state * kbd_cleanup_state = NULL;
    1.25 +static int kbd_cleanup_sigactions_installed = 0;
    1.26 +static int kbd_cleanup_atexit_installed = 0;
    1.27 +
    1.28 +static struct sigaction old_sigaction[NSIG] = { 0 };
    1.29 +
    1.30 +static int fatal_signals[] =
    1.31 +{
    1.32 +    /* Handlers for SIGTERM and SIGINT are installed in SDL_QuitInit. */
    1.33 +    SIGHUP,  SIGQUIT, SIGILL,  SIGABRT,
    1.34 +    SIGFPE,  SIGSEGV, SIGPIPE, SIGBUS,
    1.35 +    SIGSYS
    1.36 +};
    1.37 +
    1.38 +static void kbd_cleanup(void)
    1.39 +{
    1.40 +    SDL_EVDEV_keyboard_state* kbd = kbd_cleanup_state;
    1.41 +    if (kbd == NULL) {
    1.42 +        return;
    1.43 +    }
    1.44 +    kbd_cleanup_state = NULL;
    1.45 +
    1.46 +    fprintf(stderr, "(SDL restoring keyboard) ");
    1.47 +    ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);
    1.48 +}
    1.49 +
    1.50 +void
    1.51 +SDL_EVDEV_kbd_reraise_signal(int sig)
    1.52 +{
    1.53 +    raise(sig);
    1.54 +}
    1.55 +
    1.56 +siginfo_t* SDL_EVDEV_kdb_cleanup_siginfo = NULL;
    1.57 +void*      SDL_EVDEV_kdb_cleanup_ucontext = NULL;
    1.58 +
    1.59 +static void kbd_cleanup_signal_action(int signum, siginfo_t* info, void* ucontext)
    1.60 +{
    1.61 +    struct sigaction* old_action_p = &(old_sigaction[signum]);
    1.62 +    sigset_t sigset;
    1.63 +
    1.64 +    /* Restore original signal handler before going any further. */
    1.65 +    sigaction(signum, old_action_p, NULL);
    1.66 +
    1.67 +    /* Unmask current signal. */
    1.68 +    sigemptyset(&sigset);
    1.69 +    sigaddset(&sigset, signum);
    1.70 +    sigprocmask(SIG_UNBLOCK, &sigset, NULL);
    1.71 +
    1.72 +    /* Save original signal info and context for archeologists. */
    1.73 +    SDL_EVDEV_kdb_cleanup_siginfo = info;
    1.74 +    SDL_EVDEV_kdb_cleanup_ucontext = ucontext;
    1.75 +
    1.76 +    /* Restore keyboard. */
    1.77 +    kbd_cleanup();
    1.78 +
    1.79 +    /* Reraise signal. */
    1.80 +    SDL_EVDEV_kbd_reraise_signal(signum);
    1.81 +}
    1.82 +
    1.83 +static void kbd_unregister_emerg_cleanup()
    1.84 +{
    1.85 +    int tabidx, signum;
    1.86 +
    1.87 +    kbd_cleanup_state = NULL;
    1.88 +
    1.89 +    if (!kbd_cleanup_sigactions_installed) {
    1.90 +        return;
    1.91 +    }
    1.92 +    kbd_cleanup_sigactions_installed = 0;
    1.93 +
    1.94 +    for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
    1.95 +        struct sigaction* old_action_p;
    1.96 +        struct sigaction cur_action;
    1.97 +        signum = fatal_signals[tabidx];
    1.98 +        old_action_p = &(old_sigaction[signum]);
    1.99 +
   1.100 +        /* Examine current signal action */
   1.101 +        if (sigaction(signum, NULL, &cur_action))
   1.102 +            continue;
   1.103 +
   1.104 +        /* Check if action installed and not modifed */
   1.105 +        if (!(cur_action.sa_flags & SA_SIGINFO)
   1.106 +                || cur_action.sa_sigaction != &kbd_cleanup_signal_action)
   1.107 +            continue;
   1.108 +
   1.109 +        /* Restore original action */
   1.110 +        sigaction(signum, old_action_p, NULL);
   1.111 +    }
   1.112 +}
   1.113 +
   1.114 +static void kbd_cleanup_atexit(void)
   1.115 +{
   1.116 +    /* Restore keyboard. */
   1.117 +    kbd_cleanup();
   1.118 +
   1.119 +    /* Try to restore signal handlers in case shared library is being unloaded */
   1.120 +    kbd_unregister_emerg_cleanup();
   1.121 +}
   1.122 +
   1.123 +static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state * kbd)
   1.124 +{
   1.125 +    int tabidx, signum;
   1.126 +
   1.127 +    if (kbd_cleanup_state != NULL) {
   1.128 +        return;
   1.129 +    }
   1.130 +    kbd_cleanup_state = kbd;
   1.131 +
   1.132 +    if (!kbd_cleanup_atexit_installed) {
   1.133 +        /* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish
   1.134 +         * functions that are called when the shared library is unloaded.
   1.135 +         * -- man atexit(3)
   1.136 +         */
   1.137 +        atexit(kbd_cleanup_atexit);
   1.138 +        kbd_cleanup_atexit_installed = 1;
   1.139 +    }
   1.140 +
   1.141 +    if (kbd_cleanup_sigactions_installed) {
   1.142 +        return;
   1.143 +    }
   1.144 +    kbd_cleanup_sigactions_installed = 1;
   1.145 +
   1.146 +    for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
   1.147 +        struct sigaction* old_action_p;
   1.148 +        struct sigaction new_action;
   1.149 +        signum = fatal_signals[tabidx];   
   1.150 +        old_action_p = &(old_sigaction[signum]);
   1.151 +        if (sigaction(signum, NULL, old_action_p))
   1.152 +            continue;
   1.153 +
   1.154 +        /* Skip SIGHUP and SIGPIPE if handler is already installed
   1.155 +         * - assume the handler will do the cleanup
   1.156 +         */
   1.157 +        if ((signum == SIGHUP || signum == SIGPIPE)
   1.158 +                && (old_action_p->sa_handler != SIG_DFL 
   1.159 +                    || (void (*)(int))old_action_p->sa_sigaction != SIG_DFL))
   1.160 +            continue;
   1.161 +
   1.162 +        new_action = *old_action_p;
   1.163 +        new_action.sa_flags |= SA_SIGINFO;
   1.164 +        new_action.sa_sigaction = &kbd_cleanup_signal_action;
   1.165 +        sigaction(signum, &new_action, NULL);
   1.166 +    }
   1.167 +}
   1.168 +
   1.169  SDL_EVDEV_keyboard_state *
   1.170  SDL_EVDEV_kbd_init(void)
   1.171  {
   1.172 @@ -238,10 +386,20 @@
   1.173              kbd->key_maps = default_key_maps;
   1.174          }
   1.175  
   1.176 -        /* Mute the keyboard so keystrokes only generate evdev events
   1.177 -         * and do not leak through to the console
   1.178 -         */
   1.179 -        ioctl(kbd->console_fd, KDSKBMODE, K_OFF);
   1.180 +        /* Allow inhibiting keyboard mute with env. variable for debugging etc. */
   1.181 +        if (getenv("SDL_INPUT_LINUX_KEEP_KBD") == NULL) {
   1.182 +            /* Mute the keyboard so keystrokes only generate evdev events
   1.183 +             * and do not leak through to the console
   1.184 +             */
   1.185 +            ioctl(kbd->console_fd, KDSKBMODE, K_OFF);
   1.186 +
   1.187 +            /* Make sure to restore keyboard if application fails to call
   1.188 +             * SDL_Quit before exit or fatal signal is raised.
   1.189 +             */
   1.190 +            if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, SDL_FALSE)) {
   1.191 +                kbd_register_emerg_cleanup(kbd);
   1.192 +            }
   1.193 +        }
   1.194      }
   1.195  
   1.196  #ifdef DUMP_ACCENTS
   1.197 @@ -260,6 +418,8 @@
   1.198          return;
   1.199      }
   1.200  
   1.201 +    kbd_unregister_emerg_cleanup();
   1.202 +
   1.203      if (kbd->console_fd >= 0) {
   1.204          /* Restore the original keyboard mode */
   1.205          ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);