From: Bruno Haible Date: Sun, 24 Mar 2019 22:40:43 +0000 (+0100) Subject: libtextstyle: Use gnulib module 'term-style-control'. X-Git-Tag: v0.20~112 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6a2cc040a939ea2492edf7966b9ec80b95bc493c;p=thirdparty%2Fgettext.git libtextstyle: Use gnulib module 'term-style-control'. * gnulib-local/lib/term-ostream.oo.h: Include term-style-control.h. (ttyctl_t): Remove type. * gnulib-local/lib/term-ostream.oo.c (DEBUG_SIGNALS): Remove macro. Don't include , , , fatal-signal.h, sig-handler.h, same-inode.h. (SIZEOF): Remove macro. (nonintr_tcgetattr, nonintr_tcsetattr): Remove functions. (log_message, sprintf_integer, simple_errno_string, simple_signal_string, log_signal_handler_called): Remove functions. (struct term_ostream): Remove fields tty_control, same_as_stderr, non_default_active. Add control_data field instead. (get_control_data): New function. (BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT): Remove macro. (term_fd): Remove variable. (pgrp_status_t): Remove type. (pgrp_status): Remove variable. (update_pgrp_status): Remove function. (out_filename): Remove variable. (out_error): Use out_stream instead of out_filename. (restore, tcsetattr_failed): Remove functions. (orig_lflag_set, orig_lflag): Remove variables. (clobber_local_mode, restore_local_mode): Remove functions. (job_control_signals): Remove variable. (num_job_control_signals): Remove macro. (relevant_signal_set, relevant_signal_set_initialized): Remove variables. (init_relevant_signal_set, block_relevant_signals, unblock_relevant_signals, is_ignored, show_signal_marker, fatal_or_stopping_signal_handler, fatal_signal_handler, stopping_signal_handler, continuing_signal_handler, ensure_continuing_signal_handler, ensure_other_signal_handlers): Remove functions. (out_attr_change): Set out_stream and out_fd. (activate_non_default_attr, deactivate_non_default_attr): Remove functions. (restore, async_restore, async_set_attributes_from_default): New functions. (controller): New variable. (activate_default_attr, output_buffer): Update. (term_ostream::free): Invoke deactivate_term_style_controller. (term_ostream_create): Invoke activate_term_style_controller. * gnulib-local/m4/term-ostream.m4 (gl_TERM_OSTREAM): Don't test for tcgetattr(). * gnulib-local/modules/term-ostream (Depends-on): Add term-style-control. Remove fatal-signal, sigaction, sigprocmask, same-inode. --- diff --git a/gnulib-local/lib/term-ostream.oo.c b/gnulib-local/lib/term-ostream.oo.c index 364f43b63..86410bdb8 100644 --- a/gnulib-local/lib/term-ostream.oo.c +++ b/gnulib-local/lib/term-ostream.oo.c @@ -20,35 +20,19 @@ /* Specification. */ #include "term-ostream.h" -/* Set to 1 to get debugging output regarding signals. */ -#define DEBUG_SIGNALS 0 - #include #include -#include #include #include #include #include -#if DEBUG_SIGNALS -# include -#endif -#if HAVE_TCGETATTR || HAVE_TCDRAIN +#if HAVE_TCDRAIN # include -# if !defined NOFLSH /* QNX */ -# define NOFLSH 0 -# endif -#endif -#if HAVE_TCGETATTR -# include #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" @@ -65,8 +49,6 @@ static char tparambuf[100]; tparam (str, tparambuf, sizeof (tparambuf), arg1) #endif -#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) - /* =========================== Color primitives =========================== */ @@ -269,7 +251,7 @@ typedef enum /* 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; } @@ -965,38 +947,10 @@ equal_attributes (attributes_t attr1, attributes_t attr2) /* ============================ 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 @@ -1014,151 +968,6 @@ 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 _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 @@ -1198,20 +1007,12 @@ fields: 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; @@ -1222,6 +1023,12 @@ fields: 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) @@ -1251,164 +1058,6 @@ 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 - - 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 - - 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 '.*' - - $ 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 - - section 11.2.5) but tcsetattr() calls would cause it to stop due to - a SIGTTOU signal (per POSIX - ). - 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; @@ -1418,15 +1067,11 @@ 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. */ @@ -1686,448 +1331,6 @@ out_underline_change (term_ostream_t stream, term_underline_t new_underline, 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 @@ -2136,6 +1339,10 @@ out_attr_change (term_ostream_t stream, attributes_t new_attr) 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) @@ -2236,85 +1443,69 @@ out_attr_change (term_ostream_t stream, attributes_t new_attr) 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) @@ -2322,7 +1513,7 @@ 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. @@ -2363,7 +1554,7 @@ output_buffer (term_ostream_t stream, attributes_t goal_attr) if (len > 0) { if (!equal_attributes (*ap, stream->default_attr)) - activate_non_default_attr (stream); + activate_term_non_default_mode (&controller, stream); do { @@ -2395,13 +1586,13 @@ output_buffer (term_ostream_t stream, attributes_t goal_attr) /* 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. */ @@ -2498,11 +1689,9 @@ static void 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); @@ -2823,37 +2012,6 @@ term_ostream_create (int fd, const char *filename, ttyctl_t tty_control) : 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); @@ -2875,20 +2033,12 @@ term_ostream_create (int fd, const char *filename, ttyctl_t tty_control) 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; } diff --git a/gnulib-local/lib/term-ostream.oo.h b/gnulib-local/lib/term-ostream.oo.h index 5e2fd7de8..51d0e0c45 100644 --- a/gnulib-local/lib/term-ostream.oo.h +++ b/gnulib-local/lib/term-ostream.oo.h @@ -97,25 +97,9 @@ methods: void flush_to_current_style (term_ostream_t stream); }; -/* The amount of control to take over the underlying tty in order to avoid - garbled output on the screen, due to interleaved output of escape sequences - and output from the kernel (such as when the kernel echoes user's input - or when the kernel prints '^C' after the user pressed Ctrl-C). */ -typedef enum -{ - TTYCTL_AUTO = 0, /* Automatic best-possible choice. */ - TTYCTL_NONE, /* No control. - Result: Garbled output can occur, and the terminal can - be left in any state when the program is interrupted. */ - TTYCTL_PARTIAL, /* Signal handling. - Result: Garbled output can occur, but the terminal will - be left in the default state when the program is - interrupted. */ - TTYCTL_FULL /* Signal handling and disabling echo and flush-upon-signal. - Result: No garbled output, and the the terminal will - be left in the default state when the program is - interrupted. */ -} ttyctl_t; +/* Get ttyctl_t. */ +#define term_style_user_data term_ostream_representation +#include "term-style-control.h" #ifdef __cplusplus diff --git a/gnulib-local/m4/term-ostream.m4 b/gnulib-local/m4/term-ostream.m4 index c5446e985..60461970a 100644 --- a/gnulib-local/m4/term-ostream.m4 +++ b/gnulib-local/m4/term-ostream.m4 @@ -1,4 +1,4 @@ -# term-ostream.m4 serial 3 +# term-ostream.m4 serial 4 dnl Copyright (C) 2006, 2019 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -7,5 +7,5 @@ dnl with or without modifications, as long as this notice is preserved. AC_DEFUN([gl_TERM_OSTREAM], [ AC_REQUIRE([AC_C_INLINE]) - AC_CHECK_FUNCS_ONCE([tcgetattr tcdrain]) + AC_CHECK_FUNCS_ONCE([tcdrain]) ]) diff --git a/gnulib-local/modules/term-ostream b/gnulib-local/modules/term-ostream index 77e893f7d..2768bb2f8 100644 --- a/gnulib-local/modules/term-ostream +++ b/gnulib-local/modules/term-ostream @@ -8,14 +8,11 @@ m4/term-ostream.m4 Depends-on: ostream +term-style-control error stdlib -fatal-signal -sigaction -sigprocmask full-write fsync -same-inode gettext-h terminfo-h xalloc diff --git a/libtextstyle/.gitignore b/libtextstyle/.gitignore index f4196f42f..12e4908be 100644 --- a/libtextstyle/.gitignore +++ b/libtextstyle/.gitignore @@ -151,6 +151,8 @@ /lib/sys_types.in.h /lib/term-ostream.oo.c /lib/term-ostream.oo.h +/lib/term-style-control.c +/lib/term-style-control.h /lib/term-styled-ostream.oo.c /lib/term-styled-ostream.oo.h /lib/terminfo.h