Flushing pending input so that a user's
typeahead isn't read at the next prompt.




The Problem.

The code in listing 13.6 of the book the book "SAMs Teach Yourself C for Linux Programming in 21 Days" has a problem.

The problem occurs when a program reads user input, then does some time consuming processing (say more than a couple of seconds) before reading more user input. In this situation, the user might type some more on the keyboard while the program is processing and these characters will be buffered by the operating system and read on the next read call. The idea of the existing code was to use fflush (stdin) to flush all the stored characters from the input buffer.

As Benjamin Black pointed out, the C Programming FAQ states that the behavior of fflush () is defined only for output streams. To make matters worse there is no standard ANSI/ISO C way of doing this. Fortunately there is a POSIX solution.

The POSIX solution is to use the low-level unbuffered input functions or GNU/Linux specific higher level function which in turn use the lower level ones.



The High Level Solution.

The high level solution uses the GNU readline library for handling user input and functions defined in <termios.h> for modifying the behavior of the terminal on the fly. The code for this example may be downloaded here. and there is also a version with line numbers is available here. Instructions for compiling this program is in the comments at the top of the file.

This program includes some common header files on lines 46 to 48 and then includes <termios.h> and <readline/readline.h> on lines 49 and 60. The termios.h header is available on nearly all Unix-like operating systems while readline.h is a non-standard header which is usually available on all GNU/Linux systems. (If it is not, it may because the a readline package or the readline-devel package has not been installed.)

Lines 57 and 58 contain prototypes for functions implemented further on in the file.

As the comments on lines 64 to 69 explain, this program changes the behavior of the terminal during operations. If the program were to change these setting and then exit, the changes would be permanent which is rather undesirable. For this reason, we use the atexit() function to register a function (named restore_terminal ()) which will be called when the program exits.

To read input from the user we use readline () function on line 73. This function allows full command line editing just like the bash shell allowing the user to use the backspace key to correct typing mistakes. The call to readline () returns a string which is passed to sscanf () to read the int. The sscanf () function is similar to scsanf () but scans a string rather than input from the user.

The call to sleep (5) on line 82 simulates the processing of data that might be occurring, however before that a call is made to silence_terminal (TERM_SILENCE_ON) on line 79. Similarly silence_terminal (TERM_SILENCE_OFF) on line 87 is called when we want to turn the terminal back on before calling readline () again on line 88.

The restore_terminal () function is implemented on lines 100 to 102. It simply calls silence_terminal (TERM_SILENCE_OFF) to restore the terminal settings.

The real trick of this program are in the function silence_terminal () on lines 104 to 140. This function needs to store the original terminal settings when it is first called. It also needs to store modified terminal settings for when it sets the terminal into silent mode. This is achieved by the use of static variables. Remember that static variables are variables which are initialized on the first call to the function but retain (ie remember) their values between calls (unlike automatic variables which have no memory). A static int (line 116) is used to allow the function to remember whether it has been run before or not and two static struct termios structures are used to store the original and silent terminal settings.

On the first call to silence_terminal () the int initialized is set to zero and the statements from lines 120 to 125 are executed. The original terminal settings are retrieved and stored in the original structure on line 120. A second copy of these settings is retrieved on line 122 and modified on line 124 before the initialized variable is set to one on line 125.

Lines 128 to 136 either modify or restore the terminal settings depending on whether the on parameter is TRUE or FALSE. In addition, when the terminal is restored to its original condition all pending input characters (which have been stored by the operating system) are discarded using a call to tcflush().

You are encouraged to modify and experiment with this program to reassure yourself that it does indeed work as its supposed to. It is also worth reading the man pages for readline (type 'man readline' in an xterm) and the terminal control functions like tcflush and tcgetattr which are covered on the termios man page.

The Low Level Solution.

Coming soon.