18.1. Introduction
The handling of terminal I/O is a messy area, regardless of the operating system. The UNIX System is no exception. The manual page for terminal I/O is usually one of the longest in most editions of the programmer's manuals.
With the UNIX System, a schism formed in the late 1970s when System III developed a different set of terminal routines from those of Version 7. The System III style of terminal I/O continued through System V, and the Version 7 style became the standard for the BSD-derived systems. As with signals, this difference between the two worlds has been conquered by POSIX.1. In this chapter, we look at all the POSIX.1 terminal functions and some of the platform-specific additions.
Part of the complexity of the terminal I/O system occurs because people use terminal I/O for so many different things: terminals, hardwired lines between computers, modems, printers, and so on.
18.2. Overview
Terminal I/O has two modes:
Canonical mode input processing. In this mode, terminal input is processed as lines. The terminal driver returns at most one line per read request. Noncanonical mode input processing. The input characters are not assembled into lines.
If we don't do anything special, canonical mode is the default. For example, if the shell redirects standard input to the terminal and we use read and write to copy standard input to standard output, the terminal is in canonical mode, and each read returns at most one line. Programs that manipulate the entire screen, such as the vi editor, use noncanonical mode, since the commands may be single characters and are not terminated by newlines. Also, this editor doesn't want processing by the system of the special characters, since they may overlap with the editor commands. For example, the Control-D character is often the end-of-file character for the terminal, but it's also a vi command to scroll down one-half screen.
The Version 7 and older BSD-style terminal drivers supported three modes for terminal input: (a) cooked mode (the input is collected into lines, and the special characters are processed), (b) raw mode (the input is not assembled into lines, and there is no processing of special characters), and (c) cbreak mode (the input is not assembled into lines, but some of the special characters are processed). Figure 18.20 shows a POSIX.1 function that places a terminal in cbreak or raw mode.
POSIX.1 defines 11 special input characters, 9 of which we can change. We've been using some of these throughout the text: the end-of-file character (usually Control-D) and the suspend character (usually Control-Z), for example. Section 18.3 describes each of these characters.
We can think of a terminal device as being controlled by a terminal driver, usually within the kernel. Each terminal device has an input queue and an output queue, shown in Figure 18.1.
There are several points to consider from this picture.
If echoing is enabled, there is an implied link between the input queue and the output queue. The size of the input queue, MAX_INPUT (see Figure 2.11), is finite. When the input queue for a particular device fills, the system behavior is implementation dependent. Most UNIX systems echo the bell character when this happens. There is another input limit, MAX_CANON, that we don't show here. This limit is the maximum number of bytes in a canonical input line. Although the size of the output queue is finite, no constants defining that size are accessible to the program, because when the output queue starts to fill up, the kernel simply puts the writing process to sleep until room is available. We'll see how the tcflush flush function allows us to flush either the input queue or the output queue. Similarly, when we describe the tcsetattr function, we'll see how we can tell the system to change the attributes of a terminal device only after the output queue is empty. (We want to do this, for example, if we're changing the output attributes.) We can also tell the system to discard everything in the input queue when changing the terminal attributes. (We want to do this if we're changing the input attributes or changing between canonical and noncanonical modes, so that previously entered characters aren't interpreted in the wrong mode.)
Most UNIX systems implement all the canonical processing in a module called the terminal line discipline. We can think of this module as a box that sits between the kernel's generic read and write functions and the actual device driver (see Figure 18.2).
Note the similarity of this picture and the diagram of a stream shown in Figure 14.14. We'll return to this picture in Chapter 19, when we discuss pseudo terminals.
All the terminal device characteristics that we can examine and change are contained in a termios structure. This structure is defined in the header <termios.h>, which we use throughout this chapter:
struct termios {
tcflag_t c_iflag; /* input flags */
tcflag_t c_oflag; /* output flags */
tcflag_t c_cflag; /* control flags */
tcflag_t c_lflag; /* local flags */
cc_t c_cc[NCCS]; /* control characters */
};
Roughly speaking, the input flags control the input of characters by the terminal device driver (strip eighth bit on input, enable input parity checking, etc.), the output flags control the driver output (perform output processing, map newline to CR/LF, etc.), the control flags affect the RS-232 serial lines (ignore modem status lines, one or two stop bits per character, etc.), and the local flags affect the interface between the driver and the user (echo on or off, visually erase characters, enable terminal-generated signals, job control stop signal for background output, etc.).
The type tcflag_t is big enough to hold each of the flag values and is often defined as an unsigned int or an unsigned long. The c_cc array contains all the special characters that we can change. NCCS is the number of elements in this array and is typically between 15 and 20 (since most implementations of the UNIX System support more than the 11 POSIX-defined special characters). The cc_t type is large enough to hold each special character and is typically an unsigned char.
Versions of System V that predated the POSIX standard had a header named <termio.h> and a structure named termio. POSIX.1 added an s to the names, to differentiate them from their predecessors.
Figures 18.3 through 18.6 list all the terminal flags that we can change to affect the characteristics of a terminal device. Note that even though the Single UNIX Specification defines a common subset that all platforms start from, all the implementations have their own additions. Most of these additions come from the historical differences between the systems. We'll discuss each of these flag values in detail in Section 18.5.
Figure 18.3. c_cflag terminal flagsFlag | Description | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
CBAUDEXT | extended baud rate | | | | | • | CCAR_OFLOW | DCD flow control of output | | • | | • | | CCTS_OFLOW | CTS flow control of output | | • | | • | • | CDSR_OFLOW | DSR flow control of output | | • | | • | | CDTR_IFLOW | DTR flow control of input | | • | | • | | CIBAUDEXT | extended input baud rate | | | | | • | CIGNORE | ignore control flags | | • | | • | | CLOCAL | ignore modem status lines | • | • | • | • | • | CREAD | enable receiver | • | • | • | • | • | CRTSCTS | enable hardware flow control | | • | • | • | • | CRTS_IFLOW | RTS flow control of input | | • | | • | • | CRTSXOFF | enable input hardware flow control | | | | | • | CSIZE | character size mask | • | • | • | • | • | CSTOPB | send two stop bits, else one | • | • | • | • | • | HUPCL | hang up on last close | • | • | • | • | • | MDMBUF | same as CCAR_OFLOW | | • | | • | | PARENB | parity enable | • | • | • | • | • | PAREXT | mark or space parity | | | | | • | PARODD | odd parity, else even | • | • | • | • | • |
Figure 18.4. c_iflag terminal flagsFlag | Description | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
BRKINT | generate SIGINT on BREAK | • | • | • | • | • | ICRNL | map CR to NL on input | • | • | • | • | • | IGNBRK | ignore BREAK condition | • | • | • | • | • | IGNCR | ignore CR | • | • | • | • | • | IGNPAR | ignore characters with parity errors | • | • | • | • | • | IMAXBEL | ring bell on input queue full | | • | • | • | • | INLCR | map NL to CR on input | • | • | • | • | • | INPCK | enable input parity checking | • | • | • | • | • | ISTRIP | strip eighth bit off input characters | • | • | • | • | • | IUCLC | map uppercase to lowercase on input | | | • | | • | IXANY | enable any characters to restart output | XSI | • | • | • | • | IXOFF | enable start/stop input flow control | • | • | • | • | • | IXON | enable start/stop output flow control | • | • | • | • | • | PARMRK | mark parity errors | • | • | • | • | • |
Figure 18.5. c_lflag terminal flagsFlag | Description | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
ALTWERASE | use alternate WERASE algorithm | | • | | • | | ECHO | enable echo | • | • | • | • | • | ECHOCTL | echo control chars as ^(Char) | | • | • | • | • | ECHOE | visually erase chars | • | • | • | • | • | ECHOK | echo kill | • | • | • | • | • | ECHOKE | visual erase for kill | | • | • | • | • | ECHONL | echo NL | • | • | • | • | • | ECHOPRT | visual erase mode for hard copy | | • | • | • | • | EXTPROC | external character processing | | • | | • | | FLUSHO | output being flushed | | • | • | • | • | ICANON | canonical input | • | • | • | • | • | IEXTEN | enable extended input char processing | • | • | • | • | • | ISIG | enable terminal-generated signals | • | • | • | • | • | NOFLSH | disable flush after interrupt or quit | • | • | • | • | • | NOKERNINFO | no kernel output from STATUS | | • | | • | | PENDIN | retype pending input | | • | • | • | • | TOSTOP | send SIGTTOU for background output | • | • | • | • | • | XCASE | canonical upper/lower presentation | | | • | | • |
Figure 18.6. c_oflag terminal flagsFlag | Description | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
BSDLY | backspace delay mask | XSI | | • | | • | CMSPAR | mark or space parity | | | • | | | CRDLY | CR delay mask | XSI | | • | | • | FFDLY | form feed delay mask | XSI | | • | | • | NLDLY | NL delay mask | XSI | | • | | • | OCRNL | map CR to NL on output | XSI | • | • | | • | OFDEL | fill is DEL, else NUL | XSI | | • | | • | OFILL | use fill character for delay | XSI | | • | | • | OLCUC | map lowercase to uppercase on output | | | • | | • | ONLCR | map NL to CR-NL | XSI | • | • | • | • | ONLRET | NL performs CR function | XSI | • | • | | • | ONOCR | no CR output at column 0 | XSI | • | • | | • | ONOEOT | discard EOTs (^D) on output | | • | | • | | OPOST | perform output processing | • | • | • | • | • | OXTABS | expand tabs to spaces | | • | | • | | TABDLY | horizontal tab delay mask | XSI | | • | | • | VtdLY | vertical tab delay mask | XSI | | • | | • |
Given all the options available, how do we examine and change these characteristics of a terminal device? Figure 18.7 summarizes the various functions defined by the Single UNIX Specification that operate on terminal devices. (All the functions listed are part of the base POSIX specification, except for tcgetsid, which is an XSI extension. We described tcgetpgrp, tcgetsid, and tcsetpgrp in Section 9.7.)
Figure 18.7. Summary of terminal I/O functionsFunction | Description |
---|
tcgetattr | fetch attributes (termios structure) | tcsetattr | set attributes (termios structure) | cfgetispeed | get input speed | cfgetospeed | get output speed | cfsetispeed | set input speed | cfsetospeed | set output speed | tcdrain | wait for all output to be transmitted | tcflow | suspend transmit or receive | tcflush | flush pending input and/or output | tcsendbreak | send BREAK character | tcgetpgrp | get foreground process group ID | tcsetpgrp | set foreground process group ID | tcgetsid | get process group ID of session leader for controlling TTY (XSI extension) |
Note that the Single UNIX Specification doesn't use the classic ioctl on terminal devices. Instead, it uses the 13 functions shown in Figure 18.7. The reason is that the ioctl function for terminal devices uses a different data type for its final argument, which depends on the action being performed. This makes type checking of the arguments impossible.
Although only 13 functions operate on terminal devices, the first two functions in Figure 18.7 (tcgetattr and tcsetattr) manipulate almost 70 different flags (see Figures 18.3 through 18.6). The handling of terminal devices is complicated by the large number of options available for terminal devices and trying to determine which options are required for a particular device (be it a terminal, modem, printer, or whatever).
The relationships among the 13 functions shown in Figure 18.7 are shown in Figure 18.8.
POSIX.1 doesn't specify where in the termios structure the baud rate information is stored; that is an implementation detail. Some systems, such as Linux and Solaris, store this information in the c_cflag field. BSD-derived systems, such as FreeBSD and Mac OS X, have two separate fields in the structure: one for the input speed and one for the output speed.
18.3. Special Input Characters
POSIX.1 defines 11 characters that are handled specially on input. Implementations define additional special characters. Figure 18.9 summarizes these special characters.
Figure 18.9. Summary of special terminal input charactersCharacter | Description | c_cc subscript | Enabled by | Typical value | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
| | | field | flag | | | | | | |
---|
CR | carriage return | (can't change) | c_lflag | ICANON | \r | • | • | • | • | • | DISCARD | discard output | VDISCARD | c_lflag | IEXTEN | ^O | | • | • | • | • | DSUSP | delayed suspend (SIGTSTP) | VDSUSP | c_lflag | ISIG | ^Y | | • | | • | • | EOF | end of file | VEOF | c_lflag | ICANON | ^D | • | • | • | • | • | EOL | end of line | VEOL | c_lflag | ICANON | | • | • | • | • | • | EOL2 | alternate end of line | VEOL2 | c_lflag | ICANON | | | • | • | • | • | ERASE | backspace one character | VERASE | c_lflag | ICANON | ^H, ^? | • | • | • | • | • | ERASE2 | alternate backspace character | VERASE2 | c_lflag | ICANON | ^H, ^? | | • | | | | INTR | interrupt signal (SIGINT) | VINTR | c_lflag | ISIG | ^?, ^C | • | • | • | • | • | KILL | erase line | VKILL | c_lflag | ICANON | ^U | • | • | • | • | • | LNEXT | literal next | VLNEXT | c_lflag | IEXTEN | ^V | | • | • | • | • | NL | line feed (newline) | (can't change) | c_lflag | ICANON | \n | • | • | • | • | • | QUIT | quit signal (SIGQUIT) | VQUIT | c_lflag | ISIG | ^\ | • | • | • | • | • | REPRINT | reprint all input | VREPRINT | c_lflag | ICANON | ^R | | • | • | • | • | START | resume output | VSTART | c_iflag | IXON/IXOFF | ^Q | • | • | • | • | • | STATUS | status request | VSTATUS | c_lflag | ICANON | ^T | | • | | • | | STOP | stop output | VSTOP | c_iflag | IXON/IXOFF | ^S | • | • | • | • | • | SUSP | suspend signal (SIGTSTP) | VSUSP | c_lflag | ISIG | ^Z | • | • | • | • | • | WERASE | backspace one word | VWERASE | c_lflag | ICANON | ^W | | • | • | • | • |
Of the 11 POSIX.1 special characters, we can change 9 of them to almost any value that we like. The exceptions are the newline and carriage return characters (\n and \r, respectively) and perhaps the STOP and START characters (depends on the implementation). To do this, we modify the appropriate entry in the c_cc array of the termios structure. The elements in this array are referred to by name, with each name beginning with a V (the third column in Figure 18.9).
POSIX.1 allows us to disable these characters. If we set the value of an entry in the c_cc array to the value of _POSIX_VDISABLE, then we disable the corresponding special character.
In older versions of the Single UNIX Specification, support for _POSIX_VDISABLE was optional. It is now required.
All four platforms discussed in this text support this feature. Linux 2.4.22 and Solaris 9 define _POSIX_VDISABLE as 0; FreeBSD 5.2.1 and Mac OS X 10.3 define it as 0xff.
Some earlier UNIX systems disabled a feature if the corresponding special input character was 0.
Example
Before describing all the special characters in detail, let's look at a small program that changes them. The program in Figure 18.10 disables the interrupt character and sets the end-of-file character to Control-B.
Note the following in this program.
We modify the terminal characters only if standard input is a terminal device. We call isatty (Section 18.9) to check this. We fetch the _POSIX_VDISABLE value using fpathconf. The function tcgetattr (Section 18.4) fetches a termios structure from the kernel. After we've modified this structure, we call tcsetattr to set the attributes. The only attributes that change are the ones we specifically modified. Disabling the interrupt key is different from ignoring the interrupt signal. The program in Figure 18.10 simply disables the special character that causes the terminal driver to generate SIGINT. We can still use the kill function to send the signal to the process.
Figure 18.10. Disable interrupt character and change end-of-file character
#include "apue.h"
#include <termios.h>
int
main(void)
{
struct termios term;
long vdisable;
if (isatty(STDIN_FILENO) == 0)
err_quit("standard input is not a terminal device");
if ((vdisable = fpathconf(STDIN_FILENO, _PC_VDISABLE)) < 0)
err_quit("fpathconf error or _POSIX_VDISABLE not in effect");
if (tcgetattr(STDIN_FILENO, &term) < 0) /* fetch tty state */
err_sys("tcgetattr error");
term.c_cc[VINTR] = vdisable; /* disable INTR character */
term.c_cc[VEOF] = 2; /* EOF is Control-B */
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &term) < 0)
err_sys("tcsetattr error");
exit(0);
}
We now describe each of the special characters in more detail. We call these the special input characters, but two of the characters, STOP and START (Control-S and Control-Q), are also handled specially when output. Note that when recognized by the terminal driver and processed specially, most of these special characters are then discarded: they are not returned to the process in a read operation. The exceptions to this are the newline characters (NL, EOL, EOL2) and the carriage return (CR).
CR | The carriage return character. We cannot change this character. This character is recognized on input in canonical mode. When both ICANON (canonical mode) and ICRNL (map CR to NL) are set and IGNCR (ignore CR) is not set, the CR character is translated to NL and has the same effect as a NL character. This character is returned to the reading process (perhaps after being translated to a NL). | DISCARD | The discard character. This character, recognized on input in extended mode (IEXTEN), causes subsequent output to be discarded until another DISCARD character is entered or the discard condition is cleared (see the FLUSHO option). This character is discarded when processed (i.e., it is not passed to the process). | DSUSP | The delayed-suspend job-control character. This character is recognized on input in extended mode (IEXTEN) if job control is supported and if the ISIG flag is set. Like the SUSP character, this delayed-suspend character generates the SIGTSTP signal that is sent to all processes in the foreground process group (refer to Figure 9.7). But the delayed-suspend character generates a signal only when a process reads from the controlling terminal, not when the character is typed. This character is discarded when processed (i.e., it is not passed to the process). | EOF | The end-of-file character. This character is recognized on input in canonical mode (ICANON). When we type this character, all bytes waiting to be read are immediately passed to the reading process. If no bytes are waiting to be read, a count of 0 is returned. Entering an EOF character at the beginning of the line is the normal way to indicate an end of file to a program. This character is discarded when processed in canonical mode (i.e., it is not passed to the process). | EOL | The additional line delimiter character, like NL. This character is recognized on input in canonical mode (ICANON) and is returned to the reading process; however, this character is not normally used. | EOL2 | Another line delimiter character, like NL. This character is treated identically to the EOL character. | ERASE | The erase character (backspace). This character is recognized on input in canonical mode (ICANON) and erases the previous character in the line, not erasing beyond the beginning of the line. This character is discarded when processed in canonical mode (i.e., it is not passed to the process). | ERASE2 | The alternate erase character (backspace). This character is treated exactly like the erase character (ERASE).
| INTR | The interrupt character. This character is recognized on input if the ISIG flag is set and generates the SIGINT signal that is sent to all processes in the foreground process group (refer to Figure 9.7). This character is discarded when processed (i.e., it is not passed to the process).
| KILL | The kill character. (The name "kill" is overused; recall the kill function used to send a signal to a process. This character should be called the line-erase character; it has nothing to do with signals.) It is recognized on input in canonical mode (ICANON). It erases the entire line and is discarded when processed (i.e., it is not passed to the process).
| LNEXT | The literal-next character. This character is recognized on input in extended mode (IEXTEN) and causes any special meaning of the next character to be ignored. This works for all special characters listed in this section. We can use this character to type any character to a program. The LNEXT character is discarded when processed, but the next character entered is passed to the process.
| NL | The newline character, which is also called the line delimiter. We cannot change this character. This character is recognized on input in canonical mode (ICANON). This character is returned to the reading process.
| QUIT | The quit character. This character is recognized on input if the ISIG flag is set. The quit character generates the SIGQUIT signal, which is sent to all processes in the foreground process group (refer to Figure 9.7). This character is discarded when processed (i.e., it is not passed to the process).
| | Recall from Figure 10.1 that the difference between INTR and QUIT is that the QUIT character not only terminates the process by default, but also generates a core file.
| REPRINT | The reprint character. This character is recognized on input in extended, canonical mode (both IEXTEN and ICANON flags set) and causes all unread input to be output (reechoed). This character is discarded when processed (i.e., it is not passed to the process).
| START | The start character. This character is recognized on input if the IXON flag is set and is automatically generated as output if the IXOFF flag is set. A received START character with IXON set causes stopped output (from a previously entered STOP character) to restart. In this case, the START character is discarded when processed (i.e., it is not passed to the process).
| | When IXOFF is set, the terminal driver automatically generates a START character to resume input that it had previously stopped, when the new input will not overflow the input buffer. | STATUS | The BSD status-request character. This character is recognized on input in extended, canonical mode (both IEXTEN and ICANON flags set) and generates the SIGINFO signal, which is sent to all processes in the foreground process group (refer to Figure 9.7). Additionally, if the NOKERNINFO flag is not set, status information on the foreground process group is also displayed on the terminal. This character is discarded when processed (i.e., it is not passed to the process).
| STOP | The stop character. This character is recognized on input if the IXON flag is set and is automatically generated as output if the IXOFF flag is set. A received STOP character with IXON set stops the output. In this case, the STOP character is discarded when processed (i.e., it is not passed to the process). The stopped output is restarted when a START character is entered.
| | When IXOFF is set, the terminal driver automatically generates a STOP character to prevent the input buffer from overflowing.
| SUSP | The suspend job-control character. This character is recognized on input if job control is supported and if the ISIG flag is set. The suspend character generates the SIGTSTP signal, which is sent to all processes in the foreground process group (refer to Figure 9.7). This character is discarded when processed (i.e., it is not passed to the process).
| WERASE | The word-erase character. This character is recognized on input in extended, canonical mode (both IEXTEN and ICANON flags set) and causes the previous word to be erased. First, it skips backward over any white space (spaces or tabs), then backward over the previous token, leaving the cursor positioned where the first character of the previous token was located. Normally, the previous token ends when a white space character is encountered. We can change this, however, by setting the ALTWERASE flag. This flag causes the previous token to end when the first nonalphanumeric character is encountered. The word-erase character is discarded when processed (i.e., it is not passed to the process). |
Another "character" that we need to define for terminal devices is the BREAK character. BREAK is not really a character, but rather a condition that occurs during asynchronous serial data transmission. A BREAK condition is signaled to the device driver in various ways, depending on the serial interface.
Most old serial terminals have a key labeled BREAK that generates the BREAK condition, which is why most people think of BREAK as a character. Some newer terminal keyboards don't have a BREAK key. On PCs, the break key might be mapped for other purpose. For example, the Windows command interpreter can be interrupted by typing Control-BREAK.
For asynchronous serial data transmission, a BREAK is a sequence of zero-valued bits that continues for longer than the time required to send one byte. The entire sequence of zero-valued bits is considered a single BREAK. In Section 18.8, we'll see how to send a BREAK with the tcsendbreak function.
18.4. Getting and Setting Terminal Attributes
To get and set a termios structure, we call two functions: tcgetattr and tcsetattr. This is how we examine and modify the various option flags and special characters to make the terminal operate the way we want it to.
#include <termios.h>
int tcgetattr(int filedes, struct termios *termptr);
int tcsetattr(int filedes, int opt, const struct
termios *termptr);
| Both return: 0 if OK, 1 on error |
Both functions take a pointer to a termios structure and either return the current terminal attributes or set the terminal's attributes. Since these two functions operate only on terminal devices, errno is set to ENOTTY and 1 is returned if filedes does not refer to a terminal device.
The argument opt for tcsetattr lets us specify when we want the new terminal attributes to take effect. This argument is specified as one of the following constants.
TCSANOW | The change occurs immediately. | TCSADRAIN | The change occurs after all output has been transmitted. This option should be used if we are changing the output parameters. | TCSAFLUSH | The change occurs after all output has been transmitted. Furthermore, when the change takes place, all input data that has not been read is discarded (flushed). |
The return status of tcsetattr confuses the programming. This function returns OK if it was able to perform any of the requested actions, even if it couldn't perform all the requested actions. If the function returns OK, it is our responsibility to see whether all the requested actions were performed. This means that after we call tcsetattr to set the desired attributes, we need to call tcgetattr and compare the actual terminal's attributes to the desired attributes to detect any differences.
18.5. Terminal Option Flags
In this section, we list all the various terminal option flags, expanding the descriptions of all the options from Figures 18.3 through 18.6. This list is alphabetical and indicates in which of the four terminal flag fields the option appears. (The field a given option is controlled by is usually not apparent from the option name alone.) We also note whether each option is defined by the Single UNIX Specification and list the platforms that support it.
All the flags listed specify one or more bits that we turn on or clear, unless we call the flag a mask. A mask defines multiple bits grouped together from which a set of values is defined. We have a defined name for the mask and a name for each value. For example, to set the character size, we first zero the bits using the character-size mask CSIZE, and then set one of the values CS5, CS6, CS7, or CS8.
The six delay values supported by Linux and Solaris are also masks: BSDLY, CRDLY, FFDLY, NLDLY, TABDLY, and VtdLY. Refer to the termio(7I) manual page on Solaris for the length of each delay value. In all cases, a delay mask of 0 means no delay. If a delay is specified, the OFILL and OFDEL flags determine whether the driver does an actual delay or whether fill characters are transmitted instead.
Example
Figure 18.11 demonstrates the use of these masks to extract a value and to set a value.
Figure 18.11. Example of tcgetattr and tcsetattr
#include "apue.h"
#include <termios.h>
int
main(void)
{
struct termios term;
if (tcgetattr(STDIN_FILENO, &term) < 0)
err_sys("tcgetattr error");
switch (term.c_cflag & CSIZE) {
case CS5:
printf("5 bits/byte\n");
break;
case CS6:
printf("6 bits/byte\n");
break;
case CS7:
printf("7 bits/byte\n");
break;
case CS8:
printf("8 bits/byte\n");
break;
default:
printf("unknown bits/byte\n");
}
term.c_cflag &= ~CSIZE; /* zero out the bits */
term.c_cflag |= CS8; /* set 8 bits/byte */
if (tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0)
err_sys("tcsetattr error");
exit(0);
}
We now describe each of the flags.
ALTWERASE | (c_lflag, FreeBSD, Mac OS X) If set, an alternate word-erase algorithm is used when the WERASE character is entered. Instead of moving backward until the previous white space character, this flag causes the WERASE character to move backward until the first nonalphanumeric character is encountered.
| BRKINT | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If this flag is set and IGNBRK is not set, the input and output queues are flushed when a BREAK is received, and a SIGINT signal is generated. This signal is generated for the foreground process group if the terminal device is a controlling terminal.
| | If neither IGNBRK nor BRKINT is set, then a BREAK is read as a single character \0, unless PARMRK is set, in which case the BREAK is read as the 3-byte sequence \377, \0, \0.
| BSDLY | (c_oflag, XSI, Linux, Solaris) Backspace delay mask. The values for the mask are BS0 or BS1.
| CBAUDEXT | (c_cflag, Solaris) Extended baud rates. Used to enable baud rates greater than B38400. (We discuss baud rates in Section 18.7.)
| CCAR_OFLOW | (c_cflag, FreeBSD, Mac OS X) Enable hardware flow control of the output using the RS-232 modem carrier signal (DCD, known as Data- Carrier-Detect). This is the same as the old MDMBUF flag.
| CCTS_OFLOW | (c_cflag, FreeBSD, Mac OS X, Solaris) Enable hardware flow control of the output using the Clear-To-Send (CTS) RS-232 signal.
| CDSR_OFLOW | (c_cflag, FreeBSD, Mac OS X) Flow control the output according to the Data-Set-Ready (DSR) RS-232 signal.
| CDTR_IFLOW | (c_cflag, FreeBSD, Mac OS X) Flow control the input according to the Data-Terminal-Ready (DTR) RS-232 signal.
| CIBAUDEXT | (c_cflag, Solaris) Extended input baud rates. Used to enable input baud rates greater than B38400. (We discuss baud rates in Section 18.7.) | CIGNORE | (c_cflag, FreeBSD, Mac OS X) Ignore control flags.
| CLOCAL | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, the modem status lines are ignored. This usually means that the device is directly attached. When this flag is not set, an open of a terminal device usually blocks until the modem answers a call and establishes a connection, for example.
| CMSPAR | (c_oflag, Linux) Select mark or space parity. If PARODD is set, the parity bit is always 1 (mark parity). Otherwise, the parity bit is always 0 (space parity).
| CRDLY | (c_oflag, XSI, Linux, Solaris) Carriage return delay mask. The values for the mask are CR0, CR1, CR2, or CR3. | CREAD | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, the receiver is enabled, and characters can be received.
| CRTSCTS | (c_cflag, FreeBSD, Linux, Mac OS X, Solaris) Behavior depends on platform. For Solaris, enables outbound hardware flow control if set. On the other three platforms, enables both inbound and outbound hardware flow control (equivalent to CCTS_OFLOW|CRTS_IFLOW).
| CRTS_IFLOW | (c_cflag, FreeBSD, Mac OS X, Solaris) Request-To-Send (RTS) flow control of input.
| CRTSXOFF | (c_cflag, Solaris) If set, inbound hardware flow control is enabled. The state of the Request-To-Send RS-232 signal controls the flow control.
| CSIZE | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) This field is a mask that specifies the number of bits per byte for both transmission and reception. This size does not include the parity bit, if any. The values for the field defined by this mask are CS5, CS6, CS7, and CS8, for 5, 6, 7, and 8 bits per byte, respectively.
| CSTOPB | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, two stop bits are used; otherwise, one stop bit is used.
| ECHO | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, input characters are echoed back to the terminal device. Input characters can be echoed in either canonical or noncanonical mode.
| ECHOCTL | (c_lflag, FreeBSD, Linux, Mac OS X, Solaris) If set and if ECHO is set, ASCII control characters (those characters in the range 0 through octal 37, inclusive) other than the ASCII TAB, the ASCII NL, and the START and STOP characters are echoed as ^X, where X is the character formed by adding octal 100 to the control character. This means that the ASCII Control-A character (octal 1) is echoed as ^A. Also, the ASCII DELETE character (octal 177) is echoed as ^?. If this flag is not set, the ASCII control characters are echoed as themselves. As with the ECHO flag, this flag affects the echoing of control characters in both canonical and noncanonical modes.
| | Be aware that some systems echo the EOF character differently, since its typical value is Control-D. (Control-D is the ASCII EOT character, which can cause some terminals to hang up.) Check your manual.
| ECHOE | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set and if ICANON is set, the ERASE character erases the last character in the current line from the display. This is usually done in the terminal driver by writing the three-character sequence backspace, space, backspace.
| | If the WERASE character is supported, ECHOE causes the previous word to be erased using one or more of the same three-character sequence.
| | If the ECHOPRT flag is supported, the actions described here for ECHOE assume that the ECHOPRT flag is not set. | ECHOK | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set and if ICANON is set, the KILL character erases the current line from the display or outputs the NL character (to emphasize that the entire line was erased).
| | If the ECHOKE flag is supported, this description of ECHOK assumes that ECHOKE is not set.
| ECHOKE | (c_lflag, FreeBSD, Linux, Mac OS X, Solaris) If set and if ICANON is set, the KILL character is echoed by erasing each character on the line. The way in which each character is erased is selected by the ECHOE and ECHOPRT flags.
| ECHONL | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set and if ICANON is set, the NL character is echoed, even if ECHO is not set.
| ECHOPRT | (c_lflag, FreeBSD, Linux, Mac OS X, Solaris) If set and if both ICANON and ECHO are set, then the ERASE character (and WERASE character, if supported) cause all the characters being erased to be printed as they are erased. This is often useful on a hard-copy terminal to see exactly which characters are being deleted.
| EXTPROC | (c_lflag, FreeBSD, Mac OS X) If set, canonical character processing is performed external to the operating system. This can be the case if the serial communication peripheral card can offload the host processor by doing some of the line discipline processing. This can also be the case when using pseudo terminals (Chapter 19).
| FFDLY | (c_oflag, XSI, Linux, Solaris) Form feed delay mask. The values for the mask are FF0 or FF1.
| FLUSHO | (c_lflag, FreeBSD, Linux, Mac OS X, Solaris) If set, output is being flushed. This flag is set when we type the DISCARD character; the flag is cleared when we type another DISCARD character. We can also set or clear this condition by setting or clearing this terminal flag.
| HUPCL | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, the modem control lines are lowered (i.e., the modem connection is broken) when the last process closes the device.
| ICANON | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, canonical mode is in effect (Section 18.10). This enables the following characters: EOF, EOL, EOL2, ERASE, KILL, REPRINT, STATUS, and WERASE. The input characters are assembled into lines.
| | If canonical mode is not enabled, read requests are satisfied directly from the input queue. A read does not return until at least MIN bytes have been received or the timeout value TIME has expired between bytes. Refer to Section 18.11 for additional details. | ICRNL | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set and if IGNCR is not set, a received CR character is translated into a NL character.
| IEXTEN | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, the extended, implementation-defined special characters are recognized and processed.
| IGNBRK | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) When set, a BREAK condition on input is ignored. See BRKINT for a way to have a BREAK condition either generate a SIGINT signal or be read as data.
| IGNCR | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, a received CR character is ignored. If this flag is not set, it is possible to translate the received CR into a NL character if the ICRNL flag is set.
| IGNPAR | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) When set, an input byte with a framing error (other than a BREAK) or an input byte with a parity error is ignored.
| IMAXBEL | (c_iflag, FreeBSD, Linux, Mac OS X, Solaris) Ring bell when input queue is full.
| INLCR | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, a received NL character is translated into a CR character.
| INPCK | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) When set, input parity checking is enabled. If INPCK is not set, input parity checking is disabled.
| | Parity "generation and detection" and "input parity checking" are two different things. The generation and detection of parity bits is controlled by the PARENB flag. Setting this flag usually causes the device driver for the serial interface to generate parity for outgoing characters and to verify the parity of incoming characters. The flag PARODD determines whether the parity should be odd or even. If an input character arrives with the wrong parity, then the state of the INPCK flag is checked. If this flag is set, then the IGNPAR flag is checked (to see whether the input byte with the parity error should be ignored); if the byte should not be ignored, then the PARMRK flag is checked to see what characters should be passed to the reading process.
| ISIG | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, the input characters are compared against the special characters that cause the terminal-generated signals to be generated (INTR, QUIT, SUSP, and DSUSP); if equal, the corresponding signal is generated.
| ISTRIP | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) When set, valid input bytes are stripped to 7 bits. When this flag is not set, all 8 bits are processed. | IUCLC | (c_iflag, Linux, Solaris) Map uppercase to lowercase on input.
| IXANY | (c_iflag, XSI, FreeBSD, Linux, Mac OS X, Solaris) Enable any characters to restart output.
| IXOFF | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, startstop input control is enabled. When it notices that the input queue is getting full, the terminal driver outputs a STOP character. This character should be recognized by the device that is sending the data and cause the device to stop. Later, when the characters on the input queue have been processed, the terminal driver will output a START character. This should cause the device to resume sending data.
| IXON | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, startstop output control is enabled. When the terminal driver receives a STOP character, output stops. While the output is stopped, the next START character resumes the output. If this flag is not set, the START and STOP characters are read by the process as normal characters.
| MDMBUF | (c_cflag, FreeBSD, Mac OS X) Flow control the output according to the modem carrier flag. This is the old name for the CCAR_OFLOW flag.
| NLDLY | (c_oflag, XSI, Linux, Solaris) Newline delay mask. The values for the mask are NL0 or NL1.
| NOFLSH | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) By default, when the terminal driver generates the SIGINT and SIGQUIT signals, both the input and output queues are flushed. Also, when it generates the SIGSUSP signal, the input queue is flushed. If the NOFLSH flag is set, this normal flushing of the queues does not occur when these signals are generated.
| NOKERNINFO | (c_lflag, FreeBSD, Mac OS X) When set, this flag prevents the STATUS character from printing information on the foreground process group. Regardless of this flag, however, the STATUS character still causes the SIGINFO signal to be sent to the foreground process group.
| OCRNL | (c_oflag, XSI, FreeBSD, Linux, Solaris) If set, map CR to NL on output.
| OFDEL | (c_oflag, XSI, Linux, Solaris) If set, the output fill character is ASCII DEL; otherwise, it's ASCII NUL. See the OFILL flag. | OFILL | (c_oflag, XSI, Linux, Solaris) If set, fill characters (either ASCII DEL or ASCII NUL; see the OFDEL flag) are transmitted for a delay, instead of using a timed delay. See the six delay masks: BSDLY, CRDLY, FFDLY, NLDLY, TABDLY, and VtdLY.
| OLCUC | (c_oflag, Linux, Solaris) If set, map lowercase characters to uppercase characters on output. | ONLCR | (c_oflag, XSI, FreeBSD, Linux, Mac OS X, Solaris) If set, map NL to CR-NL on output.
| ONLRET | (c_oflag, XSI, FreeBSD, Linux, Solaris) If set, the NL character is assumed to perform the carriage return function on output.
| ONOCR | (c_oflag, XSI, FreeBSD, Linux, Solaris) If set, a CR is not output at column 0.
| ONOEOT | (c_oflag, FreeBSD, Mac OS X) If set, EOT (^D) characters are discarded on output. This may be necessary on some terminals that interpret the Control-D as a hangup.
| OPOST | (c_oflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, implementation-defined output processing takes place. Refer to Figure 18.6 for the various implementation-defined flags for the c_oflag word.
| OXTABS | (c_oflag, FreeBSD, Mac OS X) If set, tabs are expanded to spaces on output. This produces the same effect as setting the horizontal tab delay (TABDLY)to XTABS or TAB3.
| PARENB | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, parity generation is enabled for outgoing characters, and parity checking is performed on incoming characters. The parity is odd if PARODD is set; otherwise, it is even parity. See also the discussion of the INPCK, IGNPAR, and PARMRK flags.
| PAREXT | (c_cflag, Solaris) Select mark or space parity. If PARODD is set, the parity bit is always 1 (mark parity). Otherwise, the parity bit is always 0 (space parity).
| PARMRK | (c_iflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) When set and if IGNPAR is not set, a byte with a framing error (other than a BREAK) or a byte with a parity error is read by the process as the three-character sequence \377, \0, X, where X is the byte received in error. If ISTRIP is not set, a valid \377 is passed to the process as \377, \377. If neither IGNPAR nor PARMRK is set, a byte with a framing error (other than a BREAK) or with a parity error is read as a single character \0.
| PARODD | (c_cflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set, the parity for outgoing and incoming characters is odd parity. Otherwise, the parity is even parity. Note that the PARENB flag controls the generation and detection of parity.
| | The PARODD flag also controls whether mark or space parity is used when either the CMSPAR or PAREXT flag is set.
| PENDIN | (c_lflag, FreeBSD, Linux, Mac OS X, Solaris) If set, any input that has not been read is reprinted by the system when the next character is input. This action is similar to what happens when we type the REPRINT character. | TABDLY | (c_oflag, XSI, Linux, Solaris) Horizontal tab delay mask. The values for the mask are TAB0, TAB1, TAB2, or TAB3.
| | The value XTABS is equal to TAB3. This value causes the system to expand tabs into spaces. The system assumes a tab stop every eight spaces, and we can't change this assumption.
| TOSTOP | (c_lflag, POSIX.1, FreeBSD, Linux, Mac OS X, Solaris) If set and if the implementation supports job control, the SIGTTOU signal is sent to the process group of a background process that tries to write to its controlling terminal. By default, this signal stops all the processes in the process group. This signal is not generated by the terminal driver if the background process that is writing to the controlling terminal is either ignoring or blocking the signal.
| VTDLY | (c_oflag, XSI, Linux, Solaris) Vertical tab delay mask. The values for the mask are VT0 or VT1.
| XCASE | (c_lflag, Linux, Solaris) If set and if ICANON is also set, the terminal is assumed to be uppercase only, and all input is converted to lowercase. To input an uppercase character, precede it with a backslash. Similarly, an uppercase character is output by the system by being preceded by a backslash. (This option flag is obsolete today, since most, if not all, uppercase-only terminals have disappeared.) |
18.6. stty Command
All the options described in the previous section can be examined and changed from within a program, with the tcgetattr and tcsetattr functions (Section 18.4) or from the command line (or a shell script), with the stty(1) command. This command is simply an interface to the first six functions that we listed in Figure 18.7. If we execute this command with its -a option, it displays all the terminal options:
$ stty -a
speed 9600 baud; 25 rows; 80 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
-extproc
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -ignbrk
brkint -inpck -ignpar -parmrk
oflags: opost onlcr -ocrnl -oxtabs -onocr -onlret
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts
-dsrflow -dtrflow -mdmbuf
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
eol2 = <undef>; erase = ^H; erase2 = ^?; intr = ^C; kill = ^U;
lnext = ^V; min = 1; quit = ^; reprint = ^R; start = ^Q;
status = ^T; stop = ^S; susp = ^Z; time = 0; werase = ^W;
Option names preceded by a hyphen are disabled. The last four lines display the current settings for each of the terminal special characters (Section 18.3). The first line displays the number of rows and columns for the current terminal window; we discuss this in Section 18.12.
The stty command uses its standard input to get and set the terminal option flags. Although some older implementations used standard output, POSIX.1 requires that the standard input be used. All four implementations discussed in this text provide versions of stty that operate on standard input. This means that we can type
stty -a </dev/tty1a
if we are interested in discovering the settings on the terminal named tty1a.
18.7. Baud Rate Functions
The term baud rate is a historical term that should be referred to today as "bits per second." Although most terminal devices use the same baud rate for both input and output, the capability exists to set the two to different values, if the hardware allows this.
#include <termios.h>
speed_t cfgetispeed(const struct termios *termptr);
speed_t cfgetospeed(const struct termios *termptr);
| Both return: baud rate value
|
int cfsetispeed(struct termios *termptr, speed_t
speed);
int cfsetospeed(struct termios *termptr, speed_t
speed);
| Both return: 0 if OK, 1 on error |
The return value from the two cfget functions and the speed argument to the two cfset functions are one of the following constants: B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, or B38400. The constant B0 means "hang up." When B0 is specified as the output baud rate when tcsetattr is called, the modem control lines are no longer asserted.
Most systems define additional baud rate values, such as B57600 and B115200.
To use these functions, we must realize that the input and output baud rates are stored in the device's termios structure, as shown in Figure 18.8. Before calling either of the cfget functions, we first have to obtain the device's termios structure using tcgetattr. Similarly, after calling either of the two cfset functions, all we've done is set the baud rate in a termios structure. For this change to affect the device, we have to call tcsetattr. If there is an error in either of the baud rates that we set, we may not find out about the error until we call tcsetattr.
The four baud rate functions exist to insulate applications from differences in the way that implementations represent baud rates in the termios structure. BSD-derived platforms tend to store baud rates as numeric values equal to the rates (i.e., 9,600 baud is stored as the value 9,600), whereas Linux and System Vderived platforms tend to encode the baud rate in a bitmask. The speed values we get from the cfget functions and pass to the cfset functions are untranslated from their representation as they are stored in the termios structure.
18.8. Line Control Functions
The following four functions provide line control capability for terminal devices. All four require that filedes refer to a terminal device; otherwise, an error is returned with errno set to ENOTTY.
#include <termios.h>
int tcdrain(int filedes);
int tcflow(int filedes, int action);
int tcflush(int filedes, int queue);
int tcsendbreak(int filedes, int duration);
| All four return: 0 if OK, 1 on error
|
The tcdrain function waits for all output to be transmitted. The tcflow function gives us control over both input and output flow control. The action argument must be one of the following four values:
TCOOFF | Output is suspended. | TCOON | Output that was previously suspended is restarted. | TCIOFF | The system transmits a STOP character, which should cause the terminal device to stop sending data. | TCION | The system transmits a START character, which should cause the terminal device to resume sending data. |
The tcflush function lets us flush (throw away) either the input buffer (data that has been received by the terminal driver, which we have not read) or the output buffer (data that we have written, which has not yet been transmitted). The queue argument must be one of the following three constants:
TCIFLUSH | The input queue is flushed. | TCOFLUSH | The output queue is flushed. | TCIOFLUSH | Both the input and the output queues are flushed. |
The tcsendbreak function transmits a continuous stream of zero bits for a specified duration. If the duration argument is 0, the transmission lasts between 0.25 seconds and 0.5 seconds. POSIX.1 specifies that if duration is nonzero, the transmission time is implementation dependent.
18.9. Terminal Identification
Historically, the name of the controlling terminal in most versions of the UNIX System has been /dev/tty. POSIX.1 provides a runtime function that we can call to determine the name of the controlling terminal.
#include <stdio.h>
char *ctermid(char *ptr);
| Returns: pointer to name of controlling terminal on success, pointer to empty string on error |
If ptr is non-null, it is assumed to point to an array of at least L_ctermid bytes, and the name of the controlling terminal of the process is stored in the array. The constant L_ctermid is defined in <stdio.h>. If ptr is a null pointer, the function allocates room for the array (usually as a static variable). Again, the name of the controlling terminal of the process is stored in the array.
In both cases, the starting address of the array is returned as the value of the function. Since most UNIX systems use /dev/tty as the name of the controlling terminal, this function is intended to aid portability to other operating systems.
All four platforms described in this text return the string /dev/tty when we call ctermid.
Examplectermid Function
Figure 18.12 shows an implementation of the POSIX.1 ctermid function.
Note that we can't protect against overrunning the caller's buffer, because we have no way to determine its size.
Figure 18.12. Implementation of POSIX.1 ctermid function
#include <stdio.h>
#include <string.h>
static char ctermid_name[L_ctermid];
char *
ctermid(char *str)
{
if (str == NULL)
str = ctermid_name;
return(strcpy(str, "/dev/tty")); /* strcpy() returns str */
}
Two functions that are more interesting for a UNIX system are isatty, which returns true if a file descriptor refers to a terminal device, and ttyname, which returns the pathname of the terminal device that is open on a file descriptor.
#include <unistd.h>
int isatty(int filedes);
| Returns: 1 (true) if terminal device, 0 (false) otherwise
|
char *ttyname(int filedes);
| Returns: pointer to pathname of terminal, NULL on error |
Exampleisatty Function
The isatty function is trivial to implement, as we show in Figure 18.13. We simply try one of the terminal-specific functions (that doesn't change anything if it succeeds) and look at the return value.
We test our isatty function with the program in Figure 18.14.
When we run the program from Figure 18.14, we get the following output:
$ ./a.out
fd 0: tty
fd 1: tty
fd 2: tty
$ ./a.out </etc/passwd 2>/dev/null
fd 0: not a tty
fd 1: tty
fd 2: not a tty
Figure 18.13. Implementation of POSIX.1 isatty function
#include <termios.h>
int
isatty(int fd)
{
struct termios ts;
return(tcgetattr(fd, &ts) != -1); /* true if no error (is a tty) */
}
Figure 18.14. Test the isatty function
#include "apue.h"
int
main(void)
{
printf("fd 0: %s\n", isatty(0) ? "tty" : "not a tty");
printf("fd 1: %s\n", isatty(1) ? "tty" : "not a tty");
printf("fd 2: %s\n", isatty(2) ? "tty" : "not a tty");
exit(0);
}
Examplettyname Function
The ttyname function (Figure 18.15) is longer, as we have to search all the device entries, looking for a match.
The technique is to read the /dev directory, looking for an entry with the same device number and i-node number. Recall from Section 4.23 that each file system has a unique device number (the st_dev field in the stat structure, from Section 4.2), and each directory entry in that file system has a unique i-node number (the st_ino field in the stat structure). We assume in this function that when we hit a matching device number and matching i-node number, we've located the desired directory entry. We could also verify that the two entries have matching st_rdev fields (the major and minor device numbers for the terminal device) and that the directory entry is also a character special file. But since we've already verified that the file descriptor argument is both a terminal device and a character special file, and since a matching device number and i-node number is unique on a UNIX system, there is no need for the additional comparisons.
The name of our terminal might reside in a subdirectory in /dev. Thus, we might need to search the entire file system tree under /dev. We skip several directories that might produce incorrect or odd-looking results: /dev/., /dev/.., and /dev/fd. We also skip the aliases /dev/stdin, /dev/stdout, and /dev/stderr, since they are symbolic links to files in /dev/fd.
We can test this implementation with the program shown in Figure 18.16.
Running the program from Figure 18.16 gives us
$ ./a.out < /dev/console 2> /dev/null
fd 0: /dev/console
fd 1: /dev/ttyp3
fd 2: not a tty
Figure 18.15. Implementation of POSIX.1 ttyname function
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct devdir {
struct devdir *d_next;
char *d_name;
};
static struct devdir *head;
static struct devdir *tail;
static char pathname[_POSIX_PATH_MAX + 1];
static void
add(char *dirname)
{
struct devdir *ddp;
int len;
len = strlen(dirname);
/*
* Skip ., .., and /dev/fd.
*/
if ((dirname[len-1] == '.') && (dirname[len-2] == '/' ||
(dirname[len-2] == '.' && dirname[len-3] == '/')))
return;
if (strcmp(dirname, "/dev/fd") == 0)
return;
ddp = malloc(sizeof(struct devdir));
if (ddp == NULL)
return;
ddp->d_name = strdup(dirname);
if (ddp->d_name == NULL) {
free(ddp);
return;
}
ddp->d_next = NULL;
if (tail == NULL) {
head = ddp;
tail = ddp;
} else {
tail->d_next = ddp;
tail = ddp;
}
}
static void
cleanup(void)
{
struct devdir *ddp, *nddp;
ddp = head;
while (ddp != NULL) {
nddp = ddp->d_next;
free(ddp->d_name);
free(ddp);
ddp = nddp;
}
head = NULL;
tail = NULL;
}
static char *
searchdir(char *dirname, struct stat *fdstatp)
{
struct stat devstat;
DIR *dp;
int devlen;
struct dirent *dirp;
strcpy(pathname, dirname);
if ((dp = opendir(dirname)) == NULL)
return(NULL);
strcat(pathname, "/");
devlen = strlen(pathname);
while ((dirp = readdir(dp)) != NULL) {
strncpy(pathname + devlen, dirp->d_name,
_POSIX_PATH_MAX - devlen);
/*
* Skip aliases.
*/
if (strcmp(pathname, "/dev/stdin") == 0 ||
strcmp(pathname, "/dev/stdout") == 0 ||
strcmp(pathname, "/dev/stderr") == 0)
continue;
if (stat(pathname, &devstat) < 0)
continue;
if (S_ISDIR(devstat.st_mode)) {
add(pathname);
continue;
}
if (devstat.st_ino == fdstatp->st_ino &&
devstat.st_dev == fdstatp->st_dev) { /* found a match */
closedir(dp);
return(pathname);
}
}
closedir(dp);
return(NULL);
}
char *
ttyname(int fd)
{
struct stat fdstat;
struct devdir *ddp;
char *rval;
if (isatty(fd) == 0)
return(NULL);
if (fstat(fd, &fdstat) < 0)
return(NULL);
if (S_ISCHR(fdstat.st_mode) == 0)
return(NULL);
rval = searchdir("/dev", &fdstat);
if (rval == NULL) {
for (ddp = head; ddp != NULL; ddp = ddp->d_next)
if ((rval = searchdir(ddp->d_name, &fdstat)) != NULL)
break;
}
cleanup();
return(rval);
}
Figure 18.16. Test the ttyname function
#include "apue.h"
int
main(void)
{
char *name;
if (isatty(0)) {
name = ttyname(0);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 0: %s\n", name);
if (isatty(1)) {
name = ttyname(1);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 1: %s\n", name);
if (isatty(2)) {
name = ttyname(2);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 2: %s\n", name);
exit(0);
}
18.10. Canonical Mode
Canonical mode is simple: we issue a read, and the terminal driver returns when a line has been entered. Several conditions cause the read to return.
The read returns when the requested number of bytes have been read. We don't have to read a complete line. If we read a partial line, no information is lost; the next read starts where the previous read stopped. The read returns when a line delimiter is encountered. Recall from Section 18.3 that the following characters are interpreted as end of line in canonical mode: NL, EOL, EOL2, and EOF. Also, recall from Section 18.5 that if ICRNL is set and if IGNCR is not set, then the CR character also terminates a line, since it acts just like the NL character. Realize that of these five line delimiters, one (EOF) is discarded by the terminal driver when it's processed. The other four are returned to the caller as the last character of the line. The read also returns if a signal is caught and if the function is not automatically restarted (Section 10.5).
Examplegetpass Function
We now show the function getpass, which reads a password of some type from the user at a terminal. This function is called by the login(1) and crypt(1) programs. To read the password, the function must turn off echoing, but it can leave the terminal in canonical mode, as whatever we type as the password forms a complete line. Figure 18.17 shows a typical implementation on a UNIX system.
There are several points to consider in this example.
Instead of hardwiring /dev/tty into the program, we call the function ctermid to open the controlling terminal. We read and write only to the controlling terminal and return an error if we can't open this device for reading and writing. There are other conventions to use. The BSD version of getpass reads from standard input and writes to standard error if the controlling terminal can't be opened for reading and writing. The System V version always writes to standard error but reads only from the controlling terminal. We block the two signals SIGINT and SIGTSTP. If we didn't do this, entering the INTR character would abort the program and leave the terminal with echoing disabled. Similarly, entering the SUSP character would stop the program and return to the shell with echoing disabled. We choose to block the signals while we have echoing disabled. If they are generated while we're reading the password, they are held until we return. There are other ways to handle these signals. Some versions just ignore SIGINT (saving its previous action) while in getpass, resetting the action for this signal to its previous value before returning. This means that any occurrence of the signal while it's ignored is lost. Other versions catch SIGINT (saving its previous action) and if the signal is caught, send themselves the signal with the kill function after resetting the terminal state and signal action. None of the versions of getpass catch, ignore, or block SIGQUIT, so entering the QUIT character aborts the program and probably leaves the terminal with echoing disabled. Be aware that some shells, notably the Korn shell, turn echoing back on whenever they read interactive input. These shells are the ones that provide command-line editing and therefore manipulate the state of the terminal every time we enter an interactive command. So, if we invoke this program under one of these shells and abort it with the QUIT character, it may reenable echoing for us. Other shells that don't provide this form of command-line editing, such as the Bourne shell, will abort the program and leave the terminal in no-echo mode. If we do this to our terminal, the stty command can reenable echoing. We use standard I/O to read and write the controlling terminal. We specifically set the stream to be unbuffered; otherwise, there might be some interactions between the writing and reading of the stream (we would need some calls to fflush). We could have also used unbuffered I/O (Chapter 3), but we would have to simulate the getc function using read. We store only up to eight characters as the password. Any additional characters that are entered are ignored.
The program in Figure 18.18 calls getpass and prints what we enter to let us verify that the ERASE and KILL characters work (as they should in canonical mode).
Whenever a program that calls getpass is done with the cleartext password, the program should zero it out in memory, just to be safe. If the program were to generate a core file that others might be able to read or if some other process were somehow able to read our memory, they might be able to read the cleartext password. (By "cleartext," we mean the password that we type at the prompt that is printed by getpass. Most UNIX system programs then modify this cleartext password into an "encrypted" password. The field pw_passwd in the password file, for example, contains the encrypted password, not the cleartext password.)
Figure 18.17. Implementation of getpass function
#include <signal.h>
#include <stdio.h>
#include <termios.h>
#define MAX_PASS_LEN 8 /* max #chars for user to enter */
char *
getpass(const char *prompt)
{
static char buf[MAX_PASS_LEN + 1]; /* null byte at end */
char *ptr;
sigset_t sig, osig;
struct termios ts, ots;
FILE *fp;
int c;
if ((fp = fopen(ctermid(NULL), "r+")) == NULL)
return(NULL);
setbuf(fp, NULL);
sigemptyset(&sig);
sigaddset(&sig, SIGINT); /* block SIGINT */
sigaddset(&sig, SIGTSTP); /* block SIGTSTP */
sigprocmask(SIG_BLOCK, &sig, &osig); /* and save mask */
tcgetattr(fileno(fp), &ts); /* save tty state */
ots = ts; /* structure copy */
ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
tcsetattr(fileno(fp), TCSAFLUSH, &ts);
fputs(prompt, fp);
ptr = buf;
while ((c = getc(fp)) != EOF && c != '\n')
if (ptr < &buf[MAX_PASS_LEN])
*ptr++ = c;
*ptr = 0; /* null terminate */
putc('\n', fp); /* we echo a newline */
tcsetattr(fileno(fp), TCSAFLUSH, &ots); /* restore TTY state */
sigprocmask(SIG_SETMASK, &osig, NULL); /* restore mask */
fclose(fp); /* done with /dev/tty */
return(buf);
}
Figure 18.18. Call the getpass function
#include "apue.h"
char *getpass(const char *);
int
main(void)
{
char *ptr;
if ((ptr = getpass("Enter password:")) == NULL)
err_sys("getpass error");
printf("password: %s\n", ptr);
/* now use password (probably encrypt it) ... */
while (*ptr != 0)
*ptr++ = 0; /* zero it out when we're done with it */
exit(0);
}
18.11. Noncanonical Mode
Noncanonical mode is specified by turning off the ICANON flag in the c_lflag field of the termios structure. In noncanonical mode, the input data is not assembled into lines. The following special characters (Section 18.3) are not processed: ERASE, KILL, EOF, NL, EOL, EOL2, CR, REPRINT, STATUS, and WERASE.
As we said, canonical mode is easy: the system returns up to one line at a time. But with noncanonical mode, how does the system know when to return data to us? If it returned one byte at a time, overhead would be excessive. (Recall Figure 3.5, which showed the overhead in reading one byte at a time. Each time we doubled the amount of data returned, we halved the system call overhead.) The system can't always return multiple bytes at a time, since sometimes we don't know how much data to read until we start reading it.
The solution is to tell the system to return when either a specified amount of data has been read or after a given amount of time has passed. This technique uses two variables in the c_cc array in the termios structure: MIN and TIME. These two elements of the array are indexed by the names VMIN and VTIME.
MIN specifies the minimum number of bytes before a read returns. TIME specifies the number of tenths of a second to wait for data to arrive. There are four cases.
Case A: MIN > 0, TIME > 0
TIME specifies an interbyte timer that is started only when the first byte is received. If MIN bytes are received before the timer expires, read returns MIN bytes. If the timer expires before MIN bytes are received, read returns the bytes received. (At least one byte is returned if the timer expires, because the timer is not started until the first byte is received.) In this case, the caller blocks until the first byte is received. If data is already available when read is called, it is as if the data had been received immediately after the read.
Case B: MIN > 0, TIME == 0
The read does not return until MIN bytes have been received. This can cause a read to block indefinitely.
Case C: MIN == 0, TIME > 0
TIME specifies a read timer that is started when read is called. (Compare this to case A, in which a nonzero TIME represented an interbyte timer that was not started until the first byte was received.) The read returns when a single byte is received or when the timer expires. If the timer expires, read returns 0.
Case D: MIN == 0, TIME == 0
If some data is available, read returns up to the number of bytes requested. If no data is available, read returns 0 immediately.
Realize in all these cases that MIN is only a minimum. If the program requests more than MIN bytes of data, it's possible to receive up to the requested amount. This also applies to cases C and D, in which MIN is 0.
Figure 18.19 summarizes the four cases for noncanonical input. In this figure, nbytes is the third argument to read (the maximum number of bytes to return).
Be aware that POSIX.1 allows the subscripts VMIN and VTIME to have the same values as VEOF and VEOL, respectively. Indeed, Solaris does this for backward compatibility with older versions of System V. This creates a portability problem, however. In going from noncanonical to canonical mode, we must now restore VEOF and VEOL also. If VMIN equals VEOF and we don't restore their values, when we set VMIN to its typical value of 1, the end-of-file character becomes Control-A. The easiest way around this problem is to save the entire termios structure when going into noncanonical mode and restore it when going back to canonical mode.
Example
The program in Figure 18.20 defines the tty_cbreak and tty_raw functions that set the terminal in cbreak mode and raw mode. (The terms cbreak and raw come from the Version 7 terminal driver.) We can reset the terminal to its original state (the state before either of these functions was called) by calling the function tty_reset.
If we've called tty_cbreak, we need to call tty_reset before calling tty_raw. The same goes for calling tty_cbreak after calling tty_raw. This improves the chances that the terminal will be left in a usable state if we encounter any errors.
Two additional functions are also provided: tty_atexit can be established as an exit handler to ensure that the terminal mode is reset by exit, and tty_termios returns a pointer to the original canonical mode termios structure.
Our definition of cbreak mode is the following:
Noncanonical mode. As we mentioned at the beginning of this section, this mode turns off some input character processing. It does not turn off signal handling, so the user can always type one of the terminal-generated signals. Be aware that the caller should catch these signals, or there's a chance that the signal will terminate the program, and the terminal will be left in cbreak mode. As a general rule, whenever we write a program that changes the terminal mode, we should catch most signals. This allows us to reset the terminal mode before terminating. Echo off. One byte at a time input. To do this, we set MIN to 1 and TIME to 0. This is case B from Figure 18.19. A read won't return until at least one byte is available.
We define raw mode as follows:
Noncanonical mode. We also turn off processing of the signal-generating characters (ISIG) and the extended input character processing (IEXTEN). Additionally, we disable a BREAK character from generating a signal, by turning off BRKINT. Echo off. We disable the CR-to-NL mapping on input (ICRNL), input parity detection (INPCK), the stripping of the eighth bit on input (ISTRIP), and output flow control (IXON). Eight-bit characters (CS8), and parity checking is disabled (PARENB). All output processing is disabled (OPOST). One byte at a time input (MIN = 1, TIME = 0).
The program in Figure 18.21 tests raw and cbreak modes.
Running the program in Figure 18.21, we can see what happens with these two terminal modes:
$ ./a.out
Enter raw mode characters, terminate with DELETE
4
33
133
61
70
176
type DELETE
Enter cbreak mode characters, terminate with SIGINT
1 type Control-A
10 type backspace
signal caught type interrupt key
In raw mode, the characters entered were Control-D (04) and the special function key F7. On the terminal being used, this function key generated five characters: ESC (033), [ (0133), 1 (061), 8 (070), and ~ (0176). Note that with the output processing turned off in raw mode (~OPOST), we do not get a carriage return output after each character. Also note that special-character processing is disabled in cbreak mode (so, for example, Control-D, the end-of-file character, and backspace aren't handled specially), whereas the terminal-generated signals are still processed.
Figure 18.20. Set terminal mode to cbreak or raw
#include "apue.h"
#include <termios.h>
#include <errno.h>
static struct termios save_termios;
static int ttysavefd = -1;
static enum { RESET, RAW, CBREAK } ttystate = RESET;
int
tty_cbreak(int fd) /* put terminal into a cbreak mode */
{
int err;
struct termios buf;
if (ttystate != RESET) {
errno = EINVAL;
return(-1);
}
if (tcgetattr(fd, &buf) < 0)
return(-1);
save_termios = buf; /* structure copy */
/*
* Echo off, canonical mode off.
*/
buf.c_lflag &= ~(ECHO | ICANON);
/*
* Case B: 1 byte at a time, no timer.
*/
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
return(-1);
/*
* Verify that the changes stuck. tcsetattr can return 0 on
* partial success.
*/
if (tcgetattr(fd, &buf) < 0) {
err = errno;
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = err;
return(-1);
}
if ((buf.c_lflag & (ECHO | ICANON)) || buf.c_cc[VMIN] != 1 ||
buf.c_cc[VTIME] != 0) {
/*
* Only some of the changes were made. Restore the
* original settings.
*/
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = EINVAL;
return(-1);
}
ttystate = CBREAK;
ttysavefd = fd;
return(0);
}
int
tty_raw(int fd) /* put terminal into a raw mode */
{
int err;
struct termios buf;
if (ttystate != RESET) {
errno = EINVAL;
return(-1);
}
if (tcgetattr(fd, &buf) < 0)
return(-1);
save_termios = buf; /* structure copy */
/*
* Echo off, canonical mode off, extended input
* processing off, signal chars off.
*/
buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
/*
* No SIGINT on BREAK, CR-to-NL off, input parity
* check off, don't strip 8th bit on input, output
* flow control off.
*/
buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
/*
* Clear size bits, parity checking off.
*/
buf.c_cflag &= ~(CSIZE | PARENB);
/*
* Set 8 bits/char.
*/
buf.c_cflag |= CS8;
/*
* Output processing off.
*/
buf.c_oflag &= ~(OPOST);
/*
* Case B: 1 byte at a time, no timer.
*/
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
return(-1);
/*
* Verify that the changes stuck. tcsetattr can return 0 on
* partial success.
*/
if (tcgetattr(fd, &buf) < 0) {
err = errno;
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = err;
return(-1);
}
if ((buf.c_lflag & (ECHO | ICANON | IEXTEN | ISIG)) ||
(buf.c_iflag & (BRKINT | ICRNL | INPCK | ISTRIP | IXON)) ||
(buf.c_cflag & (CSIZE | PARENB | CS8)) != CS8 ||
(buf.c_oflag & OPOST) || buf.c_cc[VMIN] != 1 ||
buf.c_cc[VTIME] != 0) {
/*
* Only some of the changes were made. Restore the
* original settings.
*/
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = EINVAL;
return(-1);
}
ttystate = RAW;
ttysavefd = fd;
return(0);
}
int
tty_reset(int fd) /* restore terminal's mode */
{
if (ttystate == RESET)
return(0);
if (tcsetattr(fd, TCSAFLUSH, &save_termios) < 0)
return(-1);
ttystate = RESET;
return(0);
}
void
tty_atexit(void) /* can be set up by atexit(tty_atexit) */
{
if (ttysavefd >= 0)
tty_reset(ttysavefd);
}
struct termios *
tty_termios(void) /* let caller see original tty state */
{
return(&save_termios);
}
Figure 18.21. Test raw and cbreak terminal modes
#include "apue.h"
static void
sig_catch(int signo)
{
printf("signal caught\n");
tty_reset(STDIN_FILENO);
exit(0);
}
int
main(void)
{
int i;
char c;
if (signal(SIGINT, sig_catch) == SIG_ERR) /* catch signals */
err_sys("signal(SIGINT) error");
if (signal(SIGQUIT, sig_catch) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
if (signal(SIGTERM, sig_catch) == SIG_ERR)
err_sys("signal(SIGTERM) error");
if (tty_raw(STDIN_FILENO) < 0)
err_sys("tty_raw error");
printf("Enter raw mode characters, terminate with DELETE\n");
while ((i = read(STDIN_FILENO, &c, 1)) == 1) {
if ((c &= 255) == 0177) /* 0177 = ASCII DELETE */
break;
printf("%o\n", c);
}
if (tty_reset(STDIN_FILENO) < 0)
err_sys("tty_reset error");
if (i <= 0)
err_sys("read error");
if (tty_cbreak(STDIN_FILENO) < 0)
err_sys("tty_cbreak error");
printf("\nEnter cbreak mode characters, terminate with SIGINT\n");
while ((i = read(STDIN_FILENO, &c, 1)) == 1) {
c &= 255;
printf("%o\n", c);
}
if (tty_reset(STDIN_FILENO) < 0)
err_sys("tty_reset error");
if (i <= 0)
err_sys("read error");
exit(0);
}
18.12. Terminal Window Size
Most UNIX systems provide a way to keep track of the current terminal window size and to have the kernel notify the foreground process group when the size changes. The kernel maintains a winsize structure for every terminal and pseudo terminal:
struct winsize {
unsigned short ws_row; /* rows, in characters */
unsigned short ws_col; /* columns, in characters */
unsigned short ws_xpixel; /* horizontal size, pixels (unused) */
unsigned short ws_ypixel; /* vertical size, pixels (unused) */
};
The rules for this structure are as follows.
We can fetch the current value of this structure using an ioctl (Section 3.15) of TIOCGWINSZ. We can store a new value of this structure in the kernel using an ioctl of TIOCSWINSZ. If this new value differs from the current value stored in the kernel, a SIGWINCH signal is sent to the foreground process group. (Note from Figure 10.1 that the default action for this signal is to be ignored.) Other than storing the current value of the structure and generating a signal when the value changes, the kernel does nothing else with this structure. Interpreting the structure is entirely up to the application.
The reason for providing this feature is to notify applications (such as the vi editor) when the window size changes. When it receives the signal, the application can fetch the new size and redraw the screen.
Example
Figure 18.22 shows a program that prints the current window size and goes to sleep. Each time the window size changes, SIGWINCH is caught and the new size is printed. We have to terminate this program with a signal.
Running the program in Figure 18.22 on a windowed terminal gives us
$ ./a.out
35 rows, 80 columns initial size
SIGWINCH received change window size: signal is caught
40 rows, 123 columns
SIGWINCH received and again
42 rows, 33 columns
^? $ type the interrupt key to terminate
Figure 18.22. Print window size
#include "apue.h"
#include <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif
static void
pr_winsize(int fd)
{
struct winsize size;
if (ioctl(fd, TIOCGWINSZ, (char *) &size) < 0)
err_sys("TIOCGWINSZ error");
printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
}
static void
sig_winch(int signo)
{
printf("SIGWINCH received\n");
pr_winsize(STDIN_FILENO);
}
int
main(void)
{
if (isatty(STDIN_FILENO) == 0)
exit(1);
if (signal(SIGWINCH, sig_winch) == SIG_ERR)
err_sys("signal error");
pr_winsize(STDIN_FILENO); /* print initial size */
for ( ; ; ) /* and sleep forever */
pause();
}
18.13. termcap, terminfo, and curses
termcap stands for "terminal capability," and it refers to the text file /etc/termcap and a set of routines to read this file. The termcap scheme was developed at Berkeley to support the vi editor. The termcap file contains descriptions of various terminals: what features the terminal supports (how many lines and rows, whether the terminal support backspace, etc.) and how to make the terminal perform certain operations (clear the screen, move the cursor to a given location, etc.). Taking this information out of the compiled program and placing it into a text file that can easily be edited allows the vi editor to run on many different terminals.
The routines that support the termcap file were then extracted from the vi editor and placed into a separate curses library. Many features were added to make this library usable for any program that wanted to manipulate the screen.
The termcap scheme was not perfect. As more and more terminals were added to the data file, it took longer to scan the file, looking for a specific terminal. The data file also used two-character names to identify the various terminal attributes. These deficiencies led to development of the terminfo scheme and its associated curses library. The terminal descriptions in terminfo are basically compiled versions of a textual description and can be located faster at runtime. terminfo appeared with SVR2 and has been in all System V releases since then.
Historically, System Vbased systems used terminfo, and BSD-derived systems used termcap, but it is now common for systems to provide both. Mac OS X, however, supports only terminfo.
A description of terminfo and the curses library is provided by Goodheart [1991], but this is currently out of print. Strang [1986] describes the Berkeley version of the curses library. Strang, Mui, and O'Reilly [1988] provide a description of termcap and terminfo.
The ncurses library, a free version that is compatible with the SVR4 curses interface, can be found at http://invisible-island.net/ncurses/ncurses.html.
Neither termcap nor terminfo, by itself, addresses the problems we've been looking at in this chapter: changing the terminal's mode, changing one of the terminal special characters, handling the window size, and so on. What they do provide is a way to perform typical operations (clear the screen, move the cursor) on a wide variety of terminals. On the other hand, curses does help with some of the details that we've addressed in this chapter. Functions are provided by curses to set raw mode, set cbreak mode, turn echo on and off, and the like. But the curses library is designed for character-based dumb terminals, which have mostly been replaced by pixel-based graphics terminals today.
19.1. Introduction
In Chapter 9, we saw that terminal logins come in through a terminal device, automatically providing terminal semantics. A terminal line discipline (Figure 18.2) exists between the terminal and the programs that we run, so we can set the terminal's special characters (backspace, line erase, interrupt, etc.) and the like. When a login arrives on a network connection, however, a terminal line discipline is not automatically provided between the incoming network connection and the login shell. Figure 9.5 showed that a pseudo-terminal device driver is used to provide terminal semantics.
In addition to network logins, pseudo terminals have other uses that we explore in this chapter. We start with an overview on how to use pseudo terminals, followed by a discussion of specific use cases. We then provide functions to create pseudo terminals on various platforms and then use these functions to write a program that we call pty. We'll show various uses of this program: making a transcript of all the character input and output on the terminal (the script(1) program) and running coprocesses to avoid the buffering problems we encountered in the program from Figure 15.19.
19.2. Overview
The term pseudo terminal implies that it looks like a terminal to an application program, but it's not a real terminal. Figure 19.1 shows the typical arrangement of the processes involved when a pseudo terminal is being used. The key points in this figure are the following.
Normally, a process opens the pseudo-terminal master and then calls fork. The child establishes a new session, opens the corresponding pseudo-terminal slave, duplicates the file descriptor to the standard input, standard output, and standard error, and then calls exec. The pseudo-terminal slave becomes the controlling terminal for the child process. It appears to the user process above the slave that its standard input, standard output, and standard error are a terminal device. The process can issue all the terminal I/O functions from Chapter 18 on these descriptors. But since there is not a real terminal device beneath the slave, functions that don't make sense (change the baud rate, send a break character, set odd parity, etc.) are just ignored. Anything written to the master appears as input to the slave and vice versa. Indeed, all the input to the slave comes from the user process above the pseudo-terminal master. This behaves like a bidirectional pipe, but with the terminal line discipline module above the slave, we have additional capabilities over a plain pipe.
Figure 19.1 shows what a pseudo terminal looks like on a FreeBSD, Mac OS X, or Linux system. In Sections 19.3.2 and 19.3.3, we show how to open these devices.
Under Solaris, a pseudo terminal is built using the STREAMS subsystem (Section 14.4). Figure 19.2 details the arrangement of the pseudo-terminal STREAMS modules under Solaris. The two STREAMS modules that are shown as dashed boxes are optional. The pckt and ptem modules help provide semantics specific to pseudo terminals. The other two modules (ldterm and ttcompat) provide line discipline processing.
Note that the three STREAMS modules above the slave are the same as the output from the program shown in Figure 14.18 for a network login. In Section 19.3.1, we show how to build this arrangement of STREAMS modules.
From this point on, we'll simplify the figures by not showing the "read and write functions" from Figure 19.1 or the "stream head" from Figure 19.2. We'll also use the abbreviation PTY for pseudo terminal and lump all the STREAMS modules above the slave PTY in Figure 19.2 into a box called "terminal line discipline," as in Figure 19.1.
We'll now examine some of the typical uses of pseudo terminals.
Network Login Servers
Pseudo terminals are built into servers that provide network logins. The typical examples are the telnetd and rlogind servers. Chapter 15 of Stevens [1990] details the steps involved in the rlogin service. Once the login shell is running on the remote host, we have the arrangement shown in Figure 19.3. A similar arrangement is used by the telnetd server.
We show two calls to exec between the rlogind server and the login shell, because the login program is usually between the two to validate the user.
A key point in this figure is that the process driving the PTY master is normally reading and writing another I/O stream at the same time. In this example, the other I/O stream is the TCP/IP box. This implies that the process must be using some form of I/O multiplexing (Section 14.5), such as select or poll, or must be divided into two processes or threads.
script Program
The script(1) program that is supplied with most UNIX systems makes a copy in a file of everything that is input and output during a terminal session. The program does this by placing itself between the terminal and a new invocation of our login shell. Figure 19.4 details the interactions involved in the script program. Here, we specifically show that the script program is normally run from a login shell, which then waits for script to terminate.
While script is running, everything output by the terminal line discipline above the PTY slave is copied to the script file (usually called typescript). Since our keystrokes are normally echoed by that line discipline module, the script file also contains our input. The script file won't contain any passwords that we enter, however, since passwords aren't echoed.
While writing the first edition of this book, Rich Stevens used the script program to capture the output of the example programs. This avoided typographical errors that could have occurred if he had copied the program output by hand. The drawback to using script, however, is having to deal with control characters that are present in the script file.
After developing the general pty program in Section 19.5, we'll see that a trivial shell script turns it into a version of the script program.
expect Program
Pseudo terminals can be used to drive interactive programs in noninteractive modes. Numerous programs are hardwired to require a terminal to run. One example is the passwd(1) command, which requires that the user enter a password in response to a prompt.
Rather than modify all the interactive programs to support a batch mode of operation, a better solution is to provide a way to drive any interactive program from a script. The expect program [Libes 1990, 1991, 1994] provides a way to do this. It uses pseudo terminals to run other programs, similar to the pty program in Section 19.5. But expect also provides a programming language to examine the output of the program being run to make decisions about what to send the program as input. When an interactive program is being run from a script, we can't just copy everything from the script to the program and vice versa. Instead, we have to send the program some input, look at its output, and decide what to send it next.
Running Coprocesses
In the coprocess example in Figure 15.19, we couldn't invoke a coprocess that used the standard I/O library for its input and output, because when we talked to the coprocess across a pipe, the standard I/O library fully buffered the standard input and standard output, leading to a deadlock. If the coprocess is a compiled program for which we don't have the source code, we can't add fflush statements to solve this problem. Figure 15.16 showed a process driving a coprocess. What we need to do is place a pseudo terminal between the two processes, as shown in Figure 19.5, to trick the coprocess into thinking that it is being driven from a terminal instead of from another process.
Now the standard input and standard output of the coprocess look like a terminal device, so the standard I/O library will set these two streams to be line buffered.
The parent can obtain a pseudo terminal between itself and the coprocess in two ways. (The parent in this case could be either the program in Figure 15.18, which used two pipes to communicate with the coprocess, or the program in Figure 17.4, which used a single STREAMS pipe.) One way is for the parent to call the pty_fork function directly (Section 19.4) instead of calling fork. Another is to exec the pty program (Section 19.5) with the coprocess as its argument. We'll look at these two solutions after showing the pty program.
Watching the Output of Long-Running Programs
If we have a program that runs for a long time, we can easily run it in the background using any of the standard shells. But if we redirect its standard output to a file, and if it doesn't generate much output, we can't easily monitor its progress, because the standard I/O library will fully buffer its standard output. All that we'll see are blocks of output written by the standard I/O library to the output file, possibly in chunks as large as 8,192 bytes.
If we have the source code, we can insert calls to fflush. Alternatively, we can run the program under the pty program, making its standard I/O library think that its standard output is a terminal. Figure 19.6 shows this arrangement, where we have called the slow output program slowout. The fork/exec arrow from the login shell to the pty process is shown as a dashed arrow to reiterate that the pty process is running as a background job.
19.3. Opening Pseudo-Terminal Devices
The way we open a pseudo-terminal device differs among platforms. The Single UNIX Specification includes several functions as XSI extensions in an attempt to unify the methods. These extensions are based on the functions originally provided to manage STREAMS-based pseudo terminals in System V Release 4.
The posix_openpt function is provided as a portable way to open an available pseudo-terminal master device.
#include <stdlib.h>
#include <fcntl.h>
int posix_openpt(int oflag);
| Returns: file descriptor of next available PTY master if OK, 1 on error |
The oflag argument is a bitmask that specifies how the master device is to be opened, similar to the same argument used with open(2). Not all open flags are supported, however. With posix_openpt, we can specify O_RDWR to open the master device for reading and writing, and we can specify O_NOCTTY to prevent the master device from becoming a controlling terminal for the caller. All other open flags result in unspecified behavior.
Before a slave pseudo-terminal device can be used, its permissions need to be set so that it is accessible to applications. The grantpt function does just this. It sets the user ID of the slave's device node to be the caller's real user ID and sets the node's group ID to an unspecified value, usually some group that has access to terminal devices. The permissions are set to allow read and write access to individual owners and write access to group owners (0620).
#include <stdlib.h>
int grantpt(int filedes);
int unlockpt(int filedes);
| Both return: 0 on success, 1 on error |
To change permission on the slave device node, grantpt might need to fork and exec a set-user-ID program (/usr/lib/pt_chmod on Solaris, for example). Thus, the behavior is unspecified if the caller is catching SIGCHLD.
The unlockpt function is used to grant access to the slave pseudo-terminal device, thereby allowing applications to open the device. By preventing others from opening the slave device, applications setting up the devices have an opportunity to initialize the slave and master devices properly before they can be used.
Note that in both grantpt and unlockpt, the file descriptor argument is the file descriptor associated with the master pseudo-terminal device.
The ptsname function is used to find the pathname of the slave pseudo-terminal device, given the file descriptor of the master. This allows applications to identify the slave independent of any particular conventions that might be followed by a given platform. Note that the name returned might be stored in static memory, so it can be overwritten on successive calls.
#include <stdlib.h>
char *ptsname(int filedes);
| Returns: pointer to name of PTY slave if OK, NULL on error |
Figure 19.7 summarizes the pseudo-terminal functions in the Single UNIX Specification and indicates which functions are supported by the platforms discussed in this text.
Figure 19.7. XSI pseudo-terminal functionsFunction | Description | XSI | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
grantpt | Change permissions of slave PTY device. | • | • | • | | • | posix_openpt | Open a master PTY device. | • | • | | | | ptsname | Return name of slave PTY device. | • | • | • | | • | unlockpt | Allow slave PTY device to be opened. | • | • | • | | • |
On FreeBSD, unlockpt does nothing; the O_NOCTTY flag is defined only for compatibility with applications that call posix_openpt. FreeBSD does not allocate a controlling terminal as a side effect of opening a terminal device, so the O_NOCTTY flag has no effect.
Even though the Single UNIX Specification has tried to improve portability in this area, implementations are still catching up, as illustrated by Figure 19.7. Thus, we provide two functions that handle all the details: ptym_open to open the next available PTY master device and ptys_open to open the corresponding slave device.
#include "apue.h"
int ptym_open(char *pts_name, int pts_namesz);
| Returns: file descriptor of PTY master if OK, 1 on error
|
int ptys_open(char *pts_name);
| Returns: file descriptor of PTY slave if OK, 1 on error |
Normally, we don't call these two functions directly; the function pty_fork (Section 19.4) calls them and also forks a child process.
The ptym_open function determines the next available PTY master and opens the device. The caller must allocate an array to hold the name of either the master or the slave; if the call succeeds, the name of the corresponding slave is returned through pts_name. This name is then passed to ptys_open, which opens the slave device. The length of the buffer in bytes is passed in pts_namesz so that the ptym_open function doesn't copy a string that is longer than the buffer.
The reason for providing two functions to open the two devices will become obvious when we show the pty_fork function. Normally, a process calls ptym_open to open the master and obtain the name of the slave. The process then forks, and the child calls ptys_open to open the slave after calling setsid to establish a new session. This is how the slave becomes the controlling terminal for the child.
19.3.1. STREAMS-Based Pseudo Terminals
The details of the STREAMS implementation of pseudo terminals under Solaris are covered in Appendix C of Sun Microsystems [2002]. The next available PTY master device is accessed through a STREAMS clone device. A clone device is a special device that returns an unused device when it is opened. (STREAMS clone opens are discussed in detail in Rago [1993].)
The STREAMS-based PTY master clone device is /dev/ptmx. When we open it, the clone open routine automatically determines the first unused PTY master device and opens that unused device. (We'll see in the next section that, under BSD-based systems, we have to find the first unused PTY master ourselves.)
Figure 19.8. STREAMS-based pseudo-terminal open functions
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <stropts.h>
int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where strlen("/dev/ptmx") > pts_namesz.
*/
strncpy(pts_name, "/dev/ptmx", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
if ((fdm = open(pts_name, O_RDWR)) < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle
* case where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int
ptys_open(char *pts_name)
{
int fds, setup;
/*
* The following open should allocate a controlling terminal.
*/
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
/*
* Check if stream is already set up by autopush facility.
*/
if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0) {
close(fds);
return(-6);
}
if (setup == 0) {
if (ioctl(fds, I_PUSH, "ptem") < 0) {
close(fds);
return(-7);
}
if (ioctl(fds, I_PUSH, "ldterm") < 0) {
close(fds);
return(-8);
}
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
close(fds);
return(-9);
}
}
return(fds);
}
We first open the clone device /dev/ptmx to get a file descriptor for the PTY master. Opening this master device automatically locks out the corresponding slave device.
We then call grantpt to change permissions of the slave device. On Solaris, it changes the ownership of the slave to the real user ID, changes the group ownership to the group tty, and changes the permissions to allow only user-read, user-write, and group-write. The reason for setting the group ownership to tty and enabling group-write permission is that the programs wall(1) and write(1) are set-group-ID to the group tty. Calling grantpt executes the program /usr/lib/pt_chmod, which is set-user-ID to root so that it can modify the ownership and permissions of the slave.
The function unlockpt is called to clear an internal lock on the slave device. We have to do this before we can open the slave. Additionally, we must call ptsname to obtain the name of the slave device. This name is of the form /dev/pts/NNN.
The next function in the file is ptys_open, which does the actual open of the slave device. Solaris follows the historical System V behavior: if the caller is a session leader that does not already have a controlling terminal, this call to open allocates the PTY slave as the controlling terminal. If we didn't want this to happen, we could specify the O_NOCTTY flag for open.
After opening the slave device, we might need to push three STREAMS modules onto the slave's stream. Together, the pseudo terminal emulation module (ptem) and the terminal line discipline module (ldterm) act like a real terminal. The ttcompat module provides compatibility for older V7, 4BSD, and Xenix ioctl calls. It's an optional module, but since it's automatically pushed for console logins and network logins (see the output from the program shown in Figure 14.18), we push it onto the slave's stream.
The reason that we might not need to push these three modules is that they might be there already. The STREAMS system supports a facility known as autopush, which allows an administrator to configure a list of modules to be pushed onto a stream whenever a particular device is opened (see Rago [1993] for more details). We use the I_FIND ioctl command to see whether ldterm is already on the stream. If so, we assume that the stream has been configured by the autopush mechanism and avoid pushing the modules a second time.
The result of calling ptym_open and ptys_open is two file descriptors open in the calling process: one for the master and one for the slave.
19.3.2. BSD-Based Pseudo Terminals
Under BSD-based systems and Linux-based systems, we provide our own versions of the XSI functions, which we can optionally include in our library, depending on which functions (if any) are provided by the underlying platform.
In our version of posix_openpt, we have to determine the first available PTY master device. To do this, we start at /dev/ptyp0 and keep trying until we successfully open a PTY master or until we run out of devices. We can get two different errors from open: EIO means that the device is already in use; ENOENT means that the device doesn't exist. In the latter case, we can terminate the search, as all pseudo terminals are in use. Once we are able to open a PTY master, say /dev/ptyMN, the name of the corresponding slave is /dev/ttyMN. On Linux, if the name of the PTY master is /dev/pty/mXX, then the name of the corresponding PTY slave is /dev/pty/sXX.
In our version of grantpt, we call chown and chmod but realize that these two functions won't work unless the calling process has superuser permissions. If it is important that the ownership and protection be changed, these two function calls need to be placed into a set-user-ID root executable, similar to the way Solaris implements it.
The function ptys_open in Figure 19.9 simply opens the slave device. No other initialization is necessary. The open of the slave PTY under BSD-based systems does not have the side effect of allocating the device as the controlling terminal. In Section 19.4, we'll see how to allocate the controlling terminal under BSD-based systems.
Figure 19.9. Pseudo-terminal open functions for BSD and Linux
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#ifndef _HAS_OPENPT
int
posix_openpt(int oflag)
{
int fdm;
char *ptr1, *ptr2;
char ptm_name[16];
strcpy(ptm_name, "/dev/ptyXY");
/* array index: 0123456789 (for references in following code) */
for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1 != 0; ptr1++) {
ptm_name[8] = *ptr1;
for (ptr2 = "0123456789abcdef"; *ptr2 != 0; ptr2++) {
ptm_name[9] = *ptr2;
/*
* Try to open the master.
*/
if ((fdm = open(ptm_name, oflag)) < 0) {
if (errno == ENOENT) /* different from EIO */
return(-1); /* out of pty devices */
else
continue; /* try next pty device */
}
return(fdm); /* got it, return fd of master */
}
}
errno = EAGAIN;
return(-1); /* out of pty devices */
}
#endif
#ifndef _HAS_PTSNAME
char *
ptsname(int fdm)
{
static char pts_name[16];
char *ptm_name;
ptm_name = ttyname(fdm);
if (ptm_name == NULL)
return(NULL);
strncpy(pts_name, ptm_name, sizeof(pts_name));
pts_name[sizeof(pts_name) - 1] = '\0';
if (strncmp(pts_name, "/dev/pty/", 9) == 0)
pts_name[9] = 's'; /* change /dev/pty/mXX to /dev/pty/sXX */
else
pts_name[5] = 't'; /* change "pty" to "tty" */
return(pts_name);
}
#endif
#ifndef _HAS_GRANTPT
int
grantpt(int fdm)
{
struct group *grptr;
int gid;
char *pts_name;
pts_name = ptsname(fdm);
if ((grptr = getgrnam("tty")) != NULL)
gid = grptr->gr_gid;
else
gid = -1; /* group tty is not in the group file */
/*
* The following two calls won't work unless we're the superuser.
*/
if (chown(pts_name, getuid(), gid) < 0)
return(-1);
return(chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP));
}
#endif
#ifndef _HAS_UNLOCKPT
int
unlockpt(int fdm)
{
return(0); /* nothing to do */
}
#endif
int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where string length > pts_namesz.
*/
strncpy(pts_name, "/dev/ptyXX", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
if ((fdm = posix_openpt(O_RDWR)) < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle
* case where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int
ptys_open(char *pts_name)
{
int fds;
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
return(fds);
}
Our version of posix_openpt tries 16 different groups of 16 PTY master devices: /dev/ptyp0 tHRough /dev/ptyTf. The actual number of PTY devices available depends on two factors: (a) the number configured into the kernel, and (b) the number of special device files that have been created in the /dev directory. The number available to any program is the lesser of (a) or (b).
19.3.3. Linux-Based Pseudo Terminals
Linux supports the BSD method for accessing pseudo terminals, so the same functions shown in Figure 19.9 will also work on Linux. However, Linux also supports a clone-style interface to pseudo terminals using /dev/ptmx (but this is not a STREAMS device). The clone interface requires extra steps to identify and unlock a slave device. The functions we can use to access these pseudo terminals on Linux are shown in Figure 19.10.
Figure 19.10. Pseudo-terminal open functions for Linux
#include "apue.h"
#include <fcntl.h>
#ifndef _HAS_OPENPT
int
posix_openpt(int oflag)
{
int fdm;
fdm = open("/dev/ptmx", oflag);
return(fdm);
}
#endif
#ifndef _HAS_PTSNAME
char *
ptsname(int fdm)
{
int sminor;
static char pts_name[16];
if (ioctl(fdm, TIOCGPTN, &sminor) < 0)
return(NULL);
snprintf(pts_name, sizeof(pts_name), "/dev/pts/%d", sminor);
return(pts_name);
}
#endif
#ifndef _HAS_GRANTPT
int
grantpt(int fdm)
{
char *pts_name;
pts_name = ptsname(fdm);
return(chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP));
}
#endif
#ifndef _HAS_UNLOCKPT
int
unlockpt(int fdm)
{
int lock = 0;
return(ioctl(fdm, TIOCSPTLCK, &lock));
}
#endif
int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where string length > pts_namesz.
*/
strncpy(pts_name, "/dev/ptmx", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
fdm = posix_openpt(O_RDWR);
if (fdm < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle case
* where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int
ptys_open(char *pts_name)
{
int fds;
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
return(fds);
}
On Linux, the PTY slave device is already owned by group tty, so all we need to do in grantpt is ensure that the permissions are correct.
19.4. pty_fork Function
We now use the two functions from the previous section, ptym_open and ptys_open, to write a new function that we call pty_fork. This new function combines the opening of the master and the slave with a call to fork, establishing the child as a session leader with a controlling terminal.
#include "apue.h"
#include <termios.h>
#include <sys/ioctl.h> /* find struct winsize on
BSD systems */
pid_t pty_fork(int *ptrfdm, char *slave_name, int
slave_namesz,
const struct termios *slave_termios,
const struct winsize *slave_winsize);
| Returns: 0 in child, process ID of child in parent, 1 on error |
The file descriptor of the PTY master is returned through the ptrfdm pointer.
If slave_name is non-null, the name of the slave device is stored at that location. The caller has to allocate the storage pointed to by this argument.
If the pointer slave_termios is non-null, the system uses the referenced structure to initialize the terminal line discipline of the slave. If this pointer is null, the system sets the slave's termios structure to an implementation-defined initial state. Similarly, if the slave_winsize pointer is non-null, the referenced structure initializes the slave's window size. If this pointer is null, the winsize structure is normally initialized to 0.
Figure 19.11 shows the code for this function. It works on all four platforms described in this text, calling the appropriate ptym_open and ptys_open functions.
Figure 19.11. The pty_fork function
#include "apue.h"
#include <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif
pid_t
pty_fork(int *ptrfdm, char *slave_name, int slave_namesz,
const struct termios *slave_termios,
const struct winsize *slave_winsize)
{
int fdm, fds;
pid_t pid;
char pts_name[20];
if ((fdm = ptym_open(pts_name, sizeof(pts_name))) < 0)
err_sys("can't open master pty: %s, error %d", pts_name, fdm);
if (slave_name != NULL) {
/*
* Return name of slave. Null terminate to handle case
* where strlen(pts_name) > slave_namesz.
*/
strncpy(slave_name, pts_name, slave_namesz);
slave_name[slave_namesz - 1] = '\0';
}
if ((pid = fork()) < 0) {
return(-1);
} else if (pid == 0) { /* child */
if (setsid() < 0)
err_sys("setsid error");
/*
* System V acquires controlling terminal on open().
*/
if ((fds = ptys_open(pts_name)) < 0)
err_sys("can't open slave pty");
close(fdm); /* all done with master in child */
#if defined(TIOCSCTTY)
/*
* TIOCSCTTY is the BSD way to acquire a controlling terminal.
*/
if (ioctl(fds, TIOCSCTTY, (char *)0) < 0)
err_sys("TIOCSCTTY error");
#endif
/*
* Set slave's termios and window size.
*/
if (slave_termios != NULL) {
if (tcsetattr(fds, TCSANOW, slave_termios) < 0)
err_sys("tcsetattr error on slave pty");
}
if (slave_winsize != NULL) {
if (ioctl(fds, TIOCSWINSZ, slave_winsize) < 0)
err_sys("TIOCSWINSZ error on slave pty");
}
/*
* Slave becomes stdin/stdout/stderr of child.
*/
if (dup2(fds, STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
if (dup2(fds, STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
if (dup2(fds, STDERR_FILENO) != STDERR_FILENO)
err_sys("dup2 error to stderr");
if (fds != STDIN_FILENO && fds != STDOUT_FILENO &&
fds != STDERR_FILENO)
close(fds);
return(0); /* child returns 0 just like fork() */
} else { /* parent */
*ptrfdm = fdm; /* return fd of master */
return(pid); /* parent returns pid of child */
}
}
After opening the PTY master, fork is called. As we mentioned before, we want to wait to call ptys_open until in the child and after calling setsid to establish a new session. When it calls setsid, the child is not a process group leader, so the three steps listed in Section 9.5 occur: (a) a new session is created with the child as the session leader, (b) a new process group is created for the child, and (c) the child loses any association it might have had with its previous controlling terminal. Under Linux and Solaris, the slave becomes the controlling terminal of this new session when ptys_open is called. Under FreeBSD and Mac OS X, we have to call ioctl with an argument of TIOCSCTTY to allocate the controlling terminal. (Linux also supports the TIOCSCTTY ioctl command.) The two structures termios and winsize are then initialized in the child. Finally, the slave file descriptor is duplicated onto standard input, standard output, and standard error in the child. This means that whatever process the caller execs from the child will have these three descriptors connected to the slave PTY (its controlling terminal).
After the call to fork, the parent just returns the PTY master descriptor and the process ID of the child. In the next section, we use the pty_fork function in the pty program.
19.5. pty Program
The goal in writing the pty program is to be able to type
pty prog arg1 arg2
instead of
prog arg1 arg2
When we use pty to execute another program, that program is executed in a session of its own, connected to a pseudo terminal.
Let's look at the source code for the pty program. The first file (Figure 19.12) contains the main function. It calls the pty_fork function from the previous section.
Figure 19.12. The main function for the pty program
#include "apue.h"
#include <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h> /* for struct winsize */
#endif
#ifdef LINUX
#define OPTSTR "+d:einv"
#else
#define OPTSTR "d:einv"
#endif
static void set_noecho(int); /* at the end of this file */
void do_driver(char *); /* in the file driver.c */
void loop(int, int); /* in the file loop.c */
int
main(int argc, char *argv[])
{
int fdm, c, ignoreeof, interactive, noecho, verbose;
pid_t pid;
char *driver;
char slave_name[20];
struct termios orig_termios;
struct winsize size;
interactive = isatty(STDIN_FILENO);
ignoreeof = 0;
noecho = 0;
verbose = 0;
driver = NULL;
opterr = 0; /* don't want getopt() writing to stderr */
while ((c = getopt(argc, argv, OPTSTR)) != EOF) {
switch (c) {
case 'd': /* driver for stdin/stdout */
driver = optarg;
break;
case 'e': /* noecho for slave pty's line discipline */
noecho = 1;
break;
case 'i': /* ignore EOF on standard input */
ignoreeof = 1;
break;
case 'n': /* not interactive */
interactive = 0;
break;
case 'v': /* verbose */
verbose = 1;
break;
case '?':
err_quit("unrecognized option: -%c", optopt);
}
}
if (optind >= argc)
err_quit("usage: pty [ -d driver -einv ] program [ arg ... ]");
if (interactive) { /* fetch current termios and window size */
if (tcgetattr(STDIN_FILENO, &orig_termios) < 0)
err_sys("tcgetattr error on stdin");
if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0)
err_sys("TIOCGWINSZ error");
pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
&orig_termios, &size);
} else {
pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
NULL, NULL);
}
if (pid < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
if (noecho)
set_noecho(STDIN_FILENO); /* stdin is slave pty */
if (execvp(argv[optind], &argv[optind]) < 0)
err_sys("can't execute: %s", argv[optind]);
}
if (verbose) {
fprintf(stderr, "slave name = %s\n", slave_name);
if (driver != NULL)
fprintf(stderr, "driver = %s\n", driver);
}
if (interactive && driver == NULL) {
if (tty_raw(STDIN_FILENO) < 0) /* user's tty to raw mode */
err_sys("tty_raw error");
if (atexit(tty_atexit) < 0) /* reset user's tty on exit */
err_sys("atexit error");
}
if (driver)
do_driver(driver); /* changes our stdin/stdout */
loop(fdm, ignoreeof); /* copies stdin -> ptym, ptym -> stdout */
exit(0);
}
static void
set_noecho(int fd) /* turn off echo (for slave pty) */
{
struct termios stermios;
if (tcgetattr(fd, &stermios) < 0)
err_sys("tcgetattr error");
stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
/*
* Also turn off NL to CR/NL mapping on output.
*/
stermios.c_oflag &= ~(ONLCR);
if (tcsetattr(fd, TCSANOW, &stermios) < 0)
err_sys("tcsetattr error");
}
In the next section, we'll look at the various command-line options when we examine different uses of the pty program. The getopt function helps us parse command-line arguments in a consistent manner. We'll discuss getopt in more detail in Chapter 21.
Before calling pty_fork, we fetch the current values for the termios and winsize structures, passing these as arguments to pty_fork. This way, the PTY slave assumes the same initial state as the current terminal.
After returning from pty_fork, the child optionally turns off echoing for the slave PTY and then calls execvp to execute the program specified on the command line. All remaining command-line arguments are passed as arguments to this program.
The parent optionally sets the user's terminal to raw mode. In this case, the parent also sets an exit handler to reset the terminal state when exit is called. We describe the do_driver function in the next section.
The parent then calls the function loop (Figure 19.13), which copies everything received from the standard input to the PTY master and everything from the PTY master to standard output. For variety, we have coded it in two processes this time, although a single process using select, poll, or multiple threads would also work.
Figure 19.13. The loop function
#include "apue.h"
#define BUFFSIZE 512
static void sig_term(int);
static volatile sig_atomic_t sigcaught; /* set by signal handler */
void
loop(int ptym, int ignoreeof)
{
pid_t child;
int nread;
char buf[BUFFSIZE];
if ((child = fork()) < 0) {
err_sys("fork error");
} else if (child == 0) { /* child copies stdin to ptym */
for ( ; ; ) {
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) < 0)
err_sys("read error from stdin");
else if (nread == 0)
break; /* EOF on stdin means we're done */
if (writen(ptym, buf, nread) != nread)
err_sys("writen error to master pty");
}
/*
* We always terminate when we encounter an EOF on stdin,
* but we notify the parent only if ignoreeof is 0.
*/
if (ignoreeof == 0)
kill(getppid(), SIGTERM); /* notify parent */
exit(0); /* and terminate; child can't return */
}
/*
* Parent copies ptym to stdout.
*/
if (signal_intr(SIGTERM, sig_term) == SIG_ERR)
err_sys("signal_intr error for SIGTERM");
for ( ; ; ) {
if ((nread = read(ptym, buf, BUFFSIZE)) <= 0)
break; /* signal caught, error, or EOF */
if (writen(STDOUT_FILENO, buf, nread) != nread)
err_sys("writen error to stdout");
}
/*
* There are three ways to get here: sig_term() below caught the
* SIGTERM from the child, we read an EOF on the pty master (which
* means we have to signal the child to stop), or an error.
*/
if (sigcaught == 0) /* tell child if it didn't send us the signal */
kill(child, SIGTERM);
/*
* Parent returns to caller.
*/
}
/*
* The child sends us SIGTERM when it gets EOF on the pty slave or
* when read() fails. We probably interrupted the read() of ptym.
*/
static void
sig_term(int signo)
{
sigcaught = 1; /* just set flag and return */
}
Note that, with two processes, when one terminates, it has to notify the other. We use the SIGTERM signal for this notification.
19.6. Using the pty Program
We'll now look at various examples with the pty program, seeing the need for the command-line options.
If our shell is the Korn shell, we can execute
pty ksh
and get a brand new invocation of the shell, running under a pseudo terminal.
If the file ttyname is the program we showed in Figure 18.16, we can run the pty program as follows:
$ who
sar :0 Oct 5 18:07
sar pts/0 Oct 5 18:07
sar pts/1 Oct 5 18:07
sar pts/2 Oct 5 18:07
sar pts/3 Oct 5 18:07
sar pts/4 Oct 5 18:07 pts/4 is the highest PTY currently in use
$ pty ttyname run program in Figure 18.16 from PTY
fd 0: /dev/pts/5 pts/5 is the next available PTY
fd 1: /dev/pts/5
fd 2: /dev/pts/5
utmp File
In Section 6.8, we described the utmp file that records all users currently logged in to a UNIX system. The question is whether a user running a program on a pseudo terminal is considered logged in. In the case of remote logins, telnetd and rlogind, obviously an entry should be made in the utmp file for the user logged in on the pseudo terminal. There is little agreement, however, whether users running a shell on a pseudo terminal from a window system or from a program, such as script, should have entries made in the utmp file. Some systems record these and some don't. If a system doesn't record these in the utmp file, the who(1) program normally won't show the corresponding pseudo terminals as being used.
Unless the utmp file has other-write permission enabled (which is considered to be a security hole), random programs that use pseudo terminals won't be able to write to this file.
Job Control Interaction
If we run a job-control shell under pty, it works normally. For example,
pty ksh
runs the Korn shell under pty. We can run programs under this new shell and use job control just as we do with our login shell. But if we run an interactive program other than a job-control shell under pty, asin
pty cat
everything is fine until we type the job-control suspend character. At that point, the job-control character is echoed as ^Z and is ignored. Under earlier BSD-based systems, the cat process terminates, the pty process terminates, and we're back to our original shell. To understand what's going on here, we need to examine all the processes involved, their process groups, and sessions. Figure 19.14 shows the arrangement when pty cat is running.
When we type the suspend character (Control-Z), it is recognized by the line discipline module beneath the cat process, since pty puts the terminal (beneath the pty parent) into raw mode. But the kernel won't stop the cat process, because it belongs to an orphaned process group (Section 9.10). The parent of cat is the pty parent, and it belongs to another session.
Historically, implementations have handled this condition differently. POSIX.1 says only that the SIGTSTP signal can't be delivered to the process. Systems derived from 4.3BSD delivered SIGKILL instead, which the process can't even catch. In 4.4BSD, this behavior was changed to conform to POSIX.1. Instead of sending SIGKILL, the 4.4BSD kernel silently discards the SIGTSTP signal if it has the default disposition and is to be delivered to a process in an orphaned process group. Most current implementations follow this behavior.
When we use pty to run a job-control shell, the jobs invoked by this new shell are never members of an orphaned process group, because the job-control shell always belongs to the same session. In that case, the Control-Z that we type is sent to the process invoked by the shell, not to the shell itself.
The only way to avoid this inability of the process invoked by pty to handle job-control signals is to add yet another command-line flag to pty, telling it to recognize the job control suspend character itself (in the pty child) instead of letting the character get all the way through to the other line discipline.
Watching the Output of Long-Running Programs
Another example of job-control interaction with the pty program is with the example in Figure 19.6. If we run the program that generates output slowly as
pty slowout > file.out &
the pty process is stopped immediately when the child tries to read from its standard input (the terminal). The reason is that the job is a background job and gets job-control stopped when it tries to access the terminal. If we redirect standard input so that pty doesn't try to read from the terminal, as in
pty slowout < /dev/null > file.out &
the pty program stops immediately because it reads an end of file on its standard input and terminates. The solution for this problem is the -i option, which says to ignore an end of file on the standard input:
pty -i slowout < /dev/null > file.out &
This flag causes the pty child in Figure 19.13 to exit when the end of file is encountered, but the child doesn't tell the parent to terminate. Instead, the parent continues copying the PTY slave output to standard output (the file file.out in the example).
script Program
Using the pty program, we can implement the script(1) program as the following shell script:
#!/bin/sh
pty "${SHELL:-/bin/sh}" | tee typescript
Once we run this shell script, we can execute the ps command to see all the process relationships. Figure 19.15 details these relationships.
In this example, we assume that the SHELL variable is the Korn shell (probably /bin/ksh). As we mentioned earlier, script copies only what is output by the new shell (and any processes that it invokes), but since the line discipline module above the PTY slave normally has echo enabled, most of what we type also gets written to the typescript file.
Running Coprocesses
In Figure 15.8, the coprocess couldn't use the standard I/O functions, because standard input and standard output do not refer to a terminal, so the standard I/O functions treat them as fully buffered. If we run the coprocess under pty by replacing the line
if (execl("./add2", "add2", (char *)0) < 0)
with
if (execl("./pty", "pty", "-e", "add2", (char *)0) < 0)
the program now works, even if the coprocess uses standard I/O.
Figure 19.16 shows the arrangement of processes when we run the coprocess with a pseudo terminal as its input and output. It is an expansion of Figure 19.5, showing all the process connections and data flow. The box labeled "driving program" is the program from Figure 15.8, with the execl changed as described previously.
This example shows the need for the -e (no echo) option for the pty program. The pty program is not running interactively, because its standard input is not connected to a terminal. In Figure 19.12, the interactive flag defaults to false, since the call to isatty returns false. This means that the line discipline above the actual terminal remains in a canonical mode with echo enabled. By specifying the -e option, we turn off echo in the line discipline module above the PTY slave. If we don't do this, everything we type is echoed twiceby both line discipline modules.
We also have the -e option turn off the ONLCR flag in the termios structure to prevent all the output from the coprocess from being terminated with a carriage return and a newline.
Testing this example on different systems showed another problem that we alluded to in Section 14.8 when we described the readn and writen functions. The amount of data returned by a read, when the descriptor refers to something other than an ordinary disk file, can differ between implementations. This coprocess example using pty gave unexpected results that were tracked down to the read function on the pipe in the program from Figure 15.8 returning less than a line. The solution was to not use the program shown in Figure 15.8, but to use the version of this program from Exercise 15.5 that was modified to use the standard I/O library, with the standard I/O streams for the both pipes set to line buffering. By doing this, the fgets function does as many reads as required to obtain a complete line. The while loop in Figure 15.8 assumes that each line sent to the coprocess causes one line to be returned.
Driving Interactive Programs Noninteractively
Although it's tempting to think that pty can run any coprocess, even a coprocess that is interactive, it doesn't work. The problem is that pty just copies everything on its standard input to the PTY and everything from the PTY to its standard output, never looking at what it sends or what it gets back.
As an example, we can run the telnet command under pty talking directly to the remote host:
pty telnet 192.168.1.3
Doing this provides no benefit over just typing telnet 192.168.1.3, but we would like to run the telnet program from a script, perhaps to check some condition on the remote host. If the file telnet.cmd contains the four lines
sar
passwd
uptime
exit
the first line is the user name we use to log in to the remote host, the second line is the password, the third line is a command we'd like to run, and the fourth line terminates the session. But if we run this script as
pty -i < telnet.cmd telnet 192.168.1.3
it doesn't do what we want. What happens is that the contents of the file telnet.cmd are sent to the remote host before it has a chance to prompt us for an account name and password. When it turns off echoing to read the password, login uses the tcsetattr option, which discards any data already queued. Thus, the data we send is thrown away.
When we run the telnet program interactively, we wait for the remote host to prompt for a password before we type it, but the pty program doesn't know to do this. This is why it takes a more sophisticated program than pty, such as expect, to drive an interactive program from a script file.
Even running pty from the program in Figure 15.8, as we showed earlier, doesn't help, because the program in Figure 15.8 assumes that each line it writes to the pipe generates exactly one line on the other pipe. With an interactive program, one line of input may generate many lines of output. Furthermore, the program in Figure 15.8 always sent a line to the coprocess before reading from it. This won't work when we want to read from the coprocess before sending it anything.
There are a few ways to proceed from here to be able to drive an interactive program from a script. We could add a command language and interpreter to pty, but a reasonable command language would probably be ten times larger than the pty program. Another option is to take a command language and use the pty_fork function to invoke interactive programs. This is what the expect program does.
We'll take a different path and just provide an option (-d) to allow pty to be connected to a driver process for its input and output. The standard output of the driver is pty's standard input and vice versa. This is similar to a coprocess, but on "the other side" of pty. The resulting arrangement of processes is almost identical to Figure 19.16, but in the current scenario, pty does the fork and exec of the driver process. Also, instead of two half-duplex pipes, we'll use a single bidirectional pipe between pty and the driver process.
Figure 19.17 shows the source for the do_driver function, which is called by the main function of pty (Figure 19.12) when the -d option is specified.
Figure 19.17. The do_driver function for the pty program
#include "apue.h"
void
do_driver(char *driver)
{
pid_t child;
int pipe[2];
/*
* Create a stream pipe to communicate with the driver.
*/
if (s_pipe(pipe) < 0)
err_sys("can't create stream pipe");
if ((child = fork()) < 0) {
err_sys("fork error");
} else if (child == 0) { /* child */
close(pipe[1]);
/* stdin for driver */
if (dup2(pipe[0], STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
/* stdout for driver */
if (dup2(pipe[0], STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
if (pipe[0] != STDIN_FILENO && pipe[0] != STDOUT_FILENO)
close(pipe[0]);
/* leave stderr for driver alone */
execlp(driver, driver, (char *)0);
err_sys("execlp error for: %s", driver);
}
close(pipe[0]); /* parent */
if (dup2(pipe[1], STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
if (dup2(pipe[1], STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
if (pipe[1] != STDIN_FILENO && pipe[1] != STDOUT_FILENO)
close(pipe[1]);
/*
* Parent returns, but with stdin and stdout connected
* to the driver.
*/
}
By writing our own driver program that is invoked by pty, we can drive interactive programs in any way desired. Even though it has its standard input and standard output connected to pty, the driver process can still interact with the user by reading and writing /dev/tty. This solution still isn't as general as the expect program, but it provides a useful option to pty for fewer than 50 lines of code.
19.7. Advanced Features
Pseudo terminals have some additional capabilities that we briefly mention here. These capabilities are further documented in Sun Microsystems [2002] and the BSD pty(4) manual page.
Packet Mode
Packet mode lets the PTY master learn of state changes in the PTY slave. On Solaris, this mode is enabled by pushing the STREAMS module pckt onto the PTY master side. We showed this optional module in Figure 19.2. On FreeBSD, Linux, and Mac OS X, this mode is enabled with the TIOCPKT ioctl command.
The details of packet mode differ between Solaris and the other platforms. Under Solaris, the process reading the PTY master has to call getmsg to fetch the messages from the stream head, because the pckt module converts certain events into nondata STREAMS messages. With the other platforms, each read from the PTY master returns a status byte followed by optional data.
Regardless of the implementation details, the purpose of packet mode is to inform the process reading the PTY master when the following events occur at the line discipline module above the PTY slave: when the read queue is flushed, when the write queue is flushed, whenever output is stopped (e.g., Control-S), whenever output is restarted, whenever XON/XOFF flow control is enabled after being disabled, and whenever XON/XOFF flow control is disabled after being enabled. These events are used, for example, by the rlogin client and rlogind server.
Remote Mode
A PTY master can set the PTY slave into remote mode by issuing an ioctl of TIOCREMOTE. Although FreeBSD 5.2.1, Mac OS X 10.3, and Solaris 9 use the same command to enable and disable this feature, under Solaris the third argument to ioctl is an integer, whereas with FreeBSD and Mac OS X, it is a pointer to an integer. (Linux 2.4.22 doesn't support this command.)
When it sets this mode, the PTY master is telling the PTY slave's line discipline module not to perform any processing of the data that it receives from the PTY master, regardless of the canonical/noncanonical flag in the slave's termios structure. Remote mode is intended for an application, such as a window manager, that does its own line editing.
Window Size Changes
The process above the PTY master can issue the ioctl of TIOCSWINSZ to set the window size of the slave. If the new size differs from the current size, a SIGWINCH signal is sent to the foreground process group of the PTY slave.
Signal Generation
The process reading and writing the PTY master can send signals to the process group of the PTY slave. Under Solaris 9, this is done with an ioctl of TIOCSIGNAL, with the third argument set to the signal number. With FreeBSD 5.2.1 and Mac OS X 10.3, the ioctl is TIOCSIG, and the third argument is a pointer to the integer signal number. (Linux 2.4.22 doesn't support this ioctl command either.)
|
|