catch SIGTSTP and save/restore terminal settings (#4460)
authorSimon Marlow <marlowsd@gmail.com>
Fri, 7 Jan 2011 12:40:42 +0000 (12:40 +0000)
committerSimon Marlow <marlowsd@gmail.com>
Fri, 7 Jan 2011 12:40:42 +0000 (12:40 +0000)
As far as I can tell, it is the responsibility of the program to save
and restore its own terminal settings across a suspend/foreground, the
shell doesn't do it (which seems odd).  So I've added a signal handler
for SIGTSTP to the RTS which will save and restore the terminal
settings iff we modified them with hSetBuffering or hSetEcho (we
already restore them at exit time in these cases).

rts/posix/Signals.c

index e723b8f..1e1bb3f 100644 (file)
 # include <sys/eventfd.h>
 #endif
 
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -480,6 +484,75 @@ empty_handler (int sig STG_UNUSED)
 }
 
 /* -----------------------------------------------------------------------------
+   SIGTSTP handling
+
+   When a process is suspeended with ^Z and resumed again, the shell
+   makes no attempt to save and restore the terminal settings.  So on
+   resume, any terminal setting modificaions we made (e.g. turning off
+   ICANON due to hSetBuffering NoBuffering) may well be lost.  Hence,
+   we arrange to save and restore the terminal settings ourselves.
+
+   The trick we use is:
+     - catch SIGTSTP
+     - in the handler,  kill(getpid(),SIGTSTP)
+     - when this returns, restore the TTY settings
+   This means we don't have to catch SIGCONT too.
+
+   -------------------------------------------------------------------------- */
+
+static void sigtstp_handler(int sig);
+static void set_sigtstp_action (rtsBool handle);
+
+static void
+sigtstp_handler (int sig)
+{
+    int fd;
+    struct termios ts[3];
+
+    // save the current TTY state for TTYs we modified
+    for (fd = 0; fd <= 2; fd++) {
+        if (__hscore_get_saved_termios(fd) != NULL) {
+            tcgetattr(fd,&ts[fd]);
+        }
+    }
+
+    // de-install the SIGTSTP handler
+    set_sigtstp_action(rtsFalse);
+
+    // really stop the process now
+    {
+        sigset_t mask;
+        sigemptyset(&mask);
+        sigaddset(&mask, sig);
+        sigprocmask(SIG_UNBLOCK, &mask, NULL);
+        kill(getpid(), sig);
+    }
+
+    // on return, restore the TTY state
+    for (fd = 0; fd <= 2; fd++) {
+        if (__hscore_get_saved_termios(fd) != NULL) {
+            tcsetattr(0,TCSANOW,&ts[fd]);
+        }
+    }
+
+    set_sigtstp_action(rtsTrue);
+}
+
+static void
+set_sigtstp_action (rtsBool handle)
+{
+    struct sigaction sa;
+    if (handle) {
+        sa.sa_handler = sigtstp_handler;
+    } else {
+        sa.sa_handler = SIG_DFL;
+    }
+    sa.sa_flags = 0;
+    sigemptyset(&sa.sa_mask);
+    sigaction(SIGTSTP, &sa, NULL);
+}
+
+/* -----------------------------------------------------------------------------
  * Install default signal handlers.
  *
  * The RTS installs a default signal handler for catching
@@ -543,6 +616,8 @@ initDefaultHandlers(void)
     if (sigaction(SIGPIPE, &action, &oact) != 0) {
        sysErrorBelch("warning: failed to install SIGPIPE handler");
     }
+
+    set_sigtstp_action(rtsTrue);
 }
 
 void
@@ -562,6 +637,8 @@ resetDefaultHandlers(void)
     if (sigaction(SIGPIPE, &action, NULL) != 0) {
        sysErrorBelch("warning: failed to uninstall SIGPIPE handler");
     }
+
+    set_sigtstp_action(rtsFalse);
 }
 
 void