Add Book to My BookshelfPurchase This Book Online

Chapter 12 - Terminals

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

POSIX Terminal Control
On POSIX-based systems, all of the terminal input and output modes are controlled via a struct termios structure and the functions described in this section. The struct termios structure is defined in the include file termios.h:
    struct termios {
        tcflag_t    c_iflag;
        tcflag_t    c_oflag;
        tcflag_t    c_cflag;
        tcflag_t    c_lflag;
        cc_t        c_cc[CCS];
    };
The c_iflag element of the structure contains flags controlling the input of characters by the terminal driver, the c_oflag element contains flags controlling the output of characters, the c_cflag element contains flags controlling the hardware interface, and the c_lflag element contains flags controlling the interface between the terminal driver and the user. The c_cc array contains the values of the various special characters described earlier.
The c_cc array is indexed by constants whose names are identical to the special characters' names with a 'V' prepended. For example, to set the line-kill character to CTRL-X, we might use:
    #include <termios.h>
    ·
    ·
    ·
    struct termios modes;
    modes.c_cc[VKILL] = '\030';
where the octal value 030 is CTRL-X. To disable a special character, set it to a special value. The special value can be obtained by calling pathconf or fpathconf (see Chapter 9, System Configuration and Resource Limits) with the _PC_VDISABLE argument. For example, to disable the interrupt character, we might use:
    #include <termios.h>
    #include <unistd.h>
    ·
    ·
    ·
    struct termios modes;
    long vdisable;
    vdisable = fpathconf(0, _PC_VDISABLE);
    modes.c_cc[VINTR] = vdisable;
Each of the flag elements of the structure is constructed from the logical or of the attributes described in Table 12-1. To turn on a particular attribute, the flag value is or ed into the flag element. For example, to turn the ECHO attribute on, we might use this:
    #include <termios.h>
    ·
    ·
    ·
    struct termios modes;
    modes.c_lflag |= ECHO;
To turn a feature off, the complement of the attribute is anded into the flag element. For example, to turn the ECHO attribute off, we would use this:
    #include <termios.h>
    ·
    ·
    ·
    struct termios modes;
    modes.c_lflag &= ~ECHO;
Table 12-1 lists all the available attributes, and provides a very brief description of what they do. Most of these attributes, however, are not used very often. Some of the more commonly used attributes are described in more detail below:
ICRNL (c_iflag)
When set, this attribute tells the terminal driver to map the carriage return character to a newline character on input. Recall that UNIX uses the newline character as a line terminator; this attribute allows the user to use the carriage return key on the keyboard to signify the end of a line.
ISTRIP (c_iflag)
When set, this attribute tells the terminal driver to strip the eighth bit off of all input characters (by making it zero). Since ASCII is a 7-bit code, this has the general effect of forcing input into the ASCII character set.
OPOST (c_oflag)
When set, this attribute enables the output post-processing features of the terminal driver. This includes inserting delays after certain characters such as newline and tab for slow devices, mapping newline to carriage return-newline, and so forth.
ONLCR (c_oflag)
When set, this attribute tells the terminal driver to output a carriage return and a newline each time a newline character occurs in the output. Most terminal devices (and printers) will move “down” when a newline is received, but they will not move back to the leftmost column unless a carriage return is also received.
B0...B38400 (c_cflag)
The baud rate is set by turning on one of these attributes. For example, B9600 represents 9600 baud. The special rate B0 has the effect of turning off the Data Terminal Ready signal, effectively hanging up the phone line.
CREAD (c_cflag)
This attribute enables the receiver. If it is not set, characters cannot be received from the device.
ICANON (c_lflag)
When set, this attribute enables canonical input mode. This mode is described in detail below.
IEXTEN (c_lflag)
This attribute enables the processing of certain implementation-defined features. In SVR4, it enables the processing of the WERASE, REPRINT, DISCARD, and LNEXT special characters, and enables the processing of the TOSTOP, ECHOCTL, ECHOPRT, ECHOKE, FLUSHO, and PENDIN attributes.
ISIG (c_lflag)
When set, this attribute enables the signal-generating properties of some of the the special characters (DSUSP, INTR, QUIT, and SUSP).
ECHO (c_lflag)
When set, characters typed by the user are echoed (printed) back to the terminal. This attribute is normally turned off when prompting for passwords (and for other reasons).
ECHOE (c_lflag)
When set, characters are erased on receipt of the ERASE character by printing a backspace, a space, and another backspace. If not set, the user has to mentally keep track of how many characters were erased.
ECHOK (c_lflag)
When set, the terminal driver will echo a newline character when the KILL character is received; this makes things a little easier to read.
ECHOKE (c_cflag)
When set, the line is erased on receipt of a KILL character by printing a sequence of backspace-space-backspace characters.
TOSTOP (c_cflag)
When set, a process in the background that tries to perform output to the terminal will be stopped with a SIGTTOU signal until it is brought into the foreground. If not set, background processes can write to the terminal unimpeded; this usually has the effect of “messing up” whatever the user is doing at the moment.
Examining and Changing Terminal Attributes
Terminal attributes can be examined and changed with the tcgetattr and tcsetattr functions:
    #include <termios.h>
    int tcgetattr(int fd, struct termios *modes);
    int tcsetattr(int fd, int action, struct termios *modes);
