/* Specification. */
#include "term-ostream.h"
+/* Set to 1 to get debugging output regarding signals. */
+#define DEBUG_SIGNALS 0
+
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#if HAVE_TCDRAIN
+#if DEBUG_SIGNALS
+# include <stdio.h>
+#endif
+#if HAVE_TCGETATTR || HAVE_TCDRAIN
# include <termios.h>
+# if !defined NOFLSH /* QNX */
+# define NOFLSH 0
+# endif
+#endif
+#if HAVE_TCGETATTR
+# include <sys/stat.h>
#endif
#include "error.h"
#include "fatal-signal.h"
+#include "sig-handler.h"
#include "full-write.h"
#include "terminfo.h"
+#include "same-inode.h"
#include "xalloc.h"
#include "xsize.h"
#include "gettext.h"
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+/* Restriction for code executed in signal handlers:
+ - Such code must be marked ASYNC_SAFE.
+ - Such code must only access memory locations (variables, struct fields) that
+ are marked 'volatile'. */
+#define ASYNC_SAFE
+
/* =========================== Color primitives =========================== */
/* Convert a cm_common8 color in RGB encoding to BGR encoding.
See the ncurses terminfo(5) manual page, section "Color Handling", for an
explanation why this is needed. */
-static inline int
+static ASYNC_SAFE inline int
color_bgr (term_color_t color)
{
return ((color & 4) >> 2) | (color & 2) | ((color & 1) << 2);
BITFIELD_TYPE(term_underline_t, unsigned int) underline : 1;
} attributes_t;
+/* Compare two sets of attributes for equality. */
+static inline bool
+equal_attributes (attributes_t attr1, attributes_t attr2)
+{
+ return (attr1.color == attr2.color
+ && attr1.bgcolor == attr2.bgcolor
+ && attr1.weight == attr2.weight
+ && attr1.posture == attr2.posture
+ && attr1.underline == attr2.underline);
+}
-/* ============================ EINTR handling ============================ */
-#if HAVE_TCDRAIN
+/* ============================ EINTR handling ============================ */
-/* EINTR handling for tcdrain().
- This function can return -1/EINTR even though we don't have any
+/* EINTR handling for tcgetattr(), tcsetattr(), tcdrain().
+ These functions can return -1/EINTR even when we don't have any
signal handlers set up, namely when we get interrupted via SIGSTOP. */
+#if HAVE_TCGETATTR
+
+static inline int
+nonintr_tcgetattr (int fd, struct termios *tcp)
+{
+ int retval;
+
+ do
+ retval = tcgetattr (fd, tcp);
+ while (retval < 0 && errno == EINTR);
+
+ return retval;
+}
+
+static inline int
+nonintr_tcsetattr (int fd, int flush_mode, const struct termios *tcp)
+{
+ int retval;
+
+ do
+ retval = tcsetattr (fd, flush_mode, tcp);
+ while (retval < 0 && errno == EINTR);
+
+ return retval;
+}
+
+#endif
+
+#if HAVE_TCDRAIN
+
static inline int
nonintr_tcdrain (int fd)
{
#endif
+/* ========================== Logging primitives ========================== */
+
+/* We need logging, especially for the signal handling, because
+ - Debugging through gdb is hardly possible, because gdb produces output
+ by itself and interferes with the process states.
+ - strace is buggy when it comes to SIGTSTP handling: By default, it
+ sends the process a SIGSTOP signal instead of SIGTSTP. It supports
+ an option '-D -I4' to mitigate this, though. Also, race conditions
+ appear with different probability with and without strace.
+ fprintf(stderr) is not possible within async-safe code, because fprintf()
+ may invoke malloc(). */
+
+#if DEBUG_SIGNALS
+
+/* Log a simple message. */
+static ASYNC_SAFE void
+log_message (const char *message)
+{
+ full_write (STDERR_FILENO, message, strlen (message));
+}
+
+#else
+
+# define log_message(message)
+
+#endif
+
+#if HAVE_TCGETATTR || DEBUG_SIGNALS
+
+/* Async-safe implementation of sprintf (str, "%d", n). */
+static ASYNC_SAFE void
+sprintf_integer (char *str, int x)
+{
+ unsigned int y;
+ char buf[20];
+ char *p;
+ size_t n;
+
+ if (x < 0)
+ {
+ *str++ = '-';
+ y = (unsigned int) (-1 - x) + 1;
+ }
+ else
+ y = x;
+
+ p = buf + sizeof (buf);
+ do
+ {
+ *--p = '0' + (y % 10);
+ y = y / 10;
+ }
+ while (y > 0);
+ n = buf + sizeof (buf) - p;
+ memcpy (str, p, n);
+ str[n] = '\0';
+}
+
+#endif
+
+#if HAVE_TCGETATTR
+
+/* Async-safe conversion of errno value to string. */
+static ASYNC_SAFE void
+simple_errno_string (char *str, int errnum)
+{
+ switch (errnum)
+ {
+ case EBADF: strcpy (str, "EBADF"); break;
+ case EINTR: strcpy (str, "EINTR"); break;
+ case EINVAL: strcpy (str, "EINVAL"); break;
+ case EIO: strcpy (str, "EIO"); break;
+ case ENOTTY: strcpy (str, "ENOTTY"); break;
+ default: sprintf_integer (str, errnum); break;
+ }
+}
+
+#endif
+
+#if DEBUG_SIGNALS
+
+/* Async-safe conversion of signal number to name. */
+static ASYNC_SAFE void
+simple_signal_string (char *str, int sig)
+{
+ switch (sig)
+ {
+ /* Fatal signals (see fatal-signal.c). */
+ #ifdef SIGINT
+ case SIGINT: strcpy (str, "SIGINT"); break;
+ #endif
+ #ifdef SIGTERM
+ case SIGTERM: strcpy (str, "SIGTERM"); break;
+ #endif
+ #ifdef SIGHUP
+ case SIGHUP: strcpy (str, "SIGHUP"); break;
+ #endif
+ #ifdef SIGPIPE
+ case SIGPIPE: strcpy (str, "SIGPIPE"); break;
+ #endif
+ #ifdef SIGXCPU
+ case SIGXCPU: strcpy (str, "SIGXCPU"); break;
+ #endif
+ #ifdef SIGXFSZ
+ case SIGXFSZ: strcpy (str, "SIGXFSZ"); break;
+ #endif
+ #ifdef SIGBREAK
+ case SIGBREAK: strcpy (str, "SIGBREAK"); break;
+ #endif
+ /* Stopping signals. */
+ #ifdef SIGTSTP
+ case SIGTSTP: strcpy (str, "SIGTSTP"); break;
+ #endif
+ #ifdef SIGTTIN
+ case SIGTTIN: strcpy (str, "SIGTTIN"); break;
+ #endif
+ #ifdef SIGTTOU
+ case SIGTTOU: strcpy (str, "SIGTTOU"); break;
+ #endif
+ /* Continuing signals. */
+ #ifdef SIGCONT
+ case SIGCONT: strcpy (str, "SIGCONT"); break;
+ #endif
+ default: sprintf_integer (str, sig); break;
+ }
+}
+
+/* Emit a message that a given signal handler is being run. */
+static ASYNC_SAFE void
+log_signal_handler_called (int sig)
+{
+ char message[100];
+ strcpy (message, "Signal handler for signal ");
+ simple_signal_string (message + strlen (message), sig);
+ strcat (message, " called.\n");
+ log_message (message);
+}
+
+#else
+
+# define log_signal_handler_called(sig)
+
+#endif
+
+
/* ============================ term_ostream_t ============================ */
struct term_ostream : struct ostream
char *filename;
/* Values from the terminal type's terminfo/termcap description.
See terminfo(5) for details. */
- /* terminfo termcap */
- int max_colors; /* colors Co */
- int no_color_video; /* ncv NC */
- char *set_a_foreground; /* setaf AF */
- char *set_foreground; /* setf Sf */
- char *set_a_background; /* setab AB */
- char *set_background; /* setb Sb */
- char *orig_pair; /* op op */
- char *enter_bold_mode; /* bold md */
- char *enter_italics_mode; /* sitm ZH */
- char *exit_italics_mode; /* ritm ZR */
- char *enter_underline_mode; /* smul us */
- char *exit_underline_mode; /* rmul ue */
- char *exit_attribute_mode; /* sgr0 me */
+ /* terminfo termcap */
+ int max_colors; /* colors Co */
+ int no_color_video; /* ncv NC */
+ char * volatile set_a_foreground; /* setaf AF */
+ char * volatile set_foreground; /* setf Sf */
+ char * volatile set_a_background; /* setab AB */
+ char * volatile set_background; /* setb Sb */
+ char *orig_pair; /* op op */
+ char * volatile enter_bold_mode; /* bold md */
+ char * volatile enter_italics_mode; /* sitm ZH */
+ char *exit_italics_mode; /* ritm ZR */
+ char * volatile enter_underline_mode; /* smul us */
+ char *exit_underline_mode; /* rmul ue */
+ char *exit_attribute_mode; /* sgr0 me */
/* Inferred values. */
- bool supports_foreground;
- bool supports_background;
- colormodel_t colormodel;
- bool supports_weight;
- bool supports_posture;
- bool supports_underline;
+ bool volatile supports_foreground;
+ bool volatile supports_background;
+ colormodel_t volatile colormodel;
+ bool volatile supports_weight;
+ bool volatile supports_posture;
+ bool volatile supports_underline;
+ /* Inferred values for the exit handler and the signal handlers. */
+ const char * volatile restore_colors;
+ const char * volatile restore_weight;
+ const char * volatile restore_posture;
+ const char * volatile restore_underline;
+ /* Signal handling and tty control. */
+ ttyctl_t volatile tty_control;
+ #if HAVE_TCGETATTR
+ bool volatile same_as_stderr;
+ #endif
/* Variable state, representing past output. */
attributes_t default_attr; /* Default simplified attributes of the
terminal. */
return attr;
}
-/* While a line is being output, we need to be careful to restore the
- terminal's settings in case of a fatal signal or an exit() call. */
+/* There are several situations which can cause garbled output on the terminal's
+ screen:
+ (1) When the program calls exit() after calling flush_to_current_style,
+ the program would terminate and leave the terminal in a non-default
+ state.
+ (2) When the program is interrupted through a fatal signal, the terminal
+ would be left in a non-default state.
+ (3) When the program is stopped through a stopping signal, the terminal
+ would be left (for temporary use by other programs) in a non-default
+ state.
+ (4) When a foreground process receives a SIGINT, the kernel(!) prints '^C'.
+ On Linux, the place where this happens is
+ linux-5.0/drivers/tty/n_tty.c:713..730
+ within a call sequence
+ n_tty_receive_signal_char (n_tty.c:1245..1246)
+ -> commit_echoes (n_tty.c:792)
+ -> __process_echoes (n_tty.c:713..730).
+ (5) When a signal is sent, the output buffer is cleared.
+ On Linux, this output buffer consists of the "echo buffer" in the tty
+ and the "output buffer" in the driver. The place where this happens is
+ linux-5.0/drivers/tty/n_tty.c:1133..1140
+ within a call
+ isig (n_tty.c:1133..1140).
+
+ How do we mitigate these problems?
+ (1) We install an exit handler that restores the terminal to the default
+ state.
+ (2) If tty_control is TTYCTL_PARTIAL or TTYCTL_FULL:
+ For some of the fatal signals (see gnulib's 'fatal-signal' module for
+ the precise list), we install a handler that attempts to restore the
+ terminal to the default state. Since the terminal may be in the middle
+ of outputting an escape sequence at this point, the first escape
+ sequence emitted from this handler may have no effect and produce
+ garbled characters instead. Therefore the handler outputs the cleanup
+ sequence twice.
+ For the other fatal signals, we don't do anything.
+ (3) If tty_control is TTYCTL_PARTIAL or TTYCTL_FULL:
+ For some of the stopping signals (SIGTSTP, SIGTTIN, SIGTTOU), we install
+ a handler that attempts to restore the terminal to the default state.
+ For SIGCONT, we install a handler that does the opposite: it puts the
+ terminal into the desired state again.
+ For SIGSTOP, we cannot do anything.
+ (4) If tty_control is TTYCTL_FULL:
+ The kernel's action depends on L_ECHO(tty) and L_ISIG(tty), that is, on
+ the local modes of the tty (see
+ <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html>
+ section 11.2.5). We don't want to change L_ISIG; hence we change L_ECHO.
+ So, we disable the ECHO local flag of the tty; the equivalent command is
+ 'stty -echo'.
+ (5) If tty_control is TTYCTL_FULL:
+ The kernel's action depends on !L_NOFLSH(tty), that is, again on the
+ local modes of the tty (see
+ <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html>
+ section 11.2.5). So, we enable the NOFLSH local flag of the tty; the
+ equivalent command is 'stty noflsh'.
+ For terminals with a baud rate < 9600 this is suboptimal. For this case
+ - where the traditional flushing behaviour makes sense - we would use a
+ technique that involves tcdrain(), TIOCOUTQ, and usleep() when it is OK
+ to disable NOFLSH.
+
+ Regarding (4) and (5), there is a complication: Changing the local modes is
+ done through tcsetattr(). However, when the process is put into the
+ background, tcsetattr() does not operate the same way as when the process is
+ running in the foreground.
+ To test this kind of behaviour, use the 'color-filter' example like this:
+ $ yes | ./filter '.*'
+ <Ctrl-Z>
+ $ bg 1
+ We have three possible implementation options:
+ * If we don't ignore the signal SIGTTOU:
+ If the TOSTOP bit in the terminal's local mode is clear (command
+ equivalent: 'stty -tostop') and the process is put into the background,
+ normal output would continue (per POSIX
+ <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html>
+ section 11.2.5) but tcsetattr() calls would cause it to stop due to
+ a SIGTTOU signal (per POSIX
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html>).
+ Thus, the program would behave differently with term-ostream than
+ without.
+ * If we ignore the signal SIGTTOU when the TOSTOP bit in the terminal's
+ local mode is clear (i.e. when (tc.c_lflag & TOSTOP) == 0):
+ The tcsetattr() calls do not stop the process, but they don't have the
+ desired effect.
+ On Linux, when I put the process into the background and then kill it with
+ signal SIGINT, I can see that the last operation on the terminal settings
+ (as shown by 'strace') is
+ ioctl(1, TCSETSW, {B38400 opost isig icanon echo ...}) = 0
+ and yet, once the process is terminated, the terminal settings contain
+ '-echo', not 'echo'.
+ * Don't call tcsetattr() if the process is not in the foreground.
+ This approach produces reliable results.
+
+ Blocking some signals while a non-default style is active is *not* useful:
+ - It does not help against (1), since exit() is not a signal.
+ - Signal handlers are the better approach against (2) and (3).
+ - It does not help against (4) and (5), because the kernel's actions happen
+ outside the process. */
+#define BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT 0
+
+/* File descriptor of the currently open term_ostream_t. */
+static int volatile term_fd = -1;
+
+#if HAVE_TCGETATTR
+
+/* Status of the process group of term_fd. */
+typedef enum
+{
+ PGRP_UNKNOWN = 0, /* term_fd < 0. Unknown status. */
+ PGRP_NO_TTY, /* term_fd >= 0 but is not connected to a tty. */
+ PGRP_IN_FOREGROUND, /* term_fd >= 0 is a tty. This process is running in
+ the foreground. */
+ PGRP_IN_BACKGROUND /* term_fd >= 0 is a tty. This process is running in
+ the background. */
+} pgrp_status_t;
+static pgrp_status_t volatile pgrp_status = PGRP_UNKNOWN;
+
+/* Update pgrp_status, depending on term_fd. */
+static ASYNC_SAFE void
+update_pgrp_status (void)
+{
+ int fd = term_fd;
+ if (fd < 0)
+ {
+ pgrp_status = PGRP_UNKNOWN;
+ log_message ("pgrp_status = PGRP_UNKNOWN\n");
+ }
+ else
+ {
+ pid_t p = tcgetpgrp (fd);
+ if (p < 0)
+ {
+ pgrp_status = PGRP_NO_TTY;
+ log_message ("pgrp_status = PGRP_NO_TTY\n");
+ }
+ else
+ {
+ /* getpgrp () changes when the process gets put into the background
+ by a shell that implements job control. */
+ if (p == getpgrp ())
+ {
+ pgrp_status = PGRP_IN_FOREGROUND;
+ log_message ("pgrp_status = PGRP_IN_FOREGROUND\n");
+ }
+ else
+ {
+ pgrp_status = PGRP_IN_BACKGROUND;
+ log_message ("pgrp_status = PGRP_IN_BACKGROUND\n");
+ }
+ }
+ }
+}
+
+#else
+
+# define update_pgrp_status()
-/* File descriptor to which out_char shall output escape sequences. */
-static int out_fd = -1;
+#endif
-/* Filename of out_fd. */
-static const char *out_filename;
+/* Stream that contains information about how the various out_* functions shall
+ do output. */
+static term_ostream_t volatile out_stream;
-/* Output a single char to out_fd. Ignore errors. */
+/* File descriptor to which out_char and out_char_unchecked shall output escape
+ sequences.
+ Same as (out_stream != NULL ? out_stream->fd : -1). */
+static int volatile out_fd = -1;
+
+/* Filename of out_fd.
+ Same as (out_stream != NULL ? out_stream->filename : NULL). */
+static const char * volatile out_filename;
+
+/* Signal error after full_write failed. */
+static void
+out_error ()
+{
+ error (EXIT_FAILURE, errno, _("error writing to %s"), out_filename);
+}
+
+/* Output a single char to out_fd. */
static int
+out_char (int c)
+{
+ char bytes[1];
+
+ bytes[0] = (char)c;
+ /* We have to write directly to the file descriptor, not to a buffer with
+ the same destination, because of the padding and sleeping that tputs()
+ does. */
+ if (full_write (out_fd, bytes, 1) < 1)
+ out_error ();
+ return 0;
+}
+
+/* Output a single char to out_fd. Ignore errors. */
+static ASYNC_SAFE int
out_char_unchecked (int c)
{
char bytes[1];
return 0;
}
-/* State that informs the exit handler what to do. */
-static const char *restore_colors;
-static const char *restore_weight;
-static const char *restore_posture;
-static const char *restore_underline;
+/* Output escape sequences to switch the foreground color to NEW_COLOR. */
+static ASYNC_SAFE void
+out_color_change (term_ostream_t stream, term_color_t new_color,
+ bool async_safe)
+{
+ assert (stream->supports_foreground);
+ assert (new_color != COLOR_DEFAULT);
+ switch (stream->colormodel)
+ {
+ case cm_common8:
+ assert (new_color >= 0 && new_color < 8);
+ if (stream->set_a_foreground != NULL)
+ tputs (tparm (stream->set_a_foreground, color_bgr (new_color)),
+ 1, async_safe ? out_char_unchecked : out_char);
+ else
+ tputs (tparm (stream->set_foreground, new_color),
+ 1, async_safe ? out_char_unchecked : out_char);
+ break;
+ /* When we are dealing with an xterm, there is no need to go through
+ tputs() because we know there is no padding and sleeping. */
+ case cm_xterm8:
+ assert (new_color >= 0 && new_color < 8);
+ {
+ char bytes[5];
+ bytes[0] = 0x1B; bytes[1] = '[';
+ bytes[2] = '3'; bytes[3] = '0' + new_color;
+ bytes[4] = 'm';
+ if (full_write (out_fd, bytes, 5) < 5)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ case cm_xterm16:
+ assert (new_color >= 0 && new_color < 16);
+ {
+ char bytes[5];
+ bytes[0] = 0x1B; bytes[1] = '[';
+ if (new_color < 8)
+ {
+ bytes[2] = '3'; bytes[3] = '0' + new_color;
+ }
+ else
+ {
+ bytes[2] = '9'; bytes[3] = '0' + (new_color - 8);
+ }
+ bytes[4] = 'm';
+ if (full_write (out_fd, bytes, 5) < 5)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ case cm_xterm88:
+ assert (new_color >= 0 && new_color < 88);
+ {
+ char bytes[10];
+ char *p;
+ bytes[0] = 0x1B; bytes[1] = '[';
+ bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
+ bytes[5] = '5'; bytes[6] = ';';
+ p = bytes + 7;
+ if (new_color >= 10)
+ *p++ = '0' + (new_color / 10);
+ *p++ = '0' + (new_color % 10);
+ *p++ = 'm';
+ if (full_write (out_fd, bytes, p - bytes) < p - bytes)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ case cm_xterm256:
+ assert (new_color >= 0 && new_color < 256);
+ {
+ char bytes[11];
+ char *p;
+ bytes[0] = 0x1B; bytes[1] = '[';
+ bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
+ bytes[5] = '5'; bytes[6] = ';';
+ p = bytes + 7;
+ if (new_color >= 100)
+ *p++ = '0' + (new_color / 100);
+ if (new_color >= 10)
+ *p++ = '0' + ((new_color % 100) / 10);
+ *p++ = '0' + (new_color % 10);
+ *p++ = 'm';
+ if (full_write (out_fd, bytes, p - bytes) < p - bytes)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ default:
+ abort ();
+ }
+}
+
+/* Output escape sequences to switch the background color to NEW_BGCOLOR. */
+static ASYNC_SAFE void
+out_bgcolor_change (term_ostream_t stream, term_color_t new_bgcolor,
+ bool async_safe)
+{
+ assert (stream->supports_background);
+ assert (new_bgcolor != COLOR_DEFAULT);
+ switch (stream->colormodel)
+ {
+ case cm_common8:
+ assert (new_bgcolor >= 0 && new_bgcolor < 8);
+ if (stream->set_a_background != NULL)
+ tputs (tparm (stream->set_a_background, color_bgr (new_bgcolor)),
+ 1, async_safe ? out_char_unchecked : out_char);
+ else
+ tputs (tparm (stream->set_background, new_bgcolor),
+ 1, async_safe ? out_char_unchecked : out_char);
+ break;
+ /* When we are dealing with an xterm, there is no need to go through
+ tputs() because we know there is no padding and sleeping. */
+ case cm_xterm8:
+ assert (new_bgcolor >= 0 && new_bgcolor < 8);
+ {
+ char bytes[5];
+ bytes[0] = 0x1B; bytes[1] = '[';
+ bytes[2] = '4'; bytes[3] = '0' + new_bgcolor;
+ bytes[4] = 'm';
+ if (full_write (out_fd, bytes, 5) < 5)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ case cm_xterm16:
+ assert (new_bgcolor >= 0 && new_bgcolor < 16);
+ {
+ char bytes[6];
+ bytes[0] = 0x1B; bytes[1] = '[';
+ if (new_bgcolor < 8)
+ {
+ bytes[2] = '4'; bytes[3] = '0' + new_bgcolor;
+ bytes[4] = 'm';
+ if (full_write (out_fd, bytes, 5) < 5)
+ if (!async_safe)
+ out_error ();
+ }
+ else
+ {
+ bytes[2] = '1'; bytes[3] = '0';
+ bytes[4] = '0' + (new_bgcolor - 8); bytes[5] = 'm';
+ if (full_write (out_fd, bytes, 6) < 6)
+ if (!async_safe)
+ out_error ();
+ }
+ }
+ break;
+ case cm_xterm88:
+ assert (new_bgcolor >= 0 && new_bgcolor < 88);
+ {
+ char bytes[10];
+ char *p;
+ bytes[0] = 0x1B; bytes[1] = '[';
+ bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
+ bytes[5] = '5'; bytes[6] = ';';
+ p = bytes + 7;
+ if (new_bgcolor >= 10)
+ *p++ = '0' + (new_bgcolor / 10);
+ *p++ = '0' + (new_bgcolor % 10);
+ *p++ = 'm';
+ if (full_write (out_fd, bytes, p - bytes) < p - bytes)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ case cm_xterm256:
+ assert (new_bgcolor >= 0 && new_bgcolor < 256);
+ {
+ char bytes[11];
+ char *p;
+ bytes[0] = 0x1B; bytes[1] = '[';
+ bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
+ bytes[5] = '5'; bytes[6] = ';';
+ p = bytes + 7;
+ if (new_bgcolor >= 100)
+ *p++ = '0' + (new_bgcolor / 100);
+ if (new_bgcolor >= 10)
+ *p++ = '0' + ((new_bgcolor % 100) / 10);
+ *p++ = '0' + (new_bgcolor % 10);
+ *p++ = 'm';
+ if (full_write (out_fd, bytes, p - bytes) < p - bytes)
+ if (!async_safe)
+ out_error ();
+ }
+ break;
+ default:
+ abort ();
+ }
+}
+
+/* Output escape sequences to switch the weight to NEW_WEIGHT. */
+static ASYNC_SAFE void
+out_weight_change (term_ostream_t stream, term_weight_t new_weight,
+ bool async_safe)
+{
+ assert (stream->supports_weight);
+ assert (new_weight != WEIGHT_DEFAULT);
+ /* This implies: */
+ assert (new_weight == WEIGHT_BOLD);
+ tputs (stream->enter_bold_mode,
+ 1, async_safe ? out_char_unchecked : out_char);
+}
+
+/* Output escape sequences to switch the posture to NEW_POSTURE. */
+static ASYNC_SAFE void
+out_posture_change (term_ostream_t stream, term_posture_t new_posture,
+ bool async_safe)
+{
+ assert (stream->supports_posture);
+ assert (new_posture != POSTURE_DEFAULT);
+ /* This implies: */
+ assert (new_posture == POSTURE_ITALIC);
+ tputs (stream->enter_italics_mode,
+ 1, async_safe ? out_char_unchecked : out_char);
+}
+
+/* Output escape sequences to switch the underline to NEW_UNDERLINE. */
+static ASYNC_SAFE void
+out_underline_change (term_ostream_t stream, term_underline_t new_underline,
+ bool async_safe)
+{
+ assert (stream->supports_underline);
+ assert (new_underline != UNDERLINE_DEFAULT);
+ /* This implies: */
+ assert (new_underline == UNDERLINE_ON);
+ tputs (stream->enter_underline_mode,
+ 1, async_safe ? out_char_unchecked : out_char);
+}
/* The exit handler. */
static void
restore (void)
{
- /* Only do something while some output was interrupted. */
- if (out_fd >= 0)
+ /* Only do something while some output was started but not completed. */
+ if (out_stream != NULL)
{
- if (restore_colors != NULL)
- tputs (restore_colors, 1, out_char_unchecked);
- if (restore_weight != NULL)
- tputs (restore_weight, 1, out_char_unchecked);
- if (restore_posture != NULL)
- tputs (restore_posture, 1, out_char_unchecked);
- if (restore_underline != NULL)
- tputs (restore_underline, 1, out_char_unchecked);
+ if (out_stream->restore_colors != NULL)
+ tputs (out_stream->restore_colors, 1, out_char_unchecked);
+ if (out_stream->restore_weight != NULL)
+ tputs (out_stream->restore_weight, 1, out_char_unchecked);
+ if (out_stream->restore_posture != NULL)
+ tputs (out_stream->restore_posture, 1, out_char_unchecked);
+ if (out_stream->restore_underline != NULL)
+ tputs (out_stream->restore_underline, 1, out_char_unchecked);
+ }
+}
+
+#if HAVE_TCGETATTR
+
+/* Return a failure message after tcsetattr() failed. */
+static ASYNC_SAFE void
+tcsetattr_failed (char message[100], const char *caller)
+{
+ int errnum = errno;
+ strcpy (message, caller);
+ strcat (message, ": tcsetattr(fd=");
+ sprintf_integer (message + strlen (message), out_fd);
+ strcat (message, ") failed, errno=");
+ simple_errno_string (message + strlen (message), errnum);
+ strcat (message, "\n");
+}
+
+/* True when orig_lflag represents the original tc.c_lflag. */
+static bool volatile orig_lflag_set;
+static tcflag_t volatile orig_lflag;
+
+/* Modifies the tty's local mode, preparing for non-default terminal state.
+ Used only when the out_stream's tty_control is TTYCTL_FULL. */
+static ASYNC_SAFE void
+clobber_local_mode (void)
+{
+ /* Here, out_fd == term_fd. */
+ struct termios tc;
+ if (pgrp_status == PGRP_IN_FOREGROUND
+ && nonintr_tcgetattr (out_fd, &tc) >= 0)
+ {
+ if (!orig_lflag_set)
+ orig_lflag = tc.c_lflag;
+ /* Set orig_lflag_set to true before actually modifying the tty's local
+ mode, because restore_local_mode does nothing if orig_lflag_set is
+ false. */
+ orig_lflag_set = true;
+ tc.c_lflag &= ~ECHO;
+ tc.c_lflag |= NOFLSH;
+ if (nonintr_tcsetattr (out_fd, TCSANOW, &tc) < 0)
+ {
+ /* Since tcsetattr failed, restore_local_mode does not need to restore
+ anything. Set orig_lflag_set to false to indicate this. */
+ orig_lflag_set = false;
+ {
+ char message[100];
+ tcsetattr_failed (message, "term_ostream:"":clobber_local_mode");
+ full_write (STDERR_FILENO, message, strlen (message));
+ }
+ }
+ }
+}
+
+/* Modifies the tty's local mode, once the terminal is back to the default state.
+ Returns true if ECHO was turned off.
+ Used only when the out_stream's tty_control is TTYCTL_FULL. */
+static ASYNC_SAFE bool
+restore_local_mode (void)
+{
+ /* Here, out_fd == term_fd. */
+ bool echo_was_off = false;
+ /* Nothing to do if !orig_lflag_set. */
+ if (orig_lflag_set)
+ {
+ struct termios tc;
+ if (nonintr_tcgetattr (out_fd, &tc) >= 0)
+ {
+ echo_was_off = (tc.c_lflag & ECHO) == 0;
+ tc.c_lflag = orig_lflag;
+ if (nonintr_tcsetattr (out_fd, TCSADRAIN, &tc) < 0)
+ {
+ char message[100];
+ tcsetattr_failed (message, "term_ostream:"":restore_local_mode");
+ full_write (STDERR_FILENO, message, strlen (message));
+ }
+ }
+ orig_lflag_set = false;
}
+ return echo_was_off;
}
-/* The list of signals whose default behaviour is to stop the program. */
-static int stopping_signals[] =
+#endif
+
+#if defined SIGCONT
+
+/* The list of signals whose default behaviour is to stop or continue the
+ program. */
+static int const job_control_signals[] =
{
-#ifdef SIGTSTP
+ #ifdef SIGTSTP
SIGTSTP,
-#endif
-#ifdef SIGTTIN
+ #endif
+ #ifdef SIGTTIN
SIGTTIN,
-#endif
-#ifdef SIGTTOU
+ #endif
+ #ifdef SIGTTOU
SIGTTOU,
-#endif
+ #endif
+ #ifdef SIGCONT
+ SIGCONT,
+ #endif
0
};
-#define num_stopping_signals (SIZEOF (stopping_signals) - 1)
+# define num_job_control_signals (SIZEOF (job_control_signals) - 1)
+
+#endif
+
+/* The following signals are relevant because they do tputs invocations:
+ - fatal signals,
+ - stopping signals,
+ - continuing signals (SIGCONT). */
-static sigset_t stopping_signal_set;
+static sigset_t relevant_signal_set;
+static bool relevant_signal_set_initialized = false;
static void
-init_stopping_signal_set ()
+init_relevant_signal_set ()
{
- static bool stopping_signal_set_initialized = false;
- if (!stopping_signal_set_initialized)
+ if (!relevant_signal_set_initialized)
{
+ int fatal_signals[64];
+ size_t num_fatal_signals;
size_t i;
- sigemptyset (&stopping_signal_set);
- for (i = 0; i < num_stopping_signals; i++)
- sigaddset (&stopping_signal_set, stopping_signals[i]);
+ num_fatal_signals = get_fatal_signals (fatal_signals);
- stopping_signal_set_initialized = true;
+ sigemptyset (&relevant_signal_set);
+ for (i = 0; i < num_fatal_signals; i++)
+ sigaddset (&relevant_signal_set, fatal_signals[i]);
+ #if defined SIGCONT
+ for (i = 0; i < num_job_control_signals; i++)
+ sigaddset (&relevant_signal_set, job_control_signals[i]);
+ #endif
+
+ relevant_signal_set_initialized = true;
}
}
-/* Temporarily delay the stopping signals. */
-static inline void
-block_stopping_signals ()
+/* Temporarily delay the relevant signals. */
+static ASYNC_SAFE inline void
+block_relevant_signals ()
{
- init_stopping_signal_set ();
- sigprocmask (SIG_BLOCK, &stopping_signal_set, NULL);
+ /* The caller must ensure that init_relevant_signal_set () was already
+ called. */
+ if (!relevant_signal_set_initialized)
+ abort ();
+
+ sigprocmask (SIG_BLOCK, &relevant_signal_set, NULL);
}
-/* Stop delaying the stopping signals. */
-static inline void
-unblock_stopping_signals ()
+/* Stop delaying the relevant signals. */
+static ASYNC_SAFE inline void
+unblock_relevant_signals ()
{
- init_stopping_signal_set ();
- sigprocmask (SIG_UNBLOCK, &stopping_signal_set, NULL);
+ sigprocmask (SIG_UNBLOCK, &relevant_signal_set, NULL);
}
-/* Compare two sets of attributes for equality. */
-static inline bool
-equal_attributes (attributes_t attr1, attributes_t attr2)
+/* Determines whether a signal is ignored. */
+static ASYNC_SAFE bool
+is_ignored (int sig)
{
- return (attr1.color == attr2.color
- && attr1.bgcolor == attr2.bgcolor
- && attr1.weight == attr2.weight
- && attr1.posture == attr2.posture
- && attr1.underline == attr2.underline);
+ struct sigaction action;
+
+ return (sigaction (sig, NULL, &action) >= 0
+ && get_handler (&action) == SIG_IGN);
}
-/* Signal error after full_write failed. */
+#if HAVE_TCGETATTR
+
+/* Write the same signal marker that the kernel would have printed if ECHO had
+ been turned on. See (4) above.
+ This is a makeshift and is not perfect:
+ - When stderr refers to a different target than out_stream->fd, it is too
+ hairy to write the signal marker.
+ - In some cases, when the signal was generated right before and delivered
+ right after a clobber_local_mode invocation, the result is that the
+ marker appears twice, e.g. ^C^C. This occurs only with a small
+ probability.
+ - In some cases, when the signal was generated right before and delivered
+ right after a restore_local_mode invocation, the result is that the
+ marker does not appear at all. This occurs only with a small
+ probability.
+ To test this kind of behaviour, use the 'color-filter' example like this:
+ $ yes | ./filter '.*'
+ */
+static ASYNC_SAFE void
+show_signal_marker (int sig)
+{
+ /* Write to stderr, not to out_stream->fd, because out_stream->fd is often
+ logged or used with 'less -R'. */
+ if (out_stream != NULL && out_stream->same_as_stderr)
+ switch (sig)
+ {
+ /* The kernel's action when the user presses the INTR key. */
+ case SIGINT:
+ full_write (STDERR_FILENO, "^C", 2); break;
+ /* The kernel's action when the user presses the SUSP key. */
+ case SIGTSTP:
+ full_write (STDERR_FILENO, "^Z", 2); break;
+ /* The kernel's action when the user presses the QUIT key. */
+ case SIGQUIT:
+ full_write (STDERR_FILENO, "^\\", 2); break;
+ default: break;
+ }
+}
+
+#endif
+
+/* The main code of the signal handler for fatal signals and stopping signals.
+ It is reentrant. */
+static ASYNC_SAFE void
+fatal_or_stopping_signal_handler (int sig)
+{
+ bool echo_was_off = false;
+ /* Only do something while some output was interrupted. */
+ if (out_stream != NULL && out_stream->tty_control != TTYCTL_NONE)
+ {
+ unsigned int i;
+
+ /* Block the relevant signals. This is needed, because the tputs
+ invocations below are not reentrant. */
+ block_relevant_signals ();
+
+ /* Restore the terminal to the default state. */
+ for (i = 0; i < 2; i++)
+ {
+ if (out_stream->restore_colors != NULL)
+ tputs (out_stream->restore_colors, 1, out_char_unchecked);
+ if (out_stream->restore_weight != NULL)
+ tputs (out_stream->restore_weight, 1, out_char_unchecked);
+ if (out_stream->restore_posture != NULL)
+ tputs (out_stream->restore_posture, 1, out_char_unchecked);
+ if (out_stream->restore_underline != NULL)
+ tputs (out_stream->restore_underline, 1, out_char_unchecked);
+ }
+ #if HAVE_TCGETATTR
+ if (out_stream->tty_control == TTYCTL_FULL)
+ {
+ /* Restore the local mode, once the tputs calls above have reached
+ their destination. */
+ echo_was_off = restore_local_mode ();
+ }
+ #endif
+
+ /* Unblock the relevant signals. */
+ unblock_relevant_signals ();
+ }
+
+ #if HAVE_TCGETATTR
+ if (echo_was_off)
+ show_signal_marker (sig);
+ #endif
+}
+
+/* The signal handler for fatal signals.
+ It is reentrant. */
+static ASYNC_SAFE void
+fatal_signal_handler (int sig)
+{
+ log_signal_handler_called (sig);
+ fatal_or_stopping_signal_handler (sig);
+}
+
+#if defined SIGCONT
+
+/* The signal handler for stopping signals.
+ It is reentrant. */
+static ASYNC_SAFE void
+stopping_signal_handler (int sig)
+{
+ log_signal_handler_called (sig);
+ fatal_or_stopping_signal_handler (sig);
+
+ /* Now execute the signal's default action.
+ We reinstall the handler later, during the SIGCONT handler. */
+ {
+ struct sigaction action;
+ action.sa_handler = SIG_DFL;
+ action.sa_flags = SA_NODEFER;
+ sigemptyset (&action.sa_mask);
+ sigaction (sig, &action, NULL);
+ }
+ raise (sig);
+}
+
+/* The signal handler for SIGCONT.
+ It is reentrant. */
+static ASYNC_SAFE void
+continuing_signal_handler (int sig)
+{
+ log_signal_handler_called (sig);
+ update_pgrp_status ();
+ /* Only do something while some output was interrupted. */
+ if (out_stream != NULL && out_stream->tty_control != TTYCTL_NONE)
+ {
+ /* Reinstall the signals handlers removed in stopping_signal_handler. */
+ {
+ unsigned int i;
+
+ for (i = 0; i < num_job_control_signals; i++)
+ {
+ int sig = job_control_signals[i];
+
+ if (sig != SIGCONT && !is_ignored (sig))
+ {
+ struct sigaction action;
+ action.sa_handler = &stopping_signal_handler;
+ /* If we get a stopping or continuing signal while executing
+ stopping_signal_handler or continuing_signal_handler, enter
+ it recursively, since it is reentrant.
+ Hence no SA_RESETHAND. */
+ action.sa_flags = SA_NODEFER;
+ sigemptyset (&action.sa_mask);
+ sigaction (sig, &action, NULL);
+ }
+ }
+ }
+
+ /* Block the relevant signals. This is needed, because the tputs
+ invocations done inside the out_* calls below are not reentrant. */
+ block_relevant_signals ();
+
+ #if HAVE_TCGETATTR
+ if (out_stream->tty_control == TTYCTL_FULL)
+ {
+ /* Modify the local mode. */
+ clobber_local_mode ();
+ }
+ #endif
+ /* Set the terminal attributes. */
+ attributes_t new_attr = out_stream->active_attr;
+ if (new_attr.color != COLOR_DEFAULT)
+ out_color_change (out_stream, new_attr.color, true);
+ if (new_attr.bgcolor != COLOR_DEFAULT)
+ out_bgcolor_change (out_stream, new_attr.bgcolor, true);
+ if (new_attr.weight != WEIGHT_DEFAULT)
+ out_weight_change (out_stream, new_attr.weight, true);
+ if (new_attr.posture != POSTURE_DEFAULT)
+ out_posture_change (out_stream, new_attr.posture, true);
+ if (new_attr.underline != UNDERLINE_DEFAULT)
+ out_underline_change (out_stream, new_attr.underline, true);
+
+ /* Unblock the relevant signals. */
+ unblock_relevant_signals ();
+ }
+}
+
+/* Ensure the signal handlers are installed.
+ Once they are installed, we leave them installed. It's not worth
+ installing and uninstalling them each time we switch the terminal to a
+ non-default state and back; instead we set out_stream to tell the signal
+ handler whether it has something to do or not. */
+
static void
-out_error ()
+ensure_continuing_signal_handler (void)
{
- error (EXIT_FAILURE, errno, _("error writing to %s"), out_filename);
+ static bool signal_handler_installed = false;
+
+ if (!signal_handler_installed)
+ {
+ int sig = SIGCONT;
+ struct sigaction action;
+ action.sa_handler = &continuing_signal_handler;
+ /* If we get a stopping or continuing signal while executing
+ continuing_signal_handler, enter it recursively, since it is
+ reentrant. Hence no SA_RESETHAND. */
+ action.sa_flags = SA_NODEFER;
+ sigemptyset (&action.sa_mask);
+ sigaction (sig, &action, NULL);
+
+ signal_handler_installed = true;
+ }
}
-/* Output a single char to out_fd. */
-static int
-out_char (int c)
+#endif
+
+static void
+ensure_other_signal_handlers (void)
{
- char bytes[1];
+ static bool signal_handlers_installed = false;
- bytes[0] = (char)c;
- /* We have to write directly to the file descriptor, not to a buffer with
- the same destination, because of the padding and sleeping that tputs()
- does. */
- if (full_write (out_fd, bytes, 1) < 1)
- out_error ();
- return 0;
+ if (!signal_handlers_installed)
+ {
+ unsigned int i;
+
+ /* Install the handlers for the fatal signals. */
+ at_fatal_signal (fatal_signal_handler);
+
+ #if defined SIGCONT
+
+ /* Install the handlers for the stopping and continuing signals. */
+ for (i = 0; i < num_job_control_signals; i++)
+ {
+ int sig = job_control_signals[i];
+
+ if (sig == SIGCONT)
+ /* Already handled in ensure_continuing_signal_handler. */
+ ;
+ else if (!is_ignored (sig))
+ {
+ struct sigaction action;
+ action.sa_handler = &stopping_signal_handler;
+ /* If we get a stopping or continuing signal while executing
+ stopping_signal_handler, enter it recursively, since it is
+ reentrant. Hence no SA_RESETHAND. */
+ action.sa_flags = SA_NODEFER;
+ sigemptyset (&action.sa_mask);
+ sigaction (sig, &action, NULL);
+ }
+ #if DEBUG_SIGNALS
+ else
+ {
+ fprintf (stderr, "Signal %d is ignored. Not installing a handler!\n",
+ sig);
+ fflush (stderr);
+ }
+ #endif
+ }
+
+ #endif
+
+ signal_handlers_installed = true;
+ }
}
/* Output escape sequences to switch from STREAM->ACTIVE_ATTR to NEW_ATTR,
if (new_attr.color != old_attr.color
|| (cleared_attributes && new_attr.color != COLOR_DEFAULT))
{
- assert (stream->supports_foreground);
- assert (new_attr.color != COLOR_DEFAULT);
- switch (stream->colormodel)
- {
- case cm_common8:
- assert (new_attr.color >= 0 && new_attr.color < 8);
- if (stream->set_a_foreground != NULL)
- tputs (tparm (stream->set_a_foreground,
- color_bgr (new_attr.color)),
- 1, out_char);
- else
- tputs (tparm (stream->set_foreground, new_attr.color),
- 1, out_char);
- break;
- /* When we are dealing with an xterm, there is no need to go through
- tputs() because we know there is no padding and sleeping. */
- case cm_xterm8:
- assert (new_attr.color >= 0 && new_attr.color < 8);
- {
- char bytes[5];
- bytes[0] = 0x1B; bytes[1] = '[';
- bytes[2] = '3'; bytes[3] = '0' + new_attr.color;
- bytes[4] = 'm';
- if (full_write (out_fd, bytes, 5) < 5)
- out_error ();
- }
- break;
- case cm_xterm16:
- assert (new_attr.color >= 0 && new_attr.color < 16);
- {
- char bytes[5];
- bytes[0] = 0x1B; bytes[1] = '[';
- if (new_attr.color < 8)
- {
- bytes[2] = '3'; bytes[3] = '0' + new_attr.color;
- }
- else
- {
- bytes[2] = '9'; bytes[3] = '0' + (new_attr.color - 8);
- }
- bytes[4] = 'm';
- if (full_write (out_fd, bytes, 5) < 5)
- out_error ();
- }
- break;
- case cm_xterm88:
- assert (new_attr.color >= 0 && new_attr.color < 88);
- {
- char bytes[10];
- char *p;
- bytes[0] = 0x1B; bytes[1] = '[';
- bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
- bytes[5] = '5'; bytes[6] = ';';
- p = bytes + 7;
- if (new_attr.color >= 10)
- *p++ = '0' + (new_attr.color / 10);
- *p++ = '0' + (new_attr.color % 10);
- *p++ = 'm';
- if (full_write (out_fd, bytes, p - bytes) < p - bytes)
- out_error ();
- }
- break;
- case cm_xterm256:
- assert (new_attr.color >= 0 && new_attr.color < 256);
- {
- char bytes[11];
- char *p;
- bytes[0] = 0x1B; bytes[1] = '[';
- bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
- bytes[5] = '5'; bytes[6] = ';';
- p = bytes + 7;
- if (new_attr.color >= 100)
- *p++ = '0' + (new_attr.color / 100);
- if (new_attr.color >= 10)
- *p++ = '0' + ((new_attr.color % 100) / 10);
- *p++ = '0' + (new_attr.color % 10);
- *p++ = 'm';
- if (full_write (out_fd, bytes, p - bytes) < p - bytes)
- out_error ();
- }
- break;
- default:
- abort ();
- }
+ out_color_change (stream, new_attr.color, false);
}
if (new_attr.bgcolor != old_attr.bgcolor
|| (cleared_attributes && new_attr.bgcolor != COLOR_DEFAULT))
{
- assert (stream->supports_background);
- assert (new_attr.bgcolor != COLOR_DEFAULT);
- switch (stream->colormodel)
- {
- case cm_common8:
- assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 8);
- if (stream->set_a_background != NULL)
- tputs (tparm (stream->set_a_background,
- color_bgr (new_attr.bgcolor)),
- 1, out_char);
- else
- tputs (tparm (stream->set_background, new_attr.bgcolor),
- 1, out_char);
- break;
- /* When we are dealing with an xterm, there is no need to go through
- tputs() because we know there is no padding and sleeping. */
- case cm_xterm8:
- assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 8);
- {
- char bytes[5];
- bytes[0] = 0x1B; bytes[1] = '[';
- bytes[2] = '4'; bytes[3] = '0' + new_attr.bgcolor;
- bytes[4] = 'm';
- if (full_write (out_fd, bytes, 5) < 5)
- out_error ();
- }
- break;
- case cm_xterm16:
- assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 16);
- {
- char bytes[6];
- bytes[0] = 0x1B; bytes[1] = '[';
- if (new_attr.bgcolor < 8)
- {
- bytes[2] = '4'; bytes[3] = '0' + new_attr.bgcolor;
- bytes[4] = 'm';
- if (full_write (out_fd, bytes, 5) < 5)
- out_error ();
- }
- else
- {
- bytes[2] = '1'; bytes[3] = '0';
- bytes[4] = '0' + (new_attr.bgcolor - 8); bytes[5] = 'm';
- if (full_write (out_fd, bytes, 6) < 6)
- out_error ();
- }
- }
- break;
- case cm_xterm88:
- assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 88);
- {
- char bytes[10];
- char *p;
- bytes[0] = 0x1B; bytes[1] = '[';
- bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
- bytes[5] = '5'; bytes[6] = ';';
- p = bytes + 7;
- if (new_attr.bgcolor >= 10)
- *p++ = '0' + (new_attr.bgcolor / 10);
- *p++ = '0' + (new_attr.bgcolor % 10);
- *p++ = 'm';
- if (full_write (out_fd, bytes, p - bytes) < p - bytes)
- out_error ();
- }
- break;
- case cm_xterm256:
- assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 256);
- {
- char bytes[11];
- char *p;
- bytes[0] = 0x1B; bytes[1] = '[';
- bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
- bytes[5] = '5'; bytes[6] = ';';
- p = bytes + 7;
- if (new_attr.bgcolor >= 100)
- *p++ = '0' + (new_attr.bgcolor / 100);
- if (new_attr.bgcolor >= 10)
- *p++ = '0' + ((new_attr.bgcolor % 100) / 10);
- *p++ = '0' + (new_attr.bgcolor % 10);
- *p++ = 'm';
- if (full_write (out_fd, bytes, p - bytes) < p - bytes)
- out_error ();
- }
- break;
- default:
- abort ();
- }
+ out_bgcolor_change (stream, new_attr.bgcolor, false);
}
-
if (new_attr.weight != old_attr.weight
|| (cleared_attributes && new_attr.weight != WEIGHT_DEFAULT))
{
- assert (stream->supports_weight);
- assert (new_attr.weight != WEIGHT_DEFAULT);
- /* This implies: */
- assert (new_attr.weight == WEIGHT_BOLD);
- tputs (stream->enter_bold_mode, 1, out_char);
+ out_weight_change (stream, new_attr.weight, false);
}
if (new_attr.posture != old_attr.posture
|| (cleared_attributes && new_attr.posture != POSTURE_DEFAULT))
{
- assert (stream->supports_posture);
- assert (new_attr.posture != POSTURE_DEFAULT);
- /* This implies: */
- assert (new_attr.posture == POSTURE_ITALIC);
- tputs (stream->enter_italics_mode, 1, out_char);
+ out_posture_change (stream, new_attr.posture, false);
}
if (new_attr.underline != old_attr.underline
|| (cleared_attributes && new_attr.underline != UNDERLINE_DEFAULT))
{
- assert (stream->supports_underline);
- assert (new_attr.underline != UNDERLINE_DEFAULT);
- /* This implies: */
- assert (new_attr.underline == UNDERLINE_ON);
- tputs (stream->enter_underline_mode, 1, out_char);
+ out_underline_change (stream, new_attr.underline, false);
}
/* Keep track of the active attributes. */
{
if (!stream->non_default_active)
{
+ if (stream->tty_control != TTYCTL_NONE)
+ ensure_other_signal_handlers ();
+
+ #if BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT
/* Block fatal signals, so that a SIGINT or similar doesn't interrupt
- us without the possibility of restoring the terminal's state. */
- block_fatal_signals ();
- /* Likewise for SIGTSTP etc. */
- block_stopping_signals ();
-
- /* Enable the exit handler for restoring the terminal's state. */
- restore_colors =
- (stream->supports_foreground || stream->supports_background
- ? stream->orig_pair
- : NULL);
- restore_weight =
- (stream->supports_weight ? stream->exit_attribute_mode : NULL);
- restore_posture =
- (stream->supports_posture
- ? (stream->exit_italics_mode != NULL
- ? stream->exit_italics_mode
- : stream->exit_attribute_mode)
- : NULL);
- restore_underline =
- (stream->supports_underline
- ? (stream->exit_underline_mode != NULL
- ? stream->exit_underline_mode
- : stream->exit_attribute_mode)
- : NULL);
+ us without the possibility of restoring the terminal's state.
+ Likewise for SIGTSTP etc. */
+ block_relevant_signals ();
+ #endif
+
+ /* Enable the exit handler for restoring the terminal's state,
+ and make the signal handlers effective. */
+ if (out_stream != NULL)
+ {
+ /* We can't support two term_ostream_t active with non-default
+ attributes at the same time. */
+ abort ();
+ }
+ /* The uses of 'volatile' (and ISO C 99 section 5.1.2.3.(5)) ensure that
+ we set out_stream to a non-NULL value only after the memory locations
+ out_fd and out_filename have been filled. */
out_fd = stream->fd;
out_filename = stream->filename;
+ out_stream = stream;
+
+ #if HAVE_TCGETATTR
+ /* Now that the signal handlers are effective, modify the tty. */
+ if (stream->tty_control == TTYCTL_FULL)
+ {
+ /* Modify the local mode. */
+ clobber_local_mode ();
+ }
+ #endif
stream->non_default_active = true;
}
{
if (stream->non_default_active)
{
- /* Disable the exit handler. */
+ #if HAVE_TCGETATTR
+ /* Before we make the signal handlers ineffective, modify the tty. */
+ if (out_stream->tty_control == TTYCTL_FULL)
+ {
+ /* Restore the local mode, once the tputs calls from out_attr_change
+ have reached their destination. */
+ restore_local_mode ();
+ }
+ #endif
+
+ /* Disable the exit handler, and make the signal handlers ineffective. */
+ /* The uses of 'volatile' (and ISO C 99 section 5.1.2.3.(5)) ensure that
+ we reset out_fd and out_filename only after the memory location
+ out_stream has been cleared. */
+ out_stream = NULL;
out_fd = -1;
out_filename = NULL;
- /* Unblock fatal and stopping signals. */
- unblock_stopping_signals ();
- unblock_fatal_signals ();
+ #if BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT
+ /* Unblock the relevant signals. */
+ unblock_relevant_signals ();
+ #endif
stream->non_default_active = false;
}
/* Verify that the non-default attributes mode is turned off. */
if (stream->non_default_active)
abort ();
+ term_fd = -1;
+ update_pgrp_status ();
free (stream->filename);
if (stream->set_a_foreground != NULL)
free (stream->set_a_foreground);
&& (stream->exit_underline_mode != NULL
|| stream->exit_attribute_mode != NULL));
+ /* Infer the restore strings. */
+ stream->restore_colors =
+ (stream->supports_foreground || stream->supports_background
+ ? stream->orig_pair
+ : NULL);
+ stream->restore_weight =
+ (stream->supports_weight ? stream->exit_attribute_mode : NULL);
+ stream->restore_posture =
+ (stream->supports_posture
+ ? (stream->exit_italics_mode != NULL
+ ? stream->exit_italics_mode
+ : stream->exit_attribute_mode)
+ : NULL);
+ stream->restore_underline =
+ (stream->supports_underline
+ ? (stream->exit_underline_mode != NULL
+ ? stream->exit_underline_mode
+ : stream->exit_attribute_mode)
+ : NULL);
+
+ /* Prepare tty control. */
+ if (tty_control == TTYCTL_AUTO)
+ tty_control = TTYCTL_FULL;
+ stream->tty_control = tty_control;
+ if (stream->tty_control != TTYCTL_NONE)
+ init_relevant_signal_set ();
+ #if HAVE_TCGETATTR
+ if (stream->tty_control == TTYCTL_FULL)
+ {
+ struct stat statbuf1;
+ struct stat statbuf2;
+ if (fd == STDERR_FILENO
+ || (fstat (fd, &statbuf1) >= 0
+ && fstat (STDERR_FILENO, &statbuf2) >= 0
+ && SAME_INODE (statbuf1, statbuf2)))
+ stream->same_as_stderr = true;
+ else
+ stream->same_as_stderr = false;
+ }
+ else
+ /* This value is actually not used. */
+ stream->same_as_stderr = false;
+ #endif
+
+ /* Start keeping track of the process group status. */
+ term_fd = fd;
+ #if defined SIGCONT
+ ensure_continuing_signal_handler ();
+ #endif
+ update_pgrp_status ();
+
/* Initialize the buffer. */
stream->allocated = 120;
stream->buffer = XNMALLOC (stream->allocated, char);