/* 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 <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#if DEBUG_SIGNALS
-# include <stdio.h>
-#endif
-#if HAVE_TCGETATTR || HAVE_TCDRAIN
+#if 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"
tparam (str, tparambuf, sizeof (tparambuf), arg1)
#endif
-#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
-
/* =========================== Color primitives =========================== */
/* A non-default color index doesn't exist in this color model. */
static inline term_color_t
-rgb_to_color_monochrome ()
+rgb_to_color_monochrome (void)
{
return COLOR_DEFAULT;
}
/* ============================ EINTR handling ============================ */
-/* EINTR handling for tcgetattr(), tcsetattr(), tcdrain().
- These functions can return -1/EINTR even when we don't have any
+/* EINTR handling for tcdrain().
+ This function 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
#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 _GL_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 _GL_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 _GL_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 _GL_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 _GL_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
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
+ struct term_style_control_data control_data;
/* Variable state, representing past output. */
attributes_t default_attr; /* Default simplified attributes of the
terminal. */
attributes_t volatile active_attr; /* Simplified attributes that we have set
on the terminal. */
- bool non_default_active; /* True if activate_non_default_attr()
- is in effect.
- active_attr != default_attr implies
- non_default_active == true,
- but not the opposite! */
/* Variable state, representing future output. */
char *buffer; /* Buffer for the current line. */
attributes_t *attrbuffer; /* Buffer for the simplified attributes;
attributes_t simp_attr; /* Simplified current attributes. */
};
+static struct term_style_control_data *
+get_control_data (term_ostream_t stream)
+{
+ return &stream->control_data;
+}
+
/* Simplify attributes, according to the terminal's capabilities. */
static attributes_t
simplify_attributes (term_ostream_t stream, attributes_t attr)
return attr;
}
-/* 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 _GL_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()
-
-#endif
-
/* Stream that contains information about how the various out_* functions shall
do output. */
static term_ostream_t volatile out_stream;
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 ()
+out_error (void)
{
- error (EXIT_FAILURE, errno, _("error writing to %s"), out_filename);
+ error (EXIT_FAILURE, errno, _("error writing to %s"), out_stream->filename);
}
/* Output a single char to out_fd. */
1, async_safe ? out_char_unchecked : out_char);
}
-/* The exit handler. */
-static void
-restore (void)
-{
- /* Only do something while some output was started but not completed. */
- if (out_stream != NULL)
- {
- 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 _GL_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 _GL_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 _GL_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;
-}
-
-#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
- SIGTSTP,
- #endif
- #ifdef SIGTTIN
- SIGTTIN,
- #endif
- #ifdef SIGTTOU
- SIGTTOU,
- #endif
- #ifdef SIGCONT
- SIGCONT,
- #endif
- 0
- };
-
-# 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 relevant_signal_set;
-static bool relevant_signal_set_initialized = false;
-
-static void
-init_relevant_signal_set ()
-{
- if (!relevant_signal_set_initialized)
- {
- int fatal_signals[64];
- size_t num_fatal_signals;
- size_t i;
-
- num_fatal_signals = get_fatal_signals (fatal_signals);
-
- 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 relevant signals. */
-static _GL_ASYNC_SAFE inline void
-block_relevant_signals ()
-{
- /* 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 relevant signals. */
-static _GL_ASYNC_SAFE inline void
-unblock_relevant_signals ()
-{
- sigprocmask (SIG_UNBLOCK, &relevant_signal_set, NULL);
-}
-
-/* Determines whether a signal is ignored. */
-static _GL_ASYNC_SAFE bool
-is_ignored (int sig)
-{
- struct sigaction action;
-
- return (sigaction (sig, NULL, &action) >= 0
- && get_handler (&action) == SIG_IGN);
-}
-
-#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 _GL_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 _GL_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 _GL_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 _GL_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 _GL_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
-ensure_continuing_signal_handler (void)
-{
- 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;
- }
-}
-
-#endif
-
-static void
-ensure_other_signal_handlers (void)
-{
- static bool signal_handlers_installed = false;
-
- 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,
and update STREAM->ACTIVE_ATTR. */
static void
attributes_t old_attr = stream->active_attr;
bool cleared_attributes;
+ /* For out_char to work. */
+ out_stream = stream;
+ out_fd = stream->fd;
+
/* We don't know the default colors of the terminal. The only way to switch
back to a default color is to use stream->orig_pair. */
if ((new_attr.color == COLOR_DEFAULT && old_attr.color != COLOR_DEFAULT)
stream->active_attr = new_attr;
}
-/* Prepare for activating some non-default attributes.
- Note: This may block some signals. */
static void
-activate_non_default_attr (term_ostream_t stream)
+restore (term_ostream_t stream)
{
- 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.
- Likewise for SIGTSTP etc. */
- block_relevant_signals ();
- #endif
+ /* For out_char_unchecked to work. */
+ out_stream = stream;
+ out_fd = stream->fd;
- /* 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
+ if (stream->restore_colors != NULL)
+ tputs (stream->restore_colors, 1, out_char_unchecked);
+ if (stream->restore_weight != NULL)
+ tputs (stream->restore_weight, 1, out_char_unchecked);
+ if (stream->restore_posture != NULL)
+ tputs (stream->restore_posture, 1, out_char_unchecked);
+ if (stream->restore_underline != NULL)
+ tputs (stream->restore_underline, 1, out_char_unchecked);
+}
- stream->non_default_active = true;
- }
+static _GL_ASYNC_SAFE void
+async_restore (term_ostream_t stream)
+{
+ /* For out_char_unchecked to work. */
+ out_stream = stream;
+ out_fd = stream->fd;
+
+ if (stream->restore_colors != NULL)
+ tputs (stream->restore_colors, 1, out_char_unchecked);
+ if (stream->restore_weight != NULL)
+ tputs (stream->restore_weight, 1, out_char_unchecked);
+ if (stream->restore_posture != NULL)
+ tputs (stream->restore_posture, 1, out_char_unchecked);
+ if (stream->restore_underline != NULL)
+ tputs (stream->restore_underline, 1, out_char_unchecked);
}
-/* Deactivate the non-default attributes mode.
- Note: This may unblock some signals. */
-static void
-deactivate_non_default_attr (term_ostream_t stream)
+static _GL_ASYNC_SAFE void
+async_set_attributes_from_default (term_ostream_t stream)
{
- if (stream->non_default_active)
- {
- #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
+ attributes_t new_attr = stream->active_attr;
- /* 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;
-
- #if BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT
- /* Unblock the relevant signals. */
- unblock_relevant_signals ();
- #endif
+ /* For out_char_unchecked to work. */
+ out_stream = stream;
+ out_fd = stream->fd;
- stream->non_default_active = false;
- }
+ if (new_attr.color != COLOR_DEFAULT)
+ out_color_change (stream, new_attr.color, true);
+ if (new_attr.bgcolor != COLOR_DEFAULT)
+ out_bgcolor_change (stream, new_attr.bgcolor, true);
+ if (new_attr.weight != WEIGHT_DEFAULT)
+ out_weight_change (stream, new_attr.weight, true);
+ if (new_attr.posture != POSTURE_DEFAULT)
+ out_posture_change (stream, new_attr.posture, true);
+ if (new_attr.underline != UNDERLINE_DEFAULT)
+ out_underline_change (stream, new_attr.underline, true);
}
+static const struct term_style_controller controller =
+{
+ get_control_data,
+ restore,
+ async_restore,
+ async_set_attributes_from_default
+};
+
/* Activate the default attributes. */
static void
activate_default_attr (term_ostream_t stream)
/* Switch back to the default attributes. */
out_attr_change (stream, stream->default_attr);
- deactivate_non_default_attr (stream);
+ deactivate_term_non_default_mode (&controller, stream);
}
/* Output the buffered line atomically.
if (len > 0)
{
if (!equal_attributes (*ap, stream->default_attr))
- activate_non_default_attr (stream);
+ activate_term_non_default_mode (&controller, stream);
do
{
/* Before changing to goal_attr, we may need to enable the non-default
attributes mode. */
if (!equal_attributes (goal_attr, stream->default_attr))
- activate_non_default_attr (stream);
+ activate_term_non_default_mode (&controller, stream);
/* Change to goal_attr. */
if (!equal_attributes (goal_attr, stream->active_attr))
out_attr_change (stream, goal_attr);
/* When we can deactivate the non-default attributes mode, do so. */
if (equal_attributes (goal_attr, stream->default_attr))
- deactivate_non_default_attr (stream);
+ deactivate_term_non_default_mode (&controller, stream);
}
/* Implementation of ostream_t methods. */
term_ostream::free (term_ostream_t stream)
{
term_ostream_flush (stream, FLUSH_THIS_STREAM);
- /* Verify that the non-default attributes mode is turned off. */
- if (stream->non_default_active)
- abort ();
- term_fd = -1;
- update_pgrp_status ();
+
+ deactivate_term_style_controller (&controller, stream);
+
free (stream->filename);
if (stream->set_a_foreground != NULL)
free (stream->set_a_foreground);
: 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);
stream->default_attr = simplified_default;
stream->active_attr = simplified_default;
- stream->non_default_active = false;
stream->curr_attr = assumed_default;
stream->simp_attr = simplified_default;
}
- /* Register an exit handler. */
- {
- static bool registered = false;
- if (!registered)
- {
- atexit (restore);
- registered = true;
- }
- }
+ /* Prepare tty control. */
+ activate_term_style_controller (&controller, stream, fd, tty_control);
return stream;
}