bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Colored output


From: Bruno Haible
Subject: Re: Colored output
Date: Sun, 17 Mar 2019 17:30:06 +0100
User-agent: KMail/5.1.3 (Linux/4.4.0-141-generic; KDE/5.18.0; x86_64; ; )

Hi Jim,

You wrote in
<https://lists.gnu.org/archive/html/bug-gnulib/2019-01/msg00173.html>:

> I too would start with coreutils, noting that one must be careful to
> handle signals, so that the terminal is not left in a bad state when
> output is interrupted between a pair of set/reset escape sequences.
> https://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c#n1303
> Today I was surprised to see that while grep and recent diffutils also
> have --color support, grep lacks the signal-handling code that is
> present in both coreutils and diffutils.

Thank you for mentioning the need for signal handling. I have implemented
a reliable signal handling now, in libtextstyle-0.7:
  https://alpha.gnu.org/gnu/gettext/libtextstyle-0.7.tar.gz
  https://haible.de/bruno/gnu/libtextstyle-0.7.tar.gz

And I am surprised to see that the color/signal handling in coreutils and
diffutils, that you mention as starting points, implement only part of
what is needed to make things reliable.

In libtextstyle, I offer three levels of tty control to the application:

  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_NONE is what 'grep' uses.

TTYCTL_PARTIAL is what 'ls' and 'diff' use.

TTYCTL_FULL is what libtextstyle offers by default, and is - in my testing -
the only approach that does not produce garbled output.

For example, you can make GNU ls produce garbled output like this:
  - Create a directory with many files in it:
    $ for i in `seq 100000`; do touch xyz$i; done
    $ chmod a+x xyz*
  - Run 'ls' and interrupt is through Ctrl-C after 0.3 seconds. The result
    may end like this, with a stray 'C' on the screen:
xyz18858   xyz30106  xyz41357  xyz52607  xyz63858  xyz75107  xyz86358  xyz97608
xyz18859   xyz30107  C
xyz18914
  - Or run 'ls' and use Ctrl-Z to interrupt it. You may see
xyz19924   xyz31173  xyz42423  xyz53674  xyz64924  xyz76174  xyz87424  xyz98675
xyz19925   xyz31174  xy^Z
xyz19982
    That is, the output continues after it was interrupted through Ctrl-Z.

In detail:

   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 could use a
       technique that involves tcdrain(), TIOCOUTQ, and usleep() when it is OK
       to disable NOFLSH.

'ls' and 'diff' do not mitigate the problems (4) and (5).

> If you factor some of that code into gnulib, that would be a fine
> opportunity to use it to fix grep.

Would you be prepared to use the 'term-ostream' module [1][2] in 'diff' and
'grep'?

Or would it be better to separate the pure signal handling and tty cleanup
code from the code that looks up the appropriate escape sequences from
termlib/termcap, and create a module in gnulib with only the signal handling
and tty cleanup code?

Bruno

[1] 
https://git.savannah.gnu.org/gitweb/?p=gettext.git;a=blob;f=gnulib-local/lib/term-ostream.oo.h
[2] 
https://git.savannah.gnu.org/gitweb/?p=gettext.git;a=blob;f=gnulib-local/lib/term-ostream.oo.c




reply via email to

[Prev in Thread] Current Thread [Next in Thread]