Suppressing a Terminal’s Character Echo

Back in the bad old days, you used a terminal connected to a mainframe to do your computer work. The terminal had a monitor and keyboard and just enough smarts to configure itself for communications with the mainframe. One of those configuration options was character echo.

On today’s systems, the configuration options are set and forgotten, though they can still be programmed. The functions are available in the C library, defined in the termios.h header file. I must confess that Termios sounds like some Marvel supervillain, though it means Terminal I/O System.

The man page for termios goes on and on about communications, which may seem boring, but back in the day such details were vital in getting terminals connected to a central processing system. For purposes of programming the terminal with regards to character echo, a C coder deals with the termios structure, specifically its c_lflag member and the ECHO constant.

To fill the termios structure with details about a terminal’s current configuration, use the tcgetattr() function. It has two arguments: the terminal’s file descriptor number and the address of a termios structure:

int tcgetattr(int fildes, struct termios *termios_p);

To set a terminal’s configuration, use the tcsetattr() function. It sports the same two arguments as tcgetattr().

The fildes (file descriptor) argument is an integer representing the terminal’s open device. Rather than probe for the device name and use the open() function, use the defined constant STDIN_FILENO, set to 0 by default. File descriptor zero always describes the current, standard input device. Because the STDIN_FILENO constant may not be defined, my sample code tests for it manually (see below).

The integer return value is zero upon success, -1 otherwise with the errno constant set appropriately.

Once the current terminal’s details are obtained, the termios structure’s c_lflag member has its ECHO flag bit reset. When the bit is ON (set to 1), characters are echoed. Resetting it to zero turns off the echo. To reset this bit, I use the ECHO constant and bitwise XOR (exclusive OR) operator, ^, as shown in the sample code:

#include <stdio.h>
#include <termios.h>

#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif /* STDIN_FILENO */

#define SIZE 16

int main()
{
    struct termios original,noecho;
    char buffer[SIZE];
    int x;

    /* obtain the current terminal configuration */
    tcgetattr(STDIN_FILENO,&original);
    /* duplicate it */
    noecho = original;
    /* turn off full duplex */
    noecho.c_lflag = noecho.c_lflag ^ ECHO;
    /* set the terminal */
    tcsetattr(STDIN_FILENO, TCSANOW, &noecho);

    /* prompt for and input the password */
    printf("Test: ");
    fgets(buffer,SIZE,stdin);
    /* remove newline */
    for( x=0; x<SIZE; x++)
    {
        if( buffer[x]=='\n' )
        {
            buffer[x]='\0';
            break;
        }
    }
    putchar('\n');

    /* restore the terminal settings */
    tcsetattr(STDIN_FILENO, TCSANOW, &original);
    /* output the result */
    printf("Your password is '%s'\n",buffer);

    return(0);
}

Line 17 uses the tcgetattr() function to read the current terminal configuration into the original structure. This structure is duplicated to the noecho structure at Line 19.

At Line 21, the ECHO flag in the noecho structure’s c_lflag member is reset:

noecho.c_lflag = noecho.c_lflag ^ ECHO;

The ^ (XOR) operator flips only the ECHO bit, retaining the other configuration settings. This statement could also be shortened to:

noecho.c_lflag ^= ECHO;

At Line 23, the tcsetattr() function sets the new terminal configuration in structure variable noecho. From this point forward, local echo is suppressed on the current terminal. So when the fgets() function is called at Line 27, text output is suppressed.

The tcsetattr() function at Line 40 re-establishes the original terminal configuration.

Here’s a sample run:

Password:
Your password is 'hello'

This operation duplicates what the getpass() function accomplishes, which was the topic of last week’s Lesson. Obviously, using getpass() is easier, but there’s something delightfully nerdy about terminal programming — especially when it’s no longer a tedium you must endure to get a terminal up and running.

Leave a Reply