The tcgetattr function obtains the attributes for the terminal device referenced by the open file descriptor fd, and stores them in the area pointed to by modes. The tcsetattr function sets the attributes of the terminal device referenced by the open file descriptor fd to the attributes contained in the struct termios structure pointed to by modes. The value of action must be one of the following:
TCSANOW
The change occurs immediately.
TCSADRAIN
The change occurs after all pending output to the device has been transmitted. Use this function when changing parameters that affect output.
TCSAFLUSH
The change occurs after all pending output to the device has been transmitted. All input that has been received but not read by a program is discarded before the change is made.
Both tcgetattr and tcsetattr return 0 on success; if fd does not refer to a terminal device, or another error occurs, they return -1 and set errno to indicate the error.
Note that because tcsetattr sets all terminal attributes, you must pass a completely filled-in struct termios structure. Conventionally, this is done by first calling tcgetattr to get the current attributes, making changes to the structure it returns, and then passing the result to tcsetattr.
Baud Rates
The term “baud rate” is outdated and should really be referred to now as “bits per second.” However, most UNIX documentation and functions still refer to baud rate. The baud rate of a device is stored in the struct termios structure, but the POSIX standard does not specify where. This means that it's implementation-dependent, and there are functions provided to examine and change the baud rate in the structure:
    #include <termios.h>
    speed_t cfgetispeed(const struct termios *modes);
    speed_t cfgetospeed(const struct termios *modes);
    int cfsetispeed(struct termios *modes, speed_t speed);
    int cfsetospeed(struct termios *modes, speed_t speed);
The cfgetispeed and cfgetospeed functions extract the input and output baud rates for the device from the struct termios structure pointed to by modes. Note that tcgetattr must be called first, to place meaningful information into the structure. These functions return one of the constants B0...B38400.
The cfsetispeed and cfsetospeed functions set the input and output baud rates (which may be different if the device supports it) in the struct termios structure pointed to by modes to the value passed in the speed parameter. This value should be one of the constants B0...B38400. Notice that these functions only make the settings in the structure; the change does not take effect on the device until tcsetattr is called.
Job Control Functions
Three functions let you manipulate session IDs and process group IDs of the terminal:
    #include <sys/types.h>
    #include <termios.h>
    pid_t tcgetpgrp(int fd);
    int tcsetpgrp(int fd, pid_t pgid);
    pid_t tcgetsid(int fd);
