[Libguestfs] [PATCH] fish: reset the console on ^Z RHBZ#1213844

Maros Zatko
Tue, 08 Mar 2016 19:49:01 -0000

Patch registers SIGTSTP hook where it sends reset terminal color
control sequence using write and fsync.
Handler is installed only if signal is not being ignored.

Patch uses rl_free_line_state and rl_cleanup_after_signal to unhook
readline from terminal, then it calls original TSTP handler using
approach in URL below and again hooks readline using
rl_reset_after_signal.

Handling is based on code from:
http://man7.org/tlpi/code/online/dist/pgsjc/handling_SIGTSTP.c.html

This approach seems to mostly work. User is sometimes able to get
readline into state when it doesn't correctly respond to newline.
^Z and subsequent fg helps there as reset command helps broken
terminal.
---
 fish/fish.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/fish/fish.c b/fish/fish.c
index d26f8b3..ec855f4 100644
--- a/fish/fish.c
+++ b/fish/fish.c
@@ -73,6 +73,7 @@ static void cleanup_readline (void);
 static char *decode_ps1 (const char *);
 static void add_history_line (const char *);
 #endif
+static void tstp_handler (int sig);

 static int override_progress_bars = -1;
 static struct progress_bar *bar = NULL;
@@ -159,6 +160,65 @@ usage (int status)
   exit (status);
 }

+/* Handler for SIGTSTP */
+static void
+tstp_handler (int sig)
+{
+  /* Source:
http://man7.org/tlpi/code/online/dist/pgsjc/handling_SIGTSTP.c.html */
+  sigset_t tstp_mask, prev_mask;
+  int saved_errno;
+  struct sigaction sa;
+
+  /* In case we change 'errno' here */
+  saved_errno = errno;
+
+#ifdef HAVE_LIBREADLINE
+  /* Cleanup readline, unhook from terminal */
+  rl_free_line_state ();
+  rl_cleanup_after_signal ();
+#endif
+
+  /* Reset terminal color */
+#define RESETCOLOR "\033[0m"
+  if (write (1, RESETCOLOR, sizeof RESETCOLOR) != sizeof RESETCOLOR)
+    perror ("write");
+  fsync (1);
+
+  /* Set handling to default */
+  if (signal (SIGTSTP, SIG_DFL) == SIG_ERR)
+    perror ("signal");
+
+  /* Generate a further SIGTSTP */
+  raise (SIGTSTP);
+
+  /* Unblock SIGTSTP; the pending SIGTSTP immediately suspends the program */
+
+  sigemptyset (&tstp_mask);
+  sigaddset (&tstp_mask, SIGTSTP);
+  if (sigprocmask (SIG_UNBLOCK, &tstp_mask, &prev_mask) == -1)
+    perror ("sigprocmask");
+
+  /* Execution resumes here after SIGCONT */
+
+  /* Reblock SIGTSTP */
+  if (sigprocmask (SIG_SETMASK, &prev_mask, NULL) == -1)
+    perror ("sigprocmask");
+
+  /* Reestablish handler */
+  sigemptyset (&sa.sa_mask);
+  sa.sa_flags = SA_RESTART;
+  sa.sa_handler = tstp_handler;
+  if (sigaction (SIGTSTP, &sa, NULL) == -1)
+    perror ("sigaction");
+
+#ifdef HAVE_LIBREADLINE
+  /* Restore readline state */
+  rl_reset_after_signal ();
+#endif
+
+  errno = saved_errno;
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -439,6 +499,22 @@ main (int argc, char *argv[])
       exit (EXIT_FAILURE);
   }

+  /* Register ^Z handler. We need it to reset terminal colors
+   */
+  if (is_interactive) {
+    /* Only establish handler for SIGTSTP if it is not being ignored */
+    if (sigaction(SIGTSTP, NULL, &sa) == -1)
+      perror ("sigaction");
+
+    if (sa.sa_handler != SIG_IGN) {
+      sigemptyset(&sa.sa_mask);
+      sa.sa_flags = SA_RESTART;
+      sa.sa_handler = tstp_handler;
+      if (sigaction(SIGTSTP, &sa, NULL) == -1)
+        perror ("sigaction");
+    }
+  }
+
   /* Old-style -i syntax?  Since -a/-d/-N and -i was disallowed
    * previously, if we have -i without any drives but with something
    * on the command line, it must be old-style syntax.
--
2.5.0