The tcgetpgrp function returns the process group ID of the terminal referenced by the open file descriptor fd. The tcgetsid function returns the session ID of the terminal referenced by fd.
The tcsetpgrp function sets the process group ID of the terminal referenced by the open file descriptor fd to pgid. For this to succeed, the terminal must be the controlling terminal of the calling process, the controlling terminal must be associated with the session of the calling process, and pgid must be the process group ID of a process in the same session as the calling process.
On success, tcsetpgrp returns 0. On failure, all three functions return -1 and set errno to indicate the error.
Other Functions
The POSIX standard specifies four additional functions for manipulating terminal devices:
    #include <termios.h>
    int tcsendbreak(int fd, int duration);
    int tcdrain(int fd);
    int tcflush(int fd, int queue);
    int tcflow(int fd, int action);
The tcsendbreak function transmits a continuous stream of zero-valued bits (called a break condition) for the specified duration. The POSIX standard specifies that if duration is 0, the transmission lasts for between 0.25 and 0.50 seconds. But, it also specifies that if duration is non-zero, the result is implementation dependent. In SVR4, a non-zero value for duration means that no bits are transmitted at all—instead, the function behaves like tcdrain. In some other systems, a non-zero value may mean to transmit for duration×N, where N is between 0.25 and 0.50 seconds. Still other systems may provide other interpretations. Non-zero values for duration should probably be avoided for portability reasons.
The tcdrain function waits until all output written to the device referred to by fd has been transmitted, and then returns.
The tcflush function discards data written to the device referenced by fd but not transmitted, or data received but not read, depending on the value of queue:
TCIFLUSH
Flush data received but not read.
TCOFLUSH
Flush data written but not transmitted.
TCIOFLUSH
Flush both data received but not read and data written but not transmitted.
The tcflow function suspends the transmission or reception of data on the device referred to by fd, depending on the value of action:
TCOOFF
Suspend output.
TCOON
Resume output.
TCIOFF
Cause the system to transmit a STOP character to inform the device to stop transmitting data to the system.
TCION
Cause the system to transmit a START character to inform the device to start transmitting data to the system.
Canonical Mode
Canonical mode is the usual mode in which terminals operate. All of our examples up to this point have used the terminal in canonical mode. In this mode, a program issues a read request, and the read returns when a line has been entered. It is not necessary for the program to read an entire line; if a partial line is read, the next read will start where the previous one left off.
For the most part, programs that interact with the user will keep the terminal in canonical mode—it's easier to deal with, since the operating system handles all the messy details of buffering the input, handling character erases and line kills, keeping track of typeahead (when the user types faster than the program is reading), and so forth.
There are times when, operating in canonical mode, a program might want to change some of a terminal's attributes. The most common situation in which this occurs is when reading a password. Passwords, because they are meant to be secret, should not be printed on the screen as they are typed. In order to accomplish this, the program reading the password should disable the character echo attribute on the terminal. Example 12-1 shows a program that does this.
Example 12-1:  readpass
#include <termios.h>
#include <signal.h>
#include <stdio.h>
int
main(void)
{
    char line[BUFSIZ];
    sigset_t sig, savesig;
    struct termios modes, savemodes;
    /*
     * Block keyboard signals.
     */
    sigemptyset(&sig);
    sigaddset(&sig, SIGINT);
    sigaddset(&sig, SIGQUIT);
    sigaddset(&sig, SIGTSTP);
    sigprocmask(SIG_BLOCK, &sig, &savesig);
    /*
     * Get current terminal attributes.
     */
    if (tcgetattr(0, &modes) < 0) {
        perror("tcgetattr");
        exit(1);
    }
    /*
     * Save a copy of them to restore later, and then
     * change the attributes to remove echo.
     */
    savemodes = modes;
    modes.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE);
    /*
     * Make our changes take effect.
     */
    if (tcsetattr(0, TCSAFLUSH, &modes) < 0) {
        perror("tcsetattr");
        exit(1);
    }
    /*
     * Prompt for and read a line.
     */
    printf("Enter a line (will not echo): ");
    fgets(line, sizeof(line), stdin);
    line[strlen(line)-1] = '\0';
    putchar('\n');
    /*
     * Restore original terminal attributes.
     */
    if (tcsetattr(0, TCSAFLUSH, &savemodes) < 0) {
        perror("tcsetattr");
        exit(1);
    }
    /*
     * Restore original signal mask.
     */
    sigprocmask(SIG_SETMASK, &savesig, (sigset_t *) 0);
    /*
     * Print out what the user typed.
     */
    printf("You entered \"%s\"\n", line);
    exit(0);
}
    % readpass
    Enter a line (will not echo):
    You entered "test"
The program begins by setting up a signal mask to block the receipt of signals that can be generated from the keyboard. The reason for doing this is that one of these signals can cause the program to terminate or stop, leaving the terminal in an undesirable state (character echo turned off). The tcgetattr function is then used to obtain the current terminal attributes. These are saved, and then modified to remove the character echo attribute. We also remove all the “visual” erase attributes. The new attributes are set with tcsetattr, and then the user is prompted to enter a line of text. Once the line is read, the original terminal attributes and the original signal mask are restored, and the line is printed. Note that a newline character is output right after reading the input; because echo is turned off, the newline entered by the user will not be printed.
You can use this program to verify that even with echo turned off, everything else in canonical mode still works. Try entering a line of text and using your character erase and line kill characters, and verify that the output is what you'd expect.
Non-Canonical Mode
Some programs cannot use canonical mode. For example, consider the vi editor (or emacs, if you prefer). The editor's commands are single characters that must be acted upon immediately, without waiting for the user to press return. Thus, we need a way to obtain input from the user in units of characters, rather than lines. Furthermore, some of the commands used by the editor are special to the terminal driver and are not normally passed to the reading program (e.g., CTRL-D, the default EOF character, tells vi to scroll down half a screen, and CTRL-R, the REPRINT character, tells emacs to search in the reverse direction). So, we need a way to turn off these special meanings, as well.
This is what non-canonical mode is for. To enter non-canonical mode, turn off the ICANON attribute. When in non-canonical mode, all of the special characters except those that generate signals are disabled. If we also turn off the ISIG attribute, we can disable the signal-generating special characters as well. Non-canonical mode also stops the system from buffering the input into units of lines.
But, if non-canonical mode disables the line-by-line processing of input, how does the system know when to return to data to us? Older systems, which use raw or cbreak mode for non-canonical input, return the data one character at a time. Unfortunately, this requires a lot of overhead. To avoid this, POSIX allows us to tell the system to return input when either a specified amount of data has been read, or after a certain amount of time has passed. The implementation of this uses two variables in the c_cc array, MIN and TIME, indexed by VMIN and VTIME, respectively.
MIN specifies a minimum number of characters to be processed before a read returns. TIME specifies the time, in tenths of a second, to wait for input. There are four combinations of these two variables:
Case A: MIN > 0, TIME > 0
In this case, TIME serves as an inter-character timer that is activated after the first character is received, and reset after each subsequent character is received. If MIN characters are received before the timer expires, the read returns the bytes received. If the timer expires before MIN bytes have been read, the characters read so far are received. At least one character is guaranteed to be returned, because the timer does not start until the first character is processed.
Case B: MIN > 0, TIME = 0
Since TIME is 0, there is no timer involved in this case. A read will not be satisfied until MIN characters have been received.
Case C: MIN = 0, TIME > 0
In this case, since MIN is 0, TIME does not serve as an inter-character timer. Instead, it serves as a read timer that is started as soon as the read call is issued. A read is satisfied as soon as a single character is typed, or when the timer expires. Note that if the timer expires, no character is read, and read returns 0.
Case D: MIN = 0, TIME = 0
In this case, return is immediate. If data is available, the read will return up to the number of characters requested. If no data is available, read returns 0.
Example 12-2 shows a program that uses non-canonical mode to read one character at a time.
Example 12-2:  caseflip
#include <termios.h>
#include <signal.h>
#include <stdlib.h>
#include <ctype.h>
int
main(void)
{
    char c, lastc;
    sigset_t sig, savesig;
    struct termios modes, savemodes;
    /*
     * Block keyboard signals.
     */
    sigemptyset(&sig);
    sigaddset(&sig, SIGINT);
    sigaddset(&sig, SIGQUIT);
    sigaddset(&sig, SIGTSTP);
    sigprocmask(SIG_BLOCK, &sig, &savesig);
    /*
     * Get current terminal attributes.
     */
    if (tcgetattr(0, &modes) < 0) {
        perror("tcgetattr");
        exit(1);
    }
    /*
     * Save a copy of them to restore later, and then
     * change the attributes to set character-at-a-time
     * input, turn off canonical mode, and turn off echo.
     */
    savemodes = modes;
    modes.c_cc[VMIN] = 1;
    modes.c_cc[VTIME] = 0;
    modes.c_lflag &= ~ICANON;
    modes.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE);
    /*
     * Make our changes take effect.
     */
    if (tcsetattr(0, TCSAFLUSH, &modes) < 0) {
        perror("tcsetattr");
        exit(1);
    }
    /*
     * Read characters.
     */
    while (read(0, &c, 1) > 0) {
        /*
         * Turn uppercase to lowercase and lowercase
         * to uppercase.
         */
        if (isupper(c))
            c = tolower(c);
        else if (islower(c))
            c = toupper(c);
        /*
         * Since non-canonical mode disables EOF,
         * we need to handle it ourselves.
         */
        if (c == savemodes.c_cc[VEOF] && lastc == '\n')
            break;
        /*
         * Output the new character and save
         * it.
         */
        write(1, &c, 1);
        lastc = c;
    }
    /*
     * Restore the original terminal attributes.
     */
    if (tcsetattr(0, TCSAFLUSH, &savemodes) < 0) {
        perror("tcsetattr");
        exit(1);
    }
    /*
     * Restore the original signal mask.
     */
    sigprocmask(SIG_SETMASK, &savesig, (sigset_t *) 0);
    exit(0);
}
As in our previous example, this program sets a signal mask to block keyboard interrupts. It then sets MIN and TIME for character-at-a-time input, turns off canonical mode, and disables character echo. The program then reads one character at a time. For each lowercase letter it encounters, it echos the uppercase equivalent. For each uppercase letter, it echos the lowercase equivalent. Because non-canonical mode disables most of the special characters, there is no way to signal an end-of-file from the keyboard to terminate this loop. Thus, the program must check the characters it reads to see if one of them is the EOF character (and that it occurs at the beginning of a line) and break out of the loop itself.
Emulating cbreak and raw modes
When porting software from BSD-based systems, it is common to encounter two modes not available in POSIX. These are cbreak mode, enabled by setting the CBREAK attribute, and raw mode, enabled by setting the RAW attribute. These modes were described in detail earlier.
To reproduce cbreak mode on a POSIX system:
 Enable non-canonical mode (turn off ICANON).
 Enable one character at a time input (set MIN to 1 and TIME to 0).
To reproduce raw mode:
 Enable non-canonical mode (turn off ICANON).
 Disable CR-to-NL mapping on input (turn off ICRNL).
 Disable input parity detection (turn off INPCK) and input parity checking (turn off PARENB).
 Disable stripping of the eighth bit on input (turn off ISTRIP).
 Disable output flow control (turn off IXON).
 Make sure characters are eight bits wide (turn on CS8).
 Disable all output processing (turn off OPOST).
 Enable one character at a time input (set MIN to 1 and TIME to 0).

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback