Chapter 16. Terminals and Pseudo Terminals

Devices designed for interactive use[1] all have a similar interface derived from the one created decades ago for serial TeleType paper-display terminals and thus dubbed the tty interface. The tty interface is used for accessing serial terminals, consoles, xterms, network logins, and more.

This tty interface is simple in conception but complex in implementation. It is flexible and powerful, which makes it possible to write applications that do not know much about how they get their input and output and can run over the network, on a local screen, or through a modem. Applications can even run under the control of another program without being aware of it.

Unfortunately, it took the Unix implementors several tries to get the interface right. They have left us with three distinct interfaces for connecting to tty devices. The BSD sgtty and System V termio interfaces have now been superseded by the POSIX termios interface, which is a superset of the termio interface. Because all current systems support the termios interface, and because it is the most powerful of the interfaces, we document only termios, not the earlier interfaces. (For the sake of supporting legacy source code, Linux supports termio, as well as termios. It also used to support the sgtty interface in a limited way, but that support has been removed because it was never perfect, and because there was no longer significant demand for it.)

Not only does the termios interface have to support interactive program use, but it must also support other kinds of data traffic. The same serial line over which you log in using a modem you might also use for dialing out via a modem, to talk to a serial printer, or to talk to some specialized piece of hardware.

A tty device has two ends. The simplistic view is that one end is attached to the program and the other end is attached to the hardware device. This is true for a serial port; in this case, the serial device driver attaches the serial port (and thereby the terminal or modem) to a shell, editor, or other program. It is also true for the console; the console driver connects the keyboard and screen to the same types of programs. But in some cases, there is a program on each end; in these cases, one of the ends takes the place of hardware. For instance, with a network connection, one end of the tty device is connected to a program that provides the network connection, and the other end is connected to the shell, editor, or other potentially interactive program. When there is a program at each end, you need to keep a clear idea of which end is emulating hardware; in the case of network connections, the side that connects to the network is the hardware side.

tty devices that have software at both ends are called pseudo ttys, or, simply, ptys. For the first part of this chapter, you can pretend that they do not exist, because the “software” end of a pty is handled just like any tty device. Later on, we talk about programming the “hardware” end of a pty.

tty Operations

tty devices provide a large number of processing options; they are among the most complicated devices in the kernel. You can set input processing, output processing, and data flow processing options. You can also control a limited amount of data manipulation that occurs at the device driver level.

ttys operate in two basic modes: raw and cooked. Raw mode passes data to the application as it is received, with no changes made. Cooked mode, also known as canonical mode, provides a limited line editor inside the device driver and sends edited input to the application one line at a time. This mode is primarily derived from mainframe systems, in which dedicated input processing units provided cooked mode without interrupting the CPU at all.

Cooked mode processes certain control characters; for example, by default, ^U kills (erases) the current line, ^W erases the current word, backspace (^H) or delete erases the previous character, and ^R erases and then retypes the current line. Each of these control actions can be reassigned to a different character. For instance, on many terminals, DEL (character 127) is assigned the backspace action.

Terminal Utility Functions

Sometimes you do not know whether a file descriptor corresponds to a tty. This is most commonly the case for the standard output file descriptor. Programs that output text to standard output often format differently when they are writing to a pipe than when they are displaying information for human consumption. For example, when you use ls to list files, it will print multiple columns when you simply run it (most convenient for a human to read), but when you pipe it to another program, it will print one file per line (most convenient for a program to read). Run ls and ls | cat and see the difference.

You can determine whether a file descriptor corresponds to a tty by using the isatty() function, which takes a file descriptor as its argument and returns 1 if the descriptor corresponds to a tty, and 0 otherwise.

#include <unistd.h>

int isatty(int fd);

The ttyname() function provides a canonical name for the terminal (if any) associated with a file descriptor. It takes as its argument any file descriptor, and returns a pointer to a character string.

#include <unistd.h>

char *ttyname(int fd);

Because that character string is (the standard says “may be", but we all know better) in static space, you need to make a copy of the returned string before calling ttyname() again; it is not re-entrant. ttyname() returns NULL on any error, including if it is passed a file descriptor that is not associated with a tty.

Controlling Terminals

Every session (see Chapter 10) is tied to a terminal from which processes in the session get their input and to which they send their output. That terminal may be the machine’s local console, a terminal connected over a serial line, or a pseudo terminal that maps to an X window or across a network (see page 389 later in this chapter for more on pseudo terminals). The terminal to which a session is related is called the controlling terminal (or controlling tty) of the session. A terminal can be the controlling terminal for only one session at a time.

Normal processes cannot change their controlling terminal; only a session leader can do that. Under Linux, a change in the session leader’s controlling terminal is not propagated to other processes in that session. Session leaders almost always set a controlling terminal when they initially start running, before they create any child processes, in order to ensure that all the processes in the session share a common controlling terminal.

There are two interfaces for changing a session group leader’s controlling tty. The first is through the normal open() and close() system calls:

  1. Close all file descriptors that reference the current controlling terminal.

  2. Open a new terminal without specifying the O_NOCTTY flag.

The other method involves ioctl()s on separate file descriptors that reference the old and the new terminal devices:

  1. TIOCNOTTY on a file descriptor tied to the original controlling tty (usually, ioctl(0, TIOCNOTTY, NULL) works fine). This breaks the bond between the session and the tty.

  2. TIOCSCTTY on the file descriptor tied to the new controlling tty. This sets a new controlling tty.

A terminal that is being used by a session keeps track of which process group is considered the foreground process group. Processes in that process group are allowed to read from and write to the terminal, whereas processes in other process groups are not (see Chapter 15 for details on what happens when background processes try to read and write from the controlling terminal). The tcsetpgrp() function allows a process running on a terminal to change the foreground process group for that terminal.[2]

int tcsetpgrp(int ttyfd, pid_t pgrp);

The first parameter specifies the tty whose controlling process group is being changed, and pgrp is the process group that should be moved to the foreground. Processes may change the foreground process group only for their controlling terminal. If the process making the change is not in the foreground process group on that terminal, a SIGTTOU is generated, unless that signal is being ignored or is blocked.[3]

Terminal Ownership

There are two system databases used to keep track of logged-in users; utmp is used specifically for currently logged-in users, and wtmp is a record of all previous logins since the file was created. The who command uses the utmp database to print out its list of logged-in users, and the last command uses the wtmp database to print out its list of users who have logged into the system since the wtmp database was regenerated. On Linux systems, the utmp database is stored in the file /var/run/utmp, and the wtmp database is stored in the file /var/log/wtmp.

Programs that use ttys for user login sessions (whether or not they are associated with a graphical login) should update the two system databases, unless the user explicitly requests otherwise; for example, some users do not want every shell session they are running in a terminal emulator under the X Window System to be listed as a login process. Only add interactive sessions, because utmp and wtmp are not meant for logging automated programs. Any tty that is not a controlling terminal should normally not be added to the utmp and wtmp databases.

Recording with utempter

Applications that use ptys and that are written with security in mind rarely have sufficient permissions to modify the database files. These applications should provide the option to use a simple helper program that is available on most Linux systems and some other systems, but is not standardized: the utempter utility. The utempter utility is setgid (or setuid if necessary) with sufficient permissions to modify the utmp and wtmp databases, and it is accessed through a simple library. The utempter utility checks to make sure that the process owns the tty that it is trying to log into the utmp database before allowing the operation. utempter is meant to be used only for ptys; other ttys are generally opened by daemons with sufficient permissions to modify the system database files.

#include <utempter.h>

void addToUtmp(const char *pty, const char *hostname, int ptyfd);
void removeLineFromUtmp(const char *pty, int ptyfd);
void removeFromUtmp(void);

The addToUtmp() function takes three arguments. The first, pty, is the full path to the pty being added. The second, hostname, may be NULL; if not, it is the network name of the system from which a network connection using this pty originated (this sets ut_host, as documented on page 343). The third, ptyfd, must be an open file descriptor referencing the pty device named in the pty argument.

The removeLineFromUtmp() function takes two arguments; they are defined exactly like the arguments of the same name passed to the addToUtmp() function.

Some existing applications are written with a structure that makes it difficult to keep the name and file descriptor around to clean up the utmp entry. Because of this, the utempter library keeps a cache of the more recent device name and file descriptor passed to addToUtmp() and a convenience function removeFromUtmp() that takes no arguments and acts like removeLineFromUtmp() on the information it has cached. This works only for applications that add only one utmp entry; more complex applications that use more than one pty must use removeLineFromUtmp() instead.

Recording by Hand

The area of utmp and wtmp handling is one of those inconsistent areas where mechanisms have differed between systems and changed over the years; even the definition of what information is available in utmp and wtmp still differs between systems. Originally, utmp and wtmp were essentially just arrays of structures written to disk; over time, APIs were created to handle the records reliably.

At least two such interfaces have been officially standardized; the original utmp interface (specified in XSI, XPG2, and SVID2) and the extended utmpx interface (specified in XPG4.2 and in recent editions of POSIX). Conveniently, both interfaces (utmp and utmpx) are available on Linux. The utmp interface, which varies widely between machines, has a set of defines to make it possible to write portable code that take advantage of the extensions that glibc provides. The utmpx interface, which is more strictly standardized, does not (currently) provide those defines but still provides the extensions.

The Linux utmp interface was originally constructed as a superset of other existing utmp interfaces, and utmpx was standardized as a superset of other existing utmp interfaces; happily, the two supersets are essentially the same, so on Linux, the difference between the utmp and utmpx data structures is the letter x.

If you do not wish to use any extensions, we recommend that you use the utmpx interface, because as long as you are using no extensions, it is the most portable; it is strictly standardized.

If you do wish to use extensions, however, we recommend that you use the utmp interface, because glibc provides defines that let you write portable code that takes advantage of the extensions.

There is also a hybrid approach—include both header files and use the defines that glibc provides for the utmp interface to decide whether to use extensions in the utmpx interface. We recommend against this because there is no guarantee that the utmp.h and utmpx.h header files will not conflict on non-Linux systems. If you want both maximum portability and maximum functionality, this is one of those areas where you may have to write some code twice—one version using utmpx for easy portability to new systems, and then one version with #ifdefs for maximum functionality on each new system you port to.

We document here only the most commonly used extensions; the glibc documentation covers all the extensions that it supports. The utmp functions operate in terms of struct utmp; we ignore some of the extensions. The utmpx structure and functions operate exactly the same as the utmp structure and functions, and so we do not document them separately. Note that the same structure is used for both utmp and wtmp, because the two databases are so similar.

struct utmp {
    short int ut_type;          /* type of login */
    pid_t ut_pid;               /* process id of login process */
    char ut_line[UT_LINESIZE];  /* 32 characters */
    char ut_id[4];              /* inittab id */
    char ut_user[UT_NAMESIZE];  /* 32 characters */
    char ut_host[UT_HOSTSIZE];  /* 256 characters */
    struct timeval ut_tv;
    struct exit_status ut_exit; /* status of dead processes */
    long ut_session;
    int32_t ut_addr_v6[4];
};

Many of the same members are part of struct utmpx by the same name. The members that are not required to be members of struct utmpx are annotated as “not standardized by POSIX” (none of them are standardized as part of struct utmp since struct utmp is itself not standardized).

The character array members are not necessarily NULL-terminated strings. Use sizeof() or other size limitations judiciously.

ut_type

One of the following values: EMPTY, INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, DEAD_PROCESS, BOOT_TIME, NEW_TIME, OLD_TIME, RUN_LVL, or ACCOUNTING, each of which is described below.

ut_tv

The time associated with the event. This is the only member beside ut_type that POSIX specifies as always valid for nonempty entries. Some systems have instead a ut_time member instead that is measured only in seconds.

ut_pid

The process id of the associated process, for all types ending in_PROCESS.

ut_id

The inittab id of the associated process, for all types ending in_PROCESS. This is the first field in noncomment lines in the file /etc/inittab, where fields are separated by : characters. Network logins, which are not associated with inittab, may use this in other ways; for example, they may include parts of the device information here.

ut_line

The line (basename of the device or local display number for X) associated with the process. The POSIX specification is unclear about the status of ut_line; it does not list ut_line as meaningful for LOGIN_PROCESS, but in another place it implies that it is meaningful for LOGIN_PROCESS, and this makes sense in practice. POSIX says that ut_line is meaningful for USER_PROCESS. In practice, it is often also meaningful for DEAD_PROCESS, depending on the origin of the dead process.

ut_user

Normally, the name of the logged-in user; it can also be the name of the login process (normally, LOGIN) depending on the value of ut_type.

ut_host

The name of the remote host that has logged in (or is otherwise associated) with this process. The ut_host member applies only to USER_PROCESS. This member is not standardized by POSIX.

ut_exit

ut_exit.e_exit gives the exit code as provided by the WEXITSTATUS() macro, and ut_exit.e_termination gives the signal that caused the process to terminate (if it was terminated by a signal), as provided by the WTERMSIG() macro. This member is not standardized by POSIX.

ut_session

The session ID in the X Window System. This member is not standardized by POSIX.

ut_addr_v6

The IP address of the remote host, in the case of a USER_PROCESS initiated by a connection from a remote host. Use the inet_ntop() function to generate printable content. If only the first quad is nonzero, then it is an IPV4 address (inet_ntop() takes the AF_INET argument); otherwise it is an IPV6 address (inet_ntop() takes the AF_INET6 argument). This member is not standardized by POSIX.

The ut_type member defines how all the rest of the members are defined. Some ut_type values are reserved for recording system information; these are useful only for specialized system programs. We do not document these fully.

EMPTY

There is no valid information in this utmp record (such records may be reused later), so ignore the contents of this record. None of the other structure members have meaning.

INIT_PROCESS

The listed process was spawned directly from init. This value may be set by system programs (normally only the init process itself); applications should read and recognize this value but never set it. The ut_pid,ut_id, and ut_tv members are meaningful.

LOGIN_PROCESS

Instances of the login program waiting for a user to log in. The ut_id, ut_pid, and ut_tv members are useful; the ut_user member is nominally useful (on Linux, it will say LOGIN but this name of the login process is implementation-defined according to POSIX.

USER_PROCESS

This entry denotes a session leader for a logged-in user. This may be the login program after a user has logged in, the display manager or session manager for an X Window System login, a terminal emulator program configured to mark login sessions, or any other interactive user login. The ut_id, ut_user, ut_line, ut_pid, and ut_tv members are meaningful.

DEAD_PROCESS

The listed process was a session leader for a logged-in user, but has exited. The ut_id, ut_pid, and ut_tv members are meaningful according to POSIX. The ut_exit member (not specified by POSIX) is meaningful only in this context.

BOOT_TIME

The time the system booted. In utmp this will be the most recent boot; in wtmp there will be an entry for every system boot since wtmp was cleared. Only ut_tv is meaningful.

OLD_TIME and NEW_TIME

These are used only for recording times that the clock time jumped, and are recorded in pairs. Do not depend on these entries being recorded on the system even if the clock time is changed for any reason.

RUN_LVL and ACCOUNTING

These are internal system values; do not use in applications.

The interfaces defined by XPG2, SVID 2, and FSSTND 1.2 are:

#include <utmp.h>

int utmpname(char *file);
struct utmp *getutent(void);
struct utmp *getutid(const struct utmp *id);
struct utmp *getutline(const struct utmp *line);
struct utmp *pututline(const struct utmp *ut);
void setutent(void);
void endutent(void);
void updwtmp(const char *file, const struct utmp *ut);
void logwtmp(const char *line, const char *name, const char *host);

Each record in a utmp or wtmp database is called a line. All the functions that return a pointer to a struct utmp return a pointer to static data on success, and a NULL pointer on failure. Note that the static data is overwritten by every new call to any function that returns a struct utmp. Also, the POSIX standard (for utmpx) requires that the static data be cleared by the application before some searches.

The utmpx versions of these functions take struct utmpx instead of struct utmp, require including utmpx.h, and are named getutxent, getutxid, getutxline, pututxline, setutxent, and endutxent, but are otherwise identical to the utmp versions of these functions on Linux. There is no utmpxname() function defined by POSIX, although some platforms may choose to define it anyway (as does glibc).

The utmpname() function is used to determine which database you are looking at. The default database is the utmp database, but you can use this function to point to wtmp instead. Two predefined names are _PATH_UTMP for the utmp file and _PATH_WTMP for the wtmp file; for testing you might choose instead to point to a local copy. The utmpname() function returns zero on success, nonzero on failure, but success may simply mean that it was able to copy a filename into the library; it does not mean that a database actually exists at the path you have provided to it!

The getutent() function simply returns the next line from the database. If the database has not been opened yet, it returns the contents of the first line. If no more lines are available, it returns NULL.

The getutid() function takes a struct utmp and looks only at one or two members. If the ut_type is BOOT_TIME, OLD_TIME, or NEW_TIME, then it returns the next line of that type. If the ut_type is any of INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, or DEAD_PROCESS, then getutid() returns the next line matching any of those types that also has a ut_id value matching the ut_id value in the struct utmp passed to getutid(). You must clear the data in the struct utmp returned by getutid() before calling it again; otherwise, it is allowed by POSIX to return the same line as the previous invocation. If no matching lines are available, it returns NULL.

The getutline() function returns the next line with ut_id set to LOGIN_PROCESS or USER_PROCESS that also has a ut_line value matching the ut_line value in the struct utmp passed to getutline(). Like getutid(), you must clear the data in the struct utmp returned by getutline() before calling it again; otherwise, it is allowed by POSIX to return the same line as the previous invocation. If no matching lines are available, it returns NULL.

The pututline() function modifies (or adds, if necessary) the database record matching the ut_line member of its struct utmp argument. It does this only if the process has sufficient permissions to modify the database. If it succeeds in modifying the database, it returns a struct utmp that matches the data it wrote to the database. Otherwise, it returns NULL. The pututline() function is not portably applicable to the wtmp database. To modify the wtmp database, use updwtmp() or logwtmp() instead.

The setutent() function rewinds the database to the beginning.

The endutent() function closes the database. This closes the file descriptor and frees any associated data. Call endutent() before using utmpname() to access a different utmp file, as well as after you are done accessing utmp data.

BSD defined two functions that are also available as part of glibc, which are the most robust way to modify the wtmp database.

The updwtmp() function takes the filename of the wtmp database (normally _PATH_WTMP) and a filled-in struct utmp, and attempts to append the entry to the wtmp file. Failure is not reported.

The logwtmp() function is a convenience function that fills in a struct utmp and calls updwtmp() with it. The line argument is copied to ut_line, name is copied to ut_user, host is copied to ut_host, ut_tv is filled in from the current time, and ut_pid is filled in from the current process id. Like updwtmp(), it does not report failure.

The utmp program demonstrates several methods of reading utmp and wtmp databases:

  1: /* utmp.c */
  2:
  3: #include <stdio.h>
  4: #include <unistd.h>
  5: #include <string.h>
  6: #include <time.h>
  7: #include <sys/time.h>
  8: #include <sys/types.h>
  9: #include <sys/socket.h>
 10: #include <netinet/in.h>
 11: #include <arpa/inet.h>
 12: #include <utmp.h>
 13: #include <popt.h>
 14:
 15: void print_utmp_entry(struct utmp *u) {
 16:     struct tm *tp;
 17:     char *type;
 18:     char addrtext[INET6_ADDRSTRLEN];
 19:
 20:     switch (u->ut_type) {
 21:         case EMPTY: type = "EMPTY"; break;
 22:         case RUN_LVL: type = "RUN_LVL"; break;
 23:         case BOOT_TIME: type = "BOOT_TIME"; break;
 24:         case NEW_TIME: type = "NEW_TIME"; break;
 25:         case OLD_TIME: type = "OLD_TIME"; break;
 26:         case INIT_PROCESS: type = "INIT_PROCESS"; break;
 27:         case LOGIN_PROCESS: type = "LOGIN_PROCESS"; break;
 28:         case USER_PROCESS: type = "USER_PROCESS"; break;
 29:         case DEAD_PROCESS: type = "DEAD_PROCESS"; break;
 30:         case ACCOUNTING: type = "ACCOUNTING"; break;
 31:     }
 32:     printf("%-13s:", type);
 33:     switch (u->ut_type) {
 34:         case LOGIN_PROCESS:
 35:         case USER_PROCESS:
 36:         case DEAD_PROCESS:
 37:             printf(" line: %s", u->ut_line);
 38:             /* fall through */
 39:         case INIT_PROCESS:
 40:             printf("
 pid: %6d id: %4.4s", u->ut_pid, u->ut_id);
 41:     }
 42:     printf("
");
 43:     tp = gmtime(&u->ut_tv.tv_sec);
 44:     printf("  time: %24.24s.%lu
", asctime(tp), u->ut_tv.tv_usec);
 45:     switch (u->ut_type) {
 46:         case USER_PROCESS:
 47:         case LOGIN_PROCESS:
 48:         case RUN_LVL:
 49:         case BOOT_TIME:
 50:             printf("  user: %s
", u->ut_user);
 51:     }
 52:     if (u->ut_type == USER_PROCESS) {
 53:         if (u->ut_session)
 54:             printf("  sess: %lu
", u->ut_session);
 55:         if (u->ut_host)
 56:             printf("  host: %s
", u->ut_host);
 57:         if (u->ut_addr_v6[0]) {
 58:             if (!(u->ut_addr_v6[1] |
 59:                   u->ut_addr_v6[2] |
 60:                   u->ut_addr_v6[3])) {
 61:                /* only first quad filled in implies IPV4 address */
 62:                 inet_ntop(AF_INET, u->ut_addr_v6,
 63:                           addrtext, sizeof(addrtext));
 64:                 printf("  IPV4: %s
", addrtext);
 65:             } else {
 66:                 inet_ntop(AF_INET6, u->ut_addr_v6,
 67:                           addrtext, sizeof(addrtext));
 68:                 printf("  IPV6: %s
", addrtext);
 69:             }
 70:         }
 71:     }
 72:     if (u->ut_type == DEAD_PROCESS) {
 73:            printf("  exit: %u:%u
",
 74:                   u->ut_exit.e_termination,
 75:                   u->ut_exit.e_exit);
 76:     }
 77:     printf("
");
 78: }
 79:
 80: struct utmp * get_next_line(char *id, char *line) {
 81:     struct utmp request;
 82:
 83:     if (!id && !line)
 84:         return getutent();
 85:
 86:     memset(&request, 0, sizeof(request));
 87:
 88:     if (line) {
 89:         strncpy(&request.ut_line[0], line, UT_LINESIZE);
 90:         return getutline(&request);
 91:     }
 92:
 93:     request.ut_type = INIT_PROCESS;
 94:     strncpy(&request.ut_id[0], id, 4);
 95:     return getutid(&request);
 96: }
 97:
 98: void print_file(char * name, char *id, char *line) {
 99:     struct utmp *u;
100:
101:     if (utmpname(name)) {
102:         fprintf(stderr, "utmp database %s open failed
", name);
103:         return;
104:     }
105:     setutent();
106:     printf("%s:
====================
", name);
107:     while ((u=get_next_line(id, line))) {
108:         print_utmp_entry(u);
109:         /* POSIX requires us to clear the static data before
110:          * calling getutline or getutid again
111:          */
112:         memset(u, 0, sizeof(struct utmp));
113:     }
114:     endutent();
115: }
116:
117: int main(int argc, const char **argv) {
118:     char *id = NULL, *line = NULL;
119:     int show_utmp = 1, show_wtmp = 0;
120:     int c;
121:     poptContext optCon;
122:     struct poptOption optionsTable[] = {
123:         { "utmp", 'u', POPT_ARG_NONE|POPT_ARGFLAG_XOR,
124:            &show_utmp, 0,
125:           "toggle showing contents of utmp file", NULL },
126:         { "wtmp", 'w', POPT_ARG_NONE|POPT_ARGFLAG_XOR,
127:           &show_wtmp, 0,
128:           "toggle showing contents of wtmp file", NULL },
129:         { "id", 'i', POPT_ARG_STRING, &id, 0,
130:           "show process entries for specified inittab id",
131:           "<inittab id>" },
132:         { "line", 'l', POPT_ARG_STRING, &line, 0,
133:           "show process entries for specified device line",
134:           "<line>" },
135:         POPT_AUTOHELP
136:         POPT_TABLEEND
137:     };
138:
139:     optCon = poptGetContext("utmp", argc, argv, optionsTable, 0);
140:     if ((c = poptGetNextOpt(optCon)) < -1) {
141:         fprintf(stderr, "%s: %s
",
142:             poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
143:             poptStrerror(c));
144:         return 1;
145:     }
146:     poptFreeContext(optCon);
147:
148:     if (id && line)
149:         fprintf(stderr, "Cannot choose both by id and line, "
150:                         "choosing by line
");
151:
152:     if (show_utmp)
153:         print_file(_PATH_UTMP, id, line);
154:     if (show_utmp && show_wtmp)
155:         printf("


");
156:     if (show_wtmp)
157:         print_file(_PATH_WTMP, id, line);
158:
159:     return 0;
160: }

termios Overview

All tty manipulation is done through one structure, struct termios, and through several functions, all defined in the <termios.h> header file. Of those functions, only six are commonly used, and when you do not have to set line speeds, you are likely to use only two of them. The two most important functions are the tcgetattr() and tcsetattr() functions:

#include <termios.h>

struct termios {
    tcflag_t c_iflag;   /* input mode flags */
    tcflag_t c_oflag;   /* output mode flags */
    tcflag_t c_cflag;   /* control mode flags */
    tcflag_t c_lflag;   /* local mode flags */
    cc_t c_line;        /* line discipline */
    cc_t c_cc[NCCS];    /* control characters */
};

int tcgetattr (int fd, struct termios *tp);
int tcsetattr (int fd, int oact, struct termios *tp);

In almost every situation, programs should use tcgetattr() to get a device’s current settings, modify those settings, and then use tcsetattr() to make the modified settings active. Many programs also save a copy of the original settings and restore them before terminating. In general, modify only the settings that you know you care about; changing other settings may make it difficult for users to work around unusual system configurations (or bugs in your code).

tcsetattr() may not honor all the settings you choose; it is allowed to ignore arbitrary settings. In particular, if the hardware simply does not support a setting, tcsetattr() ignores it rather than return an error. If you care that a setting really takes effect, you must use tcgetattr() after tcsetattr() and test to make sure that your change took effect.

To get a tty device’s settings, you have to open the device and use the file descriptor in the tcgetattr() call. This poses a problem with some tty devices; some normally may be opened only once, to prevent device contention. Fortunately, giving the O_NONBLOCK flag to open() causes it to be opened immediately and not block on any operations. However, you may still prefer to block on read(); if so, use fcntl() to turn off O_NONBLOCK mode before you read or write to it:

fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);

The four termios flags control four distinct parts of managing input and output. The input flag, c_iflag, determines how received characters are interpreted and processed. The output flag, c_oflag, determines how characters your process writes to the tty are interpreted and processed. The control flag, c_cflag, determines serial protocol characteristics of the device and is useful only for physical devices. The local flag, c_lflag, determines how characters are collected and processed before they are sent to output processing. Figure 16.1 shows a simplified view of how each of these flags fits into the grand scheme of character processing.

Simplified View of tty Processing

Figure 16.1. Simplified View of tty Processing

We first demonstrate ways to use termios, and then present a short reference to it.

termios Examples

Passwords

One common reason to modify termios settings is to read a password without echoing characters. To do this, you want to turn off local echo while reading the password. Your code should look like this:

struct termios ts, ots;

One structure keeps the original termios settings so that you can restore them, and the other one is a copy to modify.

tcgetattr(STDIN_FILENO, &ts);

Generally, you read passwords from standard input.

ots = ts;

Keep a copy of the original termios settings to restore later.

ts.c_lflag &= ~ECHO;
ts.c_lflag |= ECHONL;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);

Turn off echoing characters except newlines, after all currently pending output is completed. (The first l in c_lflag stands for local processing.)

read_password();

Here, you read the password. This may be as simple as a single fgets() or read() call, or it may include more complex processing, depending on whether the tty is in raw mode or cooked mode, and depending on the requirements of your program.

tcsetattr(STDIN_FILENO, TCSANOW, &ots);

This restores the original termios settings and does so immediately. (We explain other options later, in the reference section on page 371.)

A full example program, readpass, looks like this:

 1: /* readpass.c */
 2:
 3: #include <stdio.h>
 4: #include <stdlib.h>
 5: #include <termios.h>
 6: #include <unistd.h>
 7:
 8: int main(void) {
 9:     struct termios ts, ots;
10:     char passbuf[1024];
11:
12:     /* get and save current termios settings */
13:     tcgetattr(STDIN_FILENO, &ts);
14:     ots = ts;
15:
16:     /* change and set new termios settings */
17:     ts.c_lflag &= ~ECHO;
18:     ts.c_lflag |= ECHONL;
19:     tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);
20:
21:     /* paranoia: check that the settings took effect */
22:     tcgetattr(STDIN_FILENO, &ts);
23:     if (ts.c_lflag & ECHO) {
24:         fprintf(stderr, "Failed to turn off echo
");
25:         tcsetattr(STDIN_FILENO, TCSANOW, &ots);
26:         exit(1);
27:     }
28:
29:     /* get and print the password */
30:     printf("enter password: ");
31:     fflush(stdout);
32:     fgets(passbuf, 1024, stdin);
33:     printf("read password: %s", passbuf);
34:     /* there was a terminating 
 in passbuf */
35:
36:     /* restore old termios settings */
37:     tcsetattr(STDIN_FILENO, TCSANOW, &ots);
38:
39:     exit(0);
40: }

Serial Communications

As an example of programming both ends of a tty, here is a program that connects the current terminal to a serial port. On one tty, the program, called robin, is talking to you as you type. On another tty, it is communicating with the serial port. In order to multiplex input to and output from the local tty and the serial port, the program uses the poll() system call described on page 245.

Here is robin.c in its entirety, followed by an explanation:

  1: /* robin.c */
  2:
  3: #include <sys/poll.h>
  4: #include <errno.h>
  5: #include <fcntl.h>
  6: #include <popt.h>
  7: #include <stdio.h>
  8: #include <stdlib.h>
  9: #include <signal.h>
 10: #include <string.h>             /* for strerror() */
 11: #include <termios.h>
 12: #include <unistd.h>
 13:
 14: void die(int exitcode, const char *error, const char *addl) {
 15:     if (error) fprintf(stderr, "%s: %s
", error, addl);
 16:     exit(exitcode);
 17: }
 18:
 19: speed_t symbolic_speed(int speednum) {
 20:     if (speednum >= 460800) return B460800;
 21:     if (speednum >= 230400) return B230400;
 22:     if (speednum >= 115200) return B115200;
 23:     if (speednum >= 57600) return B57600;
 24:     if (speednum >= 38400) return B38400;
 25:     if (speednum >= 19200) return B19200;
 26:     if (speednum >= 9600) return B9600;
 27:     if (speednum >= 4800) return B4800;
 28:     if (speednum >= 2400) return B2400;
 29:     if (speednum >= 1800) return B1800;
 30:     if (speednum >= 1200) return B1200;
 31:     if (speednum >= 600) return B600;
 32:     if (speednum >= 300) return B300;
 33:     if (speednum >= 200) return B200;
 34:     if (speednum >= 150) return B150;
 35:     if (speednum >= 134) return B134;
 36:     if (speednum >= 110) return B110;
 37:     if (speednum >= 75) return B75;
 38:     return B50;
 39: }
 40:
 41: /* These need to have file scope so that we can use them in
 42:  * signal handlers */
 43: /* old port termios settings to restore */
 44: static struct termios pots;
 45: /* old stdout/in termios settings to restore */
 46: static struct termios sots;
 47: /* port file descriptor */
 48: int    pf;
 49:
 50: /* restore original terminal settings on exit */
 51: void cleanup_termios(int signal) {
 52:     tcsetattr(pf, TCSANOW, &pots);
 53:     tcsetattr(STDIN_FILENO, TCSANOW, &sots);
 54:     exit(0);
 55: }
 56:
 57: /* handle a single escape character */
 58: void send_escape(int fd, char c) {
 59:     switch (c) {
 60:     case 'q':
 61:         /* restore termios settings and exit */
 62:         cleanup_termios(0);
 63:         break;
 64:     case 'b':
 65:         /* send a break */
 66:         tcsendbreak(fd, 0);
 67:         break;
 68:     default:
 69:         /* pass the character through */
 70:         /* "C- C-" sends "C-" */
 71:         write(fd, &c, 1);
 72:         break;
 73:     }
 74:     return;
 75: }
 76:
 77: /* handle escape characters, writing to output */
 78: void cook_buf(int fd, char *buf, int num) {
 79:     int current = 0;
 80:     static int in_escape = 0;
 81:
 82:     if (in_escape) {
 83:         /* cook_buf last called with an incomplete escape
 84:            sequence */
 85:         send_escape(fd, buf[0]);
 86:         num--;
 87:         buf++;
 88:         in_escape = 0;
 89:     }
 90:     while (current < num) {
 91: #       define CTRLCHAR(c) ((c)-0x40)
 92:         while ((current < num) && (buf[current] != CTRLCHAR('')))
 93:             current++;
 94:         if (current) write (fd, buf, current);
 95:         if (current < num) {
 96:             /* found an escape character */
 97:             current++;
 98:             if (current >= num) {
 99:                 /* interpret first character of next sequence */
100:                 in_escape = 1;
101:                 return;
102:             }
103:             send_escape(fd, buf[current]);
104:         }
105:         num -= current;
106:         buf += current;
107:         current = 0;
108:     }
109:     return;
110: }
111:
112: int main(int argc, const char *argv[]) {
113:     char    c;            /* used for argument parsing */
114:     struct  termios pts;  /* termios settings on port */
115:     struct  termios sts;  /* termios settings on stdout/in */
116:     const char   *portname;
117:     int     speed = 0;     /* used in argument parsing for speed */
118:     struct  sigaction sact;/* used to initialize signal handler */
119:     struct  pollfd ufds[2]; /* communicate with poll() */
120:     int     raw = 0;      /* raw mode? */
121:     int     flow = 0;     /* type of flow control, if any */
122:     int     crnl = 0;     /* send carriage return with newline? */
123:     int     i = 0;        /* used in the multiplex loop */
124:     int     done = 0;
125: #   define BUFSIZE 1024
126:     char    buf[BUFSIZE];
127:     poptContext optCon;   /* context for command-line options */
128:     struct poptOption optionsTable[] = {
129:              { "bps", 'b', POPT_ARG_INT, &speed, 0,
130:                "signaling rate for current maching in bps",
131:                "<BPS>" },
132:              { "crnl", 'c', POPT_ARG_VAL, &crnl, 'c',
133:                "send carriage return with each newline", NULL },
134:              { "hwflow", 'h', POPT_ARG_VAL, &flow, 'h',
135:                "use hardware flow control", NULL },
136:              { "swflow", 's', POPT_ARG_VAL, &flow, 's',
137:                "use software flow control", NULL },
138:              { "noflow", 'n', POPT_ARG_VAL, &flow, 'n',
139:                "disable flow control", NULL },
140:              { "raw", 'r', POPT_ARG_VAL, &raw, 1,
141:                "enable raw mode", NULL },
142:              POPT_AUTOHELP
143:              { NULL, '', 0, NULL, '', NULL, NULL }
144:     };
145:
146: #ifdef DSLEEP
147:     /* wait 10 minutes so we can attach a debugger */
148:     sleep(600);
149: #endif
150:
151:     optCon = poptGetContext("robin", argc, argv, optionsTable, 0);
152:     poptSetOtherOptionHelp(optCon, "<port>");
153:
154:     if (argc < 2) {
155:         poptPrintUsage(optCon, stderr, 0);
156:         die(1, "Not enough arguments", "");
157:     }
158:
159:     if ((c = poptGetNextOpt(optCon)) < -1) {
160:         /* an error occurred during option processing */
161:         fprintf(stderr, "%s: %s
",
162:                 poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
163:                 poptStrerror(c));
164:         return 1;
165:     }
166:     portname = poptGetArg(optCon);
167:     if (!portname) {
168:         poptPrintUsage(optCon, stderr, 0);
169:         die(1, "No port name specified", "");
170:     }
171:
172:     pf = open(portname, O_RDWR);
173:     if (pf < 0) {
174:         poptPrintUsage(optCon, stderr, 0);
175:         die(1, strerror(errno), portname);
176:     }
177:     poptFreeContext(optCon);
178:
179:     /* modify the port configuration */
180:     tcgetattr(pf, &pts);
181:     pots = pts;
182:     /* some things we want to set arbitrarily */
183:     pts.c_lflag &= ~ICANON;
184:     pts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
185:     pts.c_cflag |= HUPCL;
186:     pts.c_cc[VMIN] = 1;
187:     pts.c_cc[VTIME] = 0;
188:
189:     /* Standard CR/LF handling: this is a dumb terminal.
190:      * Do no translation:
191:      * no NL ->  CR/NL mapping on output, and
192:      * no CR ->  NL mapping on input.
193:      */
194:     pts.c_oflag &= ~ONLCR;
195:     pts.c_iflag &= ~ICRNL;
196:
197:     /* Now deal with the local terminal side */
198:     tcgetattr(STDIN_FILENO, &sts);
199:     sots = sts;
200:     /* again, some arbitrary things */
201:     sts.c_iflag &= ~(BRKINT | ICRNL);
202:     sts.c_iflag |= IGNBRK;
203:     sts.c_lflag &= ~ISIG;
204:     sts.c_cc[VMIN] = 1;
205:     sts.c_cc[VTIME] = 0;
206:     sts.c_lflag &= ~ICANON;
207:     /* no local echo: allow the other end to do the echoing */
208:     sts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
209:
210:     /* option handling will now modify pts and sts */
211:     switch (flow) {
212:     case 'h':
213:         /* hardware flow control */
214:         pts.c_cflag |= CRTSCTS;
215:         pts.c_iflag &= ~(IXON | IXOFF | IXANY);
216:         break;
217:     case 's':
218:         /* software flow control */
219:         pts.c_cflag &= ~CRTSCTS;
220:         pts.c_iflag |= IXON | IXOFF | IXANY;
221:         break;
222:     case 'n':
223:         /* no flow control */
224:         pts.c_cflag &= ~CRTSCTS;
225:         pts.c_iflag &= ~(IXON | IXOFF | IXANY);
226:         break;
227:     }
228:     if (crnl) {
229:         /* send CR with NL */
230:         pts.c_oflag |= ONLCR;
231:     }
232:
233:     /* speed is not modified unless -b is specified */
234:     if (speed) {
235:         cfsetospeed(&pts, symbolic_speed(speed));
236:         cfsetispeed(&pts, symbolic_speed(speed));
237:     }
238:
239:     /* set the signal handler to restore the old
240:      * termios handler */
241:     sact.sa_handler = cleanup_termios;
242:     sigaction(SIGHUP, &sact, NULL);
243:     sigaction(SIGINT, &sact, NULL);
244:     sigaction(SIGPIPE, &sact, NULL);
245:     sigaction(SIGTERM, &sact, NULL);
246:
247:     /* Now set the modified termios settings */
248:     tcsetattr(pf, TCSANOW, &pts);
249:     tcsetattr(STDIN_FILENO, TCSANOW, &sts);
250:
251:     ufds[0].fd = STDIN_FILENO;
252:     ufds[0].events = POLLIN;
253:     ufds[1].fd = pf;
254:     ufds[1].events = POLLIN;
255:
256:     do {
257:         int r;
258:
259:         r = poll(ufds, 2, -1);
260:         if ((r < 0) && (errno != EINTR))
261:             die(1, "poll failed unexpectedly", "");
262:
263:         /* First check for an opportunity to exit */
264:         if ((ufds[0].revents | ufds[1].revents) &
265:             (POLLERR | POLLHUP | POLLNVAL)) {
266:             done = 1;
267:             break;
268:         }
269:
270:         if (ufds[1].revents & POLLIN) {
271:             /* pf has characters for us */
272:             i = read(pf, buf, BUFSIZE);
273:             if (i >= 1) {
274:                 write(STDOUT_FILENO, buf, i);
275:             } else {
276:                 done = 1;
277:             }
278:         }
279:         if (ufds[0].revents & POLLIN) {
280:             /* standard input has characters for us */
281:             i = read(STDIN_FILENO, buf, BUFSIZE);
282:             if (i >= 1) {
283:                 if (raw) {
284:                     write(pf, buf, i);
285:                 } else {
286:                     cook_buf(pf, buf, i);
287:                 }
288:             } else {
289:                 done = 1;
290:         }
291:     }
292:     } while (!done);
293:
294:     /* restore original terminal settings and exit */
295:     tcsetattr(pf, TCSANOW, &pots);
296:     tcsetattr(STDIN_FILENO, TCSANOW, &sots);
297:     exit(0);
298: }

robin.c starts out by including a few header files (read the man page for each system call and library function to see which include files you need to include, as usual), then defines a few useful functions.

The symbolic_speed() function at line 19 converts an integer speed into a symbolic speed that termios can handle. Unfortunately, termios is not designed to handle arbitrary speeds, so each speed you wish to use must be part of the user-kernel interface.[4]

Note that it includes some rather high speeds. Not all serial ports support speeds as high as 230,400 or 460,800 bps; the POSIX standard defines speeds only up to 38,400 bps. To make this program portable, each line above the one that sets the speed to 38,400 bps would have to be expanded to three lines, like this:

#  ifdef B460800
    if (speednum >= 460800) return B460800;
#  endif

That still allows users to specify speeds beyond what the serial ports may be able to handle, but the source code will now compile on any system with POSIX termios. (As discussed on page 352 and page 371, any serial port has the option of refusing to honor any termios setting it is incapable of handling, and that includes speed settings. So just because B460800 is defined does not mean you can set the port speed to 460,800 bits per second.)

Next, on lines 44 through 55, we see a few global variables for communicating some variables to a signal handler, and the signal handler itself. The signal handler is designed to restore termios settings on both tty interfaces when a signal is delivered, so it needs to be able to access the structures containing the old termios settings. It also needs to know the file descriptor of the serial port (the file descriptor for standard input does not change, so it is compiled into the binary). The code is identical to that in the normal exit path, which is described later. The signal handler is later attached to signals that would terminate the process if they were ignored.

The send_escape() and cook_buf() functions are discussed later. They are used as part of the input processing in the I/O loop at the end of the main() function.

The conditionally compiled sleep(600) at the beginning of the main() function is there for debugging. In order to debug programs that modify termios settings for standard input or standard output, it is best to attach to the process from a different window or terminal session. However, that means that you cannot just set a breakpoint on the main function and step into the process one instruction at a time. You have to start the program running, find its pid, and attach to it from within the debugger. This process is described in more detail on page 370.

Therefore, if we are debugging and need to debug code that runs before the program waits for input, we need the program to sleep for a while to give us time to attach. Once we attach, we interrupt the sleep, so there is no harm in using a long sleep time. Compile robin.c with -DDSLEEP in order to activate this feature.

Ignoring debugging, we first parse the options using the popt library described in Chapter 26, and then open the serial port to which we will talk.

Next, we use the tcgetattr() function to get the existing termios configuration of the serial port, then we save a copy in pots so that we can restore it when we are through.

Starting with line 183, we modify settings for the serial port:

183:     pts.c_lflag &= ~ICANON;

This line turns off canonicalization in the serial port driver—that is, puts it in raw mode. In this mode, no characters are special—not newlines, not control characters:

184:     pts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);

This turns off all local echoing on the serial port:

185:     pts.c_cflag |= HUPCL;

If there is a modem connected, HUPCL arranges for it to be told to hang up when the final program closes the device:

186:     pts.c_cc[VMIN] = 1;
187:     pts.c_cc[VTIME] = 0;

When a tty is in raw mode, these two settings determine the read() system call’s behavior. This particular setting says that when we call read(), we want read() to wait to return until one or more bytes have been read. We never call read() unless we know that there is at least one byte to read, so this is functionally equivalent to a nonblocking read(). The definition of VMIN and VTIME is complex, as we demonstrate on page 387.

The default termios settings include some end-of-line character translation. That is okay for dial-in lines and for terminal sessions, but when we are connecting two ttys, we do not want the translation to happen twice. We do not want to map newline characters to a carriage return/newline pair on output, and we do not want to map a received carriage return to a newline on input, because we are already receiving carriage return/newline pairs from the remote system:

194:     pts.c_oflag &= ~ONLCR;
195:     pts.c_iflag &= ~ICRNL;

Without these two lines, using robin to connect to another Linux or Unix computer would result in the remote system seeing you press Return twice each time you press it once, and each time it tries to display a new line on your screen, you will see two lines. So each time you press Return (assuming you manage to log in with these terminal settings), you will see two prompts echoed back to you, and if you run vi, you will see ~ characters on every other line rather than on every line.

At this point, we have made all the changes to the serial port’s termios settings that we know we need to make before we process the command-line arguments. We now turn to modifying the settings for the tty that gives us standard input and output. Since it is one tty, we need to deal with only one file descriptor of the pair. We have chosen standard input, following the convention set by the stty program. We start out, again, by getting and saving attributes.

Then we modify some flags:

201:     sts.c_iflag &= ~(BRKINT | ICRNL);
202:     sts.c_iflag |= IGNBRK;
203:     sts.c_lflag &= ~ISIG;

Turning off BRKINT makes a difference only if robin is being called from a login session attached to another serial port on which a break can be received. Turning it off means that the tty driver does not send a SIGINT to robin when a break condition occurs on robin’s standard input, since robin does not have anything useful to do when it receives a break. Turning off ICRNL prevents any received carriage-return (' ') characters from being reported to robin as newline (' ')characters. Like turning off BRKINT, this applies only when the login session is attached to another serial port. In addition, it applies only if carriage-return characters are not being ignored (that is, if the IGNCR flag is not set).

The IGNBRK function tells the tty driver to ignore breaks. Turning on IGNBRK is actually redundant here. If IGNBRK is set, then BRKINT is ignored. But it does not hurt to set both.

Those are input processing flags. We also modify a local processing flag: We turn off ISIG. This keeps the tty driver from sending SIGINT, SIGQUIT, and SIGTSTP when the respective character (INTR, QUIT, or SUSP) is received. We do this because we want those characters to be sent to the remote system (or whatever is connected to the serial port) for processing there.

Next comes option handling. In some cases, the default modifications we made to the termios settings might not be sufficient, or they might be too much. In these cases, we provide some command-line options to modify the termios options.

By default, we leave the serial port in whatever flow-control state we find it in. However, at line 212, there are options to do hardware flow control (which uses the CTS and RTS flow-control wires), software flow control (reserving ^S and ^Q for STOP and START, respectively), and explicitly disable flow control entirely:

212:     case 'h':
213:         /* hardware flow control */
214:         pts.c_cflag |= CRTSCTS;
215:         pts.c_iflag &= ~(IXON | IXOFF | IXANY);
216:         break;
217:     case 's':
218:         /* software flow control */
219:         pts.c_cflag &= ~CRTSCTS;
220:         pts.c_iflag |= IXON | IXOFF | IXANY;
221:         break;
222:     case 'n':
223:         /* no flow control */
224:         pts.c_cflag &= ~CRTSCTS;
225:         pts.c_iflag &= ~(IXON | IXOFF | IXANY);
226:         break;

Note that software flow control involves three flags:

IXON

Stop sending output if a STOP character (usually, ^S) is received, and start again when a START character (usually, ^Q) is received.

IXOFF

Send a STOP character when there is too much data in the incoming buffer, and send a START character when enough of the data has been read.

IXANY

Allow any received character, not just START, to restart output. (This flag is commonly implemented on Unix systems, but it is not specified by POSIX.)

When robin is being used as a helper program by another program, doing any special character processing (robin usually interprets a control- character specially) may get in the way, so we provide raw mode to sidestep all such processing. On line 120, we provide a variable that determines whether raw mode is enabled; the default is not to enable raw mode. On line 140, we tell popt how to inform us that the -r or --raw option was given on the command line, enabling raw mode.

Some systems require that you send them a carriage-return character to represent a newline. The word systems should be taken broadly here; as an example, this is true of many smart peripherals, such as UPSs, that have serial ports, because they were designed to function in the DOS world, where a carriage return/newline pair is always used to denote a new line. Line 228 allows us to specify this DOS-oriented behavior:

228:     if (crnl) {
229:         /* send CR with NL */
230:         pts.c_oflag |= ONLCR;
231:     }

The final bit of option handling controls the bits per second[5] rate. Rather than include a large nested switch statement here, we call symbolic_speed(), already described, to get a speed_t that termios understands, as shown on line 233.

233:     /* speed is not modified unless -b is specified */
234:     if (speed) {
235:         cfsetospeed(&pts, symbolic_speed(speed));
236:         cfsetispeed(&pts, symbolic_speed(speed));
237:     }

Having determined the actual speed and gotten the symbolic value that represents it, we need to put that speed into the termios structure for the serial port. Since the termios structure supports asynchronous devices that can have different input and output speeds, we need to set both speeds to the same value here.

Before committing the changes we have made in our copies of the termios structures to the devices, on line 241 we register signal handlers for important signals that might otherwise kill us, causing us to leave the ttys in their raw state. For more information on signal handlers, see Chapter 12.

241:     sact.sa_handler = cleanup_termios;
242:     sigaction(SIGHUP, &sact, NULL);
243:     sigaction(SIGINT, &sact, NULL);
244:     sigaction(SIGPIPE, &sact, NULL);
245:     sigaction(SIGTERM, &sact, NULL);

Once the signal handler is in place to restore the old termios settings if robin is killed, we can safely put the new termios settings in place:

248:     tcsetattr(pf, TCSANOW, &pts);
249:     tcsetattr(STDIN_FILENO, TCSANOW, &sts);

Note that we use the TCSANOW option when setting these options, indicating that we want them to take effect immediately. Sometimes, it is appropriate to use other options; they are covered later in the reference section.

At this point, robin is ready to read and write characters. robin has two file descriptors to read from: data coming from the serial port and data coming from the keyboard. We use poll() to multiplex I/O between the four file descriptors, as described on page 245.

The poll() loop makes the simplifying assumption that it can always write as much as it was able to read. This is almost always true and does not cause problems in practice, because blocking for short periods is not noticeable in normal circumstances. The loop never reads from a file descriptor unless poll() has already said that that file descriptor has data waiting to be read, so we know that we do not block while reading.

For data coming from the keyboard, we may need to process escape sequences before writing, if the user did not select raw mode when running robin. Rather than include that code in the middle of this loop, we call cook_buf() (line 78), which calls send_escape() (line 58) when necessary. Both of these functions are simple. The only tricks are that cook_buf() may be called once with the escape character, then a second time with the character to interpret, and that it is optimized to call the write() function as little as is reasonably possible.

cook_buf() calls the send_escape() function once for every character that is preceded by an unescaped control- character. A q character restores the original termios settings and quits by calling the signal handler (with a bogus signal number of 0), which restores the termios settings appropriately before exiting. A b character generates a break condition, which is a long string of continuous 0 bits. Any other character, including a second control- character, is passed through to the serial port verbatim.

If either input file descriptor returns an end-of-file condition, robin exits the poll() loop and falls through to the exit processing, which is the same as the signal handler: It restores the old termios settings on both input file descriptors and exits. In raw mode, the only ways to get robin to quit are to close one of the file descriptors or to send it a signal.

termios Debugging

Debugging tty code is not always easy. The options overlap in meaning and affect each other in various ways, often in ways that you do not intend. But you cannot easily see what is happening with only a debugger, because the processing that you are trying to manage is happening in the kernel.

One effective way to debug code that communicates over a serial port is to use the script program. While developing robin, we connected two computers with a serial cable and verified that the connection worked by running the already-working kermit program. While still running kermit on the local computer, we ran the script program on the remote computer, which started keeping a log of all the characters in the file typescript. Then we quit kermit and ran script on the local computer, which put a typescript file in the current directory on the local computer. We then tried to run robin under script and compared the two typescript files at each end after each run to see the differences in characters. We thereby deciphered the effects of the processing options we had chosen.

Another debugging method takes advantage of the stty program. If, while you are testing a program, you think you recognize a mistake in the termios settings, you can use the stty program to make the change immediately rather than recompile your program first. If you are working on /dev/ttyS0, and you want to set the ECHOCTL flag, simply run the command

stty echoctl < /dev/ttyS0

while your program is running.

Similarly, you can view the current status of the port you are using with

stty -a < /dev/ttyS0

As we explained before, it is hard to use the same tty for running the debugger and for running the tty-mangling program you are debugging.

Instead, you want to attach to the process. This is not difficult to do. In one X-terminal session (do this under X so that you can see both ttys at once), run the program you want to debug. If you need to, put a long sleep() in it at the point at which you wish to attach to it:

$ ./robin -b 38400 /dev/ttyS1

Now, from another X-terminal session, find the pid of the program you are trying to debug, in one of two ways:

$ ps | grep robin
30483  ?  S    0:00 ./robin -b 38400 /dev/ttyS1
30485  ?  S    0:00 grep robin
$ pidof robin
30483

pidof is more convenient, but may not be available on every system. Keep the number you found (here, 30483) in mind and start a debugging session the same way you normally would.

$ gdb robin 30483
GDB is free software...
...
Attaching to program '.../robin', process 30483
Reading symbols from ...
0x40075d88 in sigsuspend ()

From this point, you can set breakpoints and watchpoints, step through the program, or whatever you like.

termios Reference

The termios interface is composed of a structure, a set of functions that operate on it, and a multitude of flags that you can set individually:

#include <termios.h>

struct termios {
    tcflag_t c_iflag;    /* input mode flags */
    tcflag_t c_oflag;    /* output mode flags */
    tcflag_t c_cflag;    /* control mode flags */
    tcflag_t c_lflag;    /* local mode flags */
    cc_t c_line;         /* line discipline */
    cc_t c_cc[NCCS];     /* control characters */
};

The c_line member is used only in very system-specific applications[6] that are beyond the scope of this book. The other five members, however, are relevant in almost all situations that require you to manipulate terminal settings.

Functions

The termios interface defines several functions, all of which are declared in <termios.h>. Four of them are convenience functions for manipulating a struct termios in a portable way; the rest are system calls. Those that start with cf are convenience functions; those functions that start with tc are terminal control system calls. All of the terminal control system calls generate SIGTTOU if the process is currently running in the background and tries to manipulate its controlling terminal (see Chapter 15).

Except as noted, these functions return 0 to indicate success and -1 to indicate failure. The function calls you may use for terminal control are:

int tcgetattr(int fd, struct termios *t);

Retrieves the current settings for the file descriptor fd and places them in the structure to which t points.

int tcsetattr(int fd, int options, struct termios *t);

Sets the current terminal settings for the file descriptor fd to the settings specified in t. Always use tcgetattr() to fill in t, then modify it. Never fill in t by hand: Some systems require that flags beyond the flags specified by POSIX be set or cleared, so filling in t by hand is nonportable.

The options argument determines when the change takes effect.

TCSANOW

The change takes effect immediately.

TCSADRAIN

The change takes effect after all the output that already has been written to fd has been transmitted; it drains the queue before taking effect. You should generally use this when you change output parameters.

TCSAFLUSH

The change takes effect after the output queue has been drained; the input queue is discarded (flushed) before the change takes effect.

If the system cannot handle some settings, such as data rate, it is allowed to ignore those settings silently without returning any error. The only way to see whether the settings were accepted is to use tcgetattr() and compare the contents of the structure it returns to the one you passed to tcsetattr().

Thus, most portable applications use code something like this:

#include <termios.h>

struct termios save;
struct termios set;
struct termios new;
int fd;
...
tcgetattr(fd, &save);
set = save;
cfsetospeed(&set, B2400);
cfsetispeed(&set, B2400);
tcsetattr(fd, &set);
tcgetattr(fd, &new);
if ((cfgetospeed(&set) != B2400) ||
    (cfgetispeed(&set) != B2400)) {
  /* complain */
}

Note that if you do not care if a termios setting sticks, it is fine to ignore the condition, as we do in robin.

speed_t cfgetospeed(struct termios *t);
speed_t cfgetispeed(struct termios *t);

Retrieve the output or input speed, respectively, from t. These functions return a symbolic speed, the same as is given to cfsetospeed() and cfsetispeed().

int cfsetospeed(struct termios *t, speed_t speed);
int cfsetispeed(struct termios *t, speed_t speed);

Set the output or input speed, respectively, in t to speed. Note that this function does not change the speed of the connection on any file descriptor; it merely sets the speed in the termios structure. The speed, like other characteristics, is applied to a file descriptor by tcsetattr().

These functions take a symbolic speed—that is, a number that matches the definition of one of the following macros whose names indicate the bits-per-second rate: B0 (0 bits per second indicates a disconnected state), B50, B75, B110, B134,[7] B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, or B4000000. B57600 and higher are not specified by POSIX; portable source code uses them only if they are protected by #ifdef statements.

More symbolic speeds will be added to the header file as Linux drivers are written for hardware that supports other data rates.

Currently, input speed is ignored. The termios interface specifies separate input and output speeds for some asynchronous hardware that allows split speeds, but little such hardware exists. Just call cfsetospeed() and cfsetispeed() in pairs so that your code will continue to work on systems that support split speeds.

Not all ttys support all rates. In particular, the serial ports on standard PCs do not support over 115,200 bps. As noted above, if you care whether this setting takes effect, you need to use tcgetattr() to check after you attempt to set it with tcsetattr(). Also, note that the rate you set is advisory. Some ttys, like local consoles, cheerfully accept and ignore any rate you give them.

int tcsendbreak(int fd, int duration)

Sends a stream of zero-valued bits on fd for a specified duration, which is known as a break. If duration is 0, the break is at least 250 milliseconds and no more than 500 milliseconds long. Unfortunately, POSIX did not bother to specify the unit in which the duration is measured, so the only portable value for duration is 0. Under Linux, duration multiplies the break; 0 or 1 specify between a quarter second and a half second; 2 specifies between a half second and a second, and so on.

int tcdrain(int fd)

Waits until all currently pending output on the file descriptor fd has been sent.

int tcflush(int fd, int queue_selector)

Discards some data on file descriptor fd, depending on the value of queue_selector:

TCIFLUSH

Flush all data that the interface has received but that has not yet been read.

TCOFLUSH

Flush all data that has been written to the interface that has not yet been sent.

TCIOFLUSH

Flush all pending data, input and output.

int tcflow(int fd, int action)

Suspend or resume output or input on the file descriptor fd. Exactly what to do is determined by action:

TCOOFF

Suspend output.

TCOON

Resume output.

TCIOFF

Send a STOP character, requesting that the other end of the connection stop sending characters.

TCION

Send a START character, requesting that the other end of the connection resume sending characters.

Note that TCIOFF and TCION are only advisory, and that even if the other end of the connection does honor them, there may be a delay before it does so.

Window Sizes

There are two ioctl() requests that, unfortunately, were not codified as part of the termios interface, although they should have been. The size of the tty, measured in rows and columns, ought to be managed with tcgetwinsize() and tcsetwinsize(), but as they do not exist, you must use ioctl() instead. For both requesting the current size and setting a new size, use a struct winsize:

#include <termios.h>

struct winsize {
    unsigned short ws_row;      /* number of rows */
    unsigned short ws_col;      /* number of columns */
    unsigned short ws_xpixel;   /* unused */
    unsigned short ws_ypixel;   /* unused */
};

To request the current size, use

struct winsize ws;

 ioctl(fd, TIOCGWINSZ, &ws);

To set a new size, fill in a struct winsize and call

ioctl(fd, TIOCSWINSZ, &ws);

See page 389 for an example of the conditions in which you would want to set a new window size.

When the window size changes, the signal SIGWINCH is sent to the foreground process group leader on that tty. Your code can catch this signal; use TIOCGWINSZ to query the new size and make any appropriate changes within your program.

Flags

The four flag variables, c_iflag, c_oflag, c_cflag, and c_lflag, hold flags that control various characteristics. The <termios.h> header file provides symbolic constant bitmasks that represent those flags. Set them with |= and unset them with &= ~, like this:

t.c_iflag |= BRKINT;
t.c_iflag &= ~IGNBRK;

A few of these symbolic defines are actually bitmasks that cover several related constants. They are used to extract parts of the structure for comparison:

if ((t.c_cflag & CSIZE) == CS7)
  character_size = 7;

The set of flags differs from system to system. The most important flags are specified by POSIX, but Linux follows System V in including several useful flags that POSIX does not define. This documentation is not complete; Linux supports some flags that you will probably never need. We cover the flags that you might possibly have a reason to use and refrain from confusing you with the rest.

In order to enable you to write portable software, we have labelled every flag that is not specified by the POSIX standard. For those flags, you should write code like this:

#ifdef IUCLC
    t.c_iflag |= IUCLC;
#endif

Also, some areas that present particular portability problems are mentioned, and we even break our rule of not confusing you with details from other implementations by presenting a few details about what other systems do.

Input Flags

The input mode flags affect input processing even though they sometimes have an effect on output. The flags that operate on c_iflag are as follows:

BRKINT and IGNBRK

If IGNBRK is set, break conditions (see tcsendbreak(), mentioned earlier) are ignored.

 

If IGNBRK is not set and BRKINT is set, break conditions cause the tty to flush all queued input and output data and send a SIGINT to processes in the foreground process group for the tty.

 

If IGNBRK is not set and BRKINT is not set, break conditions are read as a zero-valued character (''), except if PARMRK is set, in which case a framing error is detected and the three bytes '377' '' '' are delivered to the application instead.

PARMRK and IGNPAR

If IGNPAR is set, received bytes containing parity or framing errors are ignored (except as specified for break conditions earlier).

 

If IGNPAR is not set and PARMRK is set, a received byte with a parity or framing error is reported to the application as the three-byte sequence '377' '' 'n', where n is the byte as it was received. In this case, if ISTRIP is not set, a valid '377' character is reported to the application as the two-character sequence '377' '377'; if ISTRIP is set, a '377' character has its high bit stripped and is reported as '177'.

 

If neither PARMRK nor IGNPAR is set, a received byte with a parity or framing error other than a break condition is reported to the application as a single '' character.

INPCK

If INPCK is set, parity checking is enabled. If it is not enabled, PARMRK and IGNPAR have no effect on received parity errors.

ISTRIP

If ISTRIP is set, the high-order bit is stripped from all received bytes, limiting them to seven bits.

INLCR

If INLCR is set, received newline (' ')characters are translated to carriage-return (' ') characters.

IGNCR

If IGNCR is set, received carriage returns (' ') are ignored (not reported to the application).

ICRNL

If ICRNL is set and IGNCR is not set, received carriage-return (' ') characters are reported to the application as newline (' ') characters.

IUCLC

If IUCLC and IEXTEN are set, received uppercase characters are reported to the application as lowercase characters. This flag is not specified by POSIX.

IXOFF

If IXOFF is set, the tty may send Control-S and Control-Q characters to the terminal to request that it stop and resume output (that is, sending data to the computer), respectively, to avoid overflowing the tty’s input buffers. This is relevant only to serial terminals, as network and local terminals have more direct forms of flow control. Even serial terminals often have hardware flow control, which is controlled by the control flag (c_cflag) and which makes software flow control (Control-S and Control-Q) irrelevant.

IXON

If IXON is set, a received Control-S character stops output to this tty and a received Control-Q character restarts output to this tty. This is relevant to any form of terminal I/O, as some users type literal Control-S and Control-Q characters to suspend and resume output.

IXANY

If IXANY is set, any received character (not just Control-Q) restarts output. This flag is not specified by POSIX.

IMAXBEL

If IMAXBEL is set, the alert ('a') character is sent whenever a character is received and the input buffer is already full. This flag is not specified by POSIX.

Output Flags

The output mode flags modify output processing only if OPOST is set. None of these flags are portable, because POSIX defines only OPOST and calls it “implementation defined.” However, you will find that real terminal-handling applications often do need output processing, and the output flags available under Linux are generally available on most Unix systems, including SVR4.

The terminal code keeps track of the current column, which allows it to suppress extra carriage-return characters (' ') and to convert tabs to spaces when appropriate. This current column is zero-based; the first column is column zero. The current column is set to zero whenever a carriage return (' ')is sent or implied, as it may be by a newline character (' ') when ONLRET or ONLCR is set, or when the current column is set to one and a backspace character ('') is sent.

The flags that operate on c_oflag are as follows:

OPOST

This is the only output flag specified by POSIX, which says that it turns on “implementation-defined” output processing. If OPOST is not set, none of the other output mode flags are consulted and no output processing is done.

OLCUC

If OLCUC is set, lowercase characters are sent to the terminal as uppercase characters. This flag is not specified by POSIX.

ONLCR

If ONLCR is set, when a newline character (' ') is sent, a carriage return (' ') is sent before the newline. The current column is set to zero. This flag is not specified by POSIX.

ONOCR

If ONOCR is set, carriage-return characters (' ') are neither processed nor sent if the current column is zero. This flag is not specified by POSIX.

OCRNL

If OCRNL is set, carriage-return characters (' ') are translated into newline characters (' '). If, in addition, ONLRET is set, the current column is set to zero. This flag is not specified by POSIX.

ONLRET

If ONLRET is set, when a newline character (' ') or carriage-return character (' ')is sent, the current column is set to zero. This flag is not specified by POSIX.

OXTABS

If OXTABS is set, tabs are expanded to spaces. Tab stops are assumed to be every eight characters, and the number of space characters that are sent is determined by the current column. This flag is not specified by POSIX.

In addition, there are delay flags that you never need to set; they are designed to compensate for old, badly designed, and, by now, mercifully rare hardware. The termcap and terminfo libraries are responsible for managing delay flags, which means that you should never have to modify them. termcap & terminfo [Strang, 1991B], which documents them, describes them as obsolete. The Linux kernel does not currently implement them, and as there has been no demand for this feature, they likely will never be implemented.

Control Flags

The control mode flags affect protocol parameters, such as parity and flow control.[8] The flags that operate on c_cflag are as follows:

CLOCAL

If CLOCAL is set, modem control lines are ignored. If it is not set, then open() will block until the modem announces an off-hook condition by asserting the carrier-detect line.

CREAD

Characters can be received only if CREAD is set. It cannot necessarily be unset.[9]

CSIZE

CSIZE is a mask for the codes that set the size of a transmitted character in bits. The character size should be set to:CS5 for five bits per characterCS6 for six bits per characterCS7 for seven bits per characterCS8 for eight bits per character

CSTOPB

If CSTOPB is set, two stop bits are generated at the end of each transmitted character frame. If CSTOPB is not set, only one stop bit is generated. Obsolete equipment that requires two stop bits is rare.

HUPCL

If HUPCL is set, then when the final open file descriptor on the device is closed, the DTR and RTS lines on the serial port (if they exist) will be lowered to signal the modem to hang up. This means, for example, that when a user who logged in through a modem then logs out, the modem will be hung up. If a communications program has the device open for outbound calls and the process then closes the device (or exits), the modem will be hung up.

PARENB and PARODD

If PARENB is set, a parity bit is generated. If PARODD is not set, even parity is generated. If PARODD is set, odd parity is generated.

 

If PARENB is not set, PARODD is ignored.

CRTSCTS

Use hardware flow control (RTS and CTS lines). At high speeds (19,200 bps and higher) software flow control via the XON and XOFF characters becomes ineffective and hardware flow control must be used instead.

 

This flag is not specified by POSIX and is not available by this name on most other Unix systems. This is a particularly nonportable area of terminal control, despite the common need for hardware flow control on modern systems. SVR4 is particularly egregious in that it provides no way to enable hardware flow control through termios, only through a different interface called termiox.

[9] Try running stty -cread!

Control Characters

Control characters are characters that have special meanings that may differ depending on whether the terminal is in canonical input mode or raw input mode and on the settings of various control flags. Each offset (except for VMIN and VTIME) in the c_cc array designates an action and holds the character code that is assigned that action. For example, set the interrupt character to Control-C with code like this:

ts.c_cc[VINTR] = CTRLCHAR('C'),

CTRLCHAR() is a macro defined as

#define CTRLCHAR(ch) ((ch)&0x1F)

Some systems have a CTRL() macro defined in <termios.h>, but it is not defined on all systems, so defining our own version is more portable.

We use the ^C notation to designate Control-C.

The character positions that are not specified by POSIX are active only if the IEXTEN local control (c_lflag) flag is set.

The control characters that you can use as subscripts to the c_cc array are:

VINTR

Offset VINTR is usually set to ^C. It normally flushes the input and output queues and sends SIGINT to the members of the foreground process group associated with the tty. Processes that do not explicitly handle SIGINT will exit immediately.

VQUIT

Offset VQUIT is usually set to ^. It normally flushes the input and output queues and sends SIGQUIT to the members of the foreground process group associated with the tty. Processes that do not explicitly handle SIGQUIT will abort, dumping core if possible (see page 120).

VERASE

Offset VERASE is usually set to ^H or ^?. In canonical mode, it normally erases the previous character on the line. In raw mode, it is meaningless.

VKILL

Offset VKILL is usually set to ^U. In canonical mode, it normally erases the entire line. In raw mode, it is meaningless.

VEOF

Offset VEOF is usually set to ^D. In canonical mode, it causes read() on that file descriptor to return 0, signaling an end-of-file condition. On some systems, it may share space with the VMIN character, which is active only in raw mode. (This is not an issue if you save a struct termios with canonical mode settings to restore once you are done with raw mode, which is proper termios programming practice, anyway.)

VSTOP

Offset VSTOP is usually set to ^S. It causes the tty to pause output until the VSTART character is received, or, if IXANY is set, until any character is received.

VSTART

Offset VSTART is usually set to ^Q. It restarts paused tty output.

VSUSP

Offset VSUSP is usually set to ^Z. It causes the current foreground process group to be sent SIGTSTP; see Chapter 15 for details.

VEOL and VEOL2

In canonical mode, these characters, in addition to the newline character (' '), signal an end-of-line condition. This causes the collected buffer to be transmitted and a new buffer started. On some systems, VEOL may share space with the VTIME character, which is active only in raw mode, just as VEOF may share space with VMIN. The VEOL2 character is not specified by POSIX.

VREPRINT

Offset VREPRINT is usually set to ^R. In canonical mode, if the ECHO local flag is set, it causes the VREPRINT character to be echoed locally, a newline (and a carriage return, if appropriate) to be echoed locally, and the whole current buffer to be reprinted. This character is not specified by POSIX.

VWERASE

Offset VWERASE is usually set to ^W. In canonical mode, it erases any white space at the end of the buffer, then all adjacent non-white-space characters, which has the effect of erasing the previous word on the line. This character is not specified by POSIX.

VLNEXT

Offset VLNEXT is usually set to ^V. It is not itself entered into the buffer, but it causes the next character input to be put into the buffer literally, even if it is one of the control characters. Of course, to enter a single literal VLNEXT character, type it twice. This character is not specified by POSIX.

To disable any control character position, set its value to _POSIX_VDISABLE. This only works if _POSIX_VDISABLE is defined, and is defined as something other than -1. _POSIX_VDISABLE works on Linux, but a portable program will, unfortunately, not be able to depend on disabling control character positions on all systems.

Local Flags

The local mode flags affect local processing, which (roughly) refers to how characters are collected before they are output. When the device is in canonical (cooked) mode, characters are echoed locally without being sent to the remote system until a newline character is encountered. At that point, the whole line is sent, and the remote end processes it without echoing it again. In raw mode, each character is sent to the remote system as it is received. Sometimes the character is echoed only by the remote system; sometimes only by the local system; and sometimes, such as when reading a password, it is not echoed at all.

Some flags may act differently, depending on whether the terminal is in canonical mode or raw mode. Those that act differently in canonical and raw mode are marked.

The flags that operate on c_lflag are as follows:

ICANON

If ICANON is set, canonical mode is enabled. If ICANON is not set, raw mode is enabled.

ECHO

If ECHO is set, local echo is enabled. If ECHO is not set, all the other flags whose names start with ECHO are effectively disabled and function as if they are not set, except for ECHONL.

ECHOCTL

If ECHOCTL is set, control characters are printed as ^C, where C is the character formed by adding octal 0100 to the control character, mod octal 0200. So Control-C is displayed as ^C, and Control-? (octal 0177) is represented as ^? (? is octal 77). This flag is not specified by POSIX.

ECHOE

In canonical mode, if ECHOE is set, then when the ERASE character is received, the previous character on the display is erased if possible.

ECHOK and ECHOKE

In canonical mode, when the KILL character is received, the entire current line is erased from the buffer.

 

If neither ECHOK, ECHOKE, nor ECHOE is set, the ECHOCTL representation of the KILL character (^U by default) is printed to indicate that the line has been erased.

 

If ECHOE and ECHOK are set but ECHOKE is not set, the ECHOCTL representation of the KILL character is printed, followed by a newline, which is then processed appropriately by OPOST handling if OPOST is set.

 

If ECHOE, ECHOK, and ECHOKE are all set, the line is erased.

 

See the description of ECHOPRT for another variation on this theme.

 

The ECHOKE flag is not specified by POSIX. On systems without the ECHOKE flag, setting the ECHOK flag may be equivalent to setting both the ECHOK and ECHOKE flags under Linux.

ECHONL

In canonical mode, if ECHONL is set, newline (' ') characters are echoed even if ECHO is not set.

ECHOPRT

In canonical mode, if ECHOPRT is set, characters are printed as they are erased when the ERASE or WERASE (or KILL, if ECHOK and ECHOKE are set) characters are received. When the first erase character in a sequence is received, a is printed, and when the final erased character is printed (the end of the line is reached or a nonerasing character is typed), a / is printed. Every normal character you type is merely echoed. So typing asdf, followed by two ERASE characters, followed by df, followed by a KILL character, would look like asdffd/dffdsa/

 

This is useful for debugging and for using hardcopy terminals, such as the original teletype, where the characters are printed on paper, and is otherwise useless. This flag is not specified by POSIX.

ISIG

If ISIG is set, the INTR, QUIT, and SUSP control characters cause the corresponding signal (SIGINT, SIGQUIT, or SIGTSTP, respectively; see Chapter 12) to be sent to all the processes in the current foreground process group on that tty.

NOFLSH

Usually, when the INTR and QUIT characters are received, the input and output queues are flushed. When the SUSP character is received, only the input queue is flushed. If NOFLSH is set, neither queue is flushed.

TOSTOP

If TOSTOP is set, then when a process that is not in the current foreground process group attempts to write to its controlling terminal, SIGTTOU is sent to the entire process group of which the process is a member. By default, this signal stops a process, as if the SUSP character had been pressed.

IEXTEN

This flag is specified as implementation-dependent by POSIX. It enables implementation-defined processing of input characters. Although portable programs do not set this bit, the IUCLC and certain character-erasing facilities in Linux depend on it being set. Fortunately, it is generally enabled by default on Linux systems, because the kernel initially enables it when setting up ttys, so you should not normally need to set it for any reason.

Controlling read()

Two elements in the c_cc array are not control characters and are relevant only in raw mode: VTIME and VMIN. In raw mode only, these determine when read() returns. In canonical mode, read() returns only when lines have been assembled or end-of-file is reached, unless the O_NONBLOCK option is set.

In raw mode, it would not be efficient to read one byte at a time, and it is also inefficient to poll the port by reading in nonblocking mode. This leaves two complementary methods of reading efficiently.

The first is to use poll(), as documented in Chapter 13 and demonstrated in robin.c. If poll() says that a file descriptor is ready to read, you know that you can read() some number of bytes immediately. However, combining poll() with the second method can make your code even more efficient by making it possible to read more bytes at a time.

The VTIME and VMIN “control characters” have a complex relationship. VTIME specifies an amount of time to wait in tenths of seconds (which cannot be larger than a cc_t, usually an 8-bit unsigned char), which may be zero. VMIN specifies the minimum number of bytes to wait for (not to read—read()’s third argument specifies the maximum number of bytes to read), which may also be zero.

  • If VTIME is zero, VMIN specifies the number of bytes to wait for. A read() call does not return until at least VMIN bytes have been read or a signal has been received.

  • If VMIN is zero, VTIME specifies the number of tenths of seconds for read() to wait before returning, even if no data is available. In this case, read() returning zero does not necessarily indicate an end-of-file condition, as it usually does.

  • If neither VTIME nor VMIN is zero, VTIME specifies the number of tenths of seconds for read() to wait after at least one byte is available. If data is available when read() is called, a timer starts immediately. If data is not available when read() is called, a timer is started when the first byte arrives. The read() call returns either when at least VMIN bytes have arrived or when the timer expires, whichever comes first. It always returns at least one byte because the timer does not start until at least one byte is available.

  • If VTIME and VMIN are both zero, read() always returns immediately, even if no data is available. Again, zero does not necessarily indicate an end-of-file condition.

Pseudo ttys

A pseudo tty, or pty, is a mechanism that allows a user-level program to take the place (logically speaking) of a tty driver for a piece of hardware. The pty has two distinct ends: The end that emulates hardware is called the pty master, and the end that provides programs with a normal tty interface is called the pty slave. The slave looks like a normal tty; the master is like a standard character device and is not a tty.

A serial port driver is generally implemented as an interrupt-driven piece of code in the kernel, to which programs talk through a specific device file. That does not have to be the case, however. For example, at least one SCSI-based terminal server exists that uses a generic interface to the SCSI protocol to have a user-level program that talks to the terminal server and provides access to the serial ports via ptys.

Network terminal sessions are done in the same manner; the rlogind and telnetd programs connect a network socket to a pty master and run a shell on a pty slave to make network connections act like ttys, allowing you to run interactive programs over a non-tty network connection. The screen program multiplexes several pty connections onto one tty, which may or may not itself be a pty, connected to the user. The expect program allows programs that insist on being run in interactive mode on a tty to be run on a pty slave under the control of another program connected to a pty master.

Opening Pseudo ttys

There are broad categories of ways to open pseudo ttys: The way everyone actually does it (in Linux, at least), the more or less standard-compliant way based on SysV, and the mostly abandoned way based on old BSD practice. The most common method among Linux system programmers is a set of BSD extensions that also have been implemented as part of glibc. The less common method is documented as part of the 1998 Unix98 standard, and documented differently in the 2000 revision of the Unix98 standard.

Historically, there have been two different methods of opening pseudo ttys on Unix and Unix-like systems. Linux originally followed the BSD model, even though it is more complex to use, because the SysV model is explicitly written in terms of STREAMS, and Linux does not implement STREAMS. The BSD model, however, requires that each application search for an unused pty master by knowing about many specific device names. Between 64 and 256 pty devices are normally available, and to find the first open device, programs search through the devices in order by minor number. They do this by searching in the peculiar lexicographic manner demonstrated in the ptypair program included in this section.

The BSD model presents several problems:

  • Each application must know the entire space of available names. When the set of possible pseudo ttys is expanded, each application that uses pseudo ttys must be modified with explicit knowledge of all possible device names. This is inconvenient and error-prone.

  • The time taken to search becomes measurable when you are searching through thousands of device nodes in a well-populated /dev directory, wasting system time and slowing down access to the system, so this scales very poorly to large systems.

  • Permission handing can be problematic. For example, if a program terminates abnormally, it can leave pseudo tty device files with inappropriate permissions.

Because the SysV model is explicitly written in terms of STREAMS, and requires using STREAMS ioctl() calls to set up the slave devices, the SysV model was not really an option for Linux. However, the Unix98 interface does not specify the STREAMS-specific functionality, and in 1998 Linux added support for Unix98-style pseudo ttys.

The Linux kernel can be compiled without support for the Unix98 interface, and you may encounter older systems without Unix98-style pseudo ttys, so we present code that attempts to open Unix98-style pseudo ttys, but is also able to fall back to the BSD interface. (We do not document the STREAMS-specific parts of the SysV model; see [Stevens, 1992] for details on the STREAMS interface specifics. You should not normally need the STREAMS-specific code; the Unix98 specification does not require it.)

Opening Pseudo ttys the Easy Ways

In the libutil library, glibc provides openpty() and forkpty(), two functions which do nearly all the work of pseudo tty handling for you.

#include <pty.h>

int openpty(int *masterfd, int *slavefd, char *name,
            struct termios *term, struct winsize *winp);
int forkpty(int *masterfd, char *name,
            struct termios *term, struct winsize *winp);

openpty() opens a set of master and slave pseudo ttys, and optionally uses struct termios and struct winsize structures passed in as options to set up the slave pseudo tty, returning 0 to indicate success and -1 to indicate failure. The master and slave file descriptors are passed back in the masterfd and slavefd arguments, respectively. The term and winp arguments may be NULL, in which case they are ignored and no setup is done.

forkpty() works the same way as openpty(), but instead of returning the slave file descriptor, it forks and sets up the slave pseudo tty as the controlling terminal on stdin, stdout, and stderr for the child process, and then, like fork(), returns the pid of the child in the parent and 0 in the child, or -1 to indicate failure.

Even these convenient interfaces have a major problem: the name argument was originally intended to pass the name of the pseudo tty device back to the calling code, but it cannot be used in a safe fashion, because openpty() and forkpty() have no way of knowing how large the buffer is. Always pass NULL as the name argument. Use the ttyname() function, described on page 337, to get the pathname of the pseudo tty device file.

The normally preferred way of working with struct termios is to use a read-modify-write cycle, but that does not apply here, for two reasons: You can pass NULL and take the default values, which should be sufficient for most cases; and when you do want to provide termios settings, you often are borrowing settings from another tty, or know exactly what the settings should be for some other reason (for example, the SCSI serial port concentrator described earlier in this chapter). Ignoring errors for clarity:

tcgetattr(STDIN_FILENO, &term);
ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
pid = forkpty(&masterfd, NULL, &term, &ws);

Opening Pseudo ttys the Hard Ways

The Unix98 interface for allocating a pseudo tty pair is a set of functions:

#define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <fcntl.h>

int posix_openpt(int oflag);
int grantpt(int fildes);
int unlockpt(int fildes);
char *ptsname(int fildes);

The posix_openpt() function is the same as opening the /dev/ptmx device but is theoretically more portable (once it is accepted everywhere). We recommend using open(“/dev/ptmx", oflag) at this time for maximum practical portability. You want one or two open() or posix_openpt() flags: Use O_RDWR normally; if you are not opening the controlling tty for the process, instead use O_RDWR|O_NOCTTY.open() or posix_openpt() returns an open file descriptor for the master pseudo tty. You then call grantpt() with the master pseudo tty file descriptor returned from posix_openpt() to change the mode and ownership of the slave pseudo tty, and then unlockpt() to make the slave pseudo tty available to be opened. The Unix98 interface for opening the slave pseudo tty is simply to open the name returned by ptsname(). These functions all return -1 on error, except for ptsname(), which returns NULL on error.

The functions in ptypair.c allocate a matched pair of pty devices. The example get_master_pty() function on line 22 of ptypair.c opens a master pty and returns the file descriptor to the parent, and also provides the name of the corresponding slave pty to open. It first tries the Unix98 interface for allocating a master pty, and if that does not work (for example, if the kernel has been compiled without Unix98 pty support, a possibility for embedded systems), it falls back to the old BSD-style interface. The corresponding get_slave_pty() function on line 87 can be used after a fork() to open the corresponding slave pty device:

  1: /* ptypair.c */
  2:
  3: #define _XOPEN_SOURCE 600
  4: #include <errno.h>
  5: #include <fcntl.h>
  6: #include <grp.h>
  7: #include <stdlib.h>
  8: #include <string.h>
  9: #include <sys/types.h>
 10: #include <sys/stat.h>
 11: #include <unistd.h>
 12:
 13:
 14: /* get_master_pty() takes a double-indirect character pointer in
 15:  * which to put a slave name, and returns an integer file
 16:  * descriptor. If it returns < 0, an error has occurred. Otherwise,
 17:  * it has returned the master pty file descriptor, and fills in
 18:  * *name with the name of the corresponding slave pty. Once the
 19:  * slave pty has been opened, you are responsible to free *name.
 20:  */
 21:
 22: int get_master_pty(char **name) {
 23:     int i, j;
 24:     /* default to returning error */
 25:     int master = -1;
 26:     char *slavename;
 27:
 28:     master = open("/dev/ptmx", O_RDWR);
 29:     /* This is equivalent to, though more widely implemented but
 30:      * theoretically less portable than:
 31:      * master = posix_openpt(O_RDWR);
 32:      */
 33:
 34:     if (master >= 0 && grantpt (master) >= 0 &&
 35:                        unlockpt (master) >= 0) {
 36:         slavename = ptsname(master);
 37:         if (!slavename) {
 38:             close(master);
 39:             master = -1;
 40:             /* fall through to fallback */
 41:         } else {
 42:             *name = strdup(slavename);
 43:             return master;
 44:         }
 45:     }
 46:
 47:     /* The rest of this function is a fallback for older systems */
 48:
 49:     /* create a dummy name to fill in */
 50:     *name = strdup("/dev/ptyXX");
 51:
 52:     /* search for an unused pty */
 53:     for (i=0; i<16 && master <= 0; i++) {
 54:         for (j=0; j<16 && master <= 0; j++) {
 55:             (*name)[8] = "pqrstuvwxyzPQRST"[i];
 56:             (*name)[9] = "0123456789abcdef"[j];
 57:             /* open the master pty */
 58:             if ((master = open(*name, O_RDWR)) < 0) {
 59:                 if (errno == ENOENT) {
 60:                     /* we are out of pty devices */
 61:                     free (*name);
 62:                     return (master);
 63:                 }
 64:             }
 65:         }
 66:     }
 67:
 68:     if ((master < 0) && (i == 16) && (j == 16)) {
 69:         /* must have tried every pty unsuccessfully */
 70:         free (*name);
 71:         return (master);
 72:     }
 73:
 74:     /* By substituting a letter, we change the master pty
 75:      * name into the slave pty name.
 76:      */
 77:     (*name)[5] = 't';
 78:
 79:     return (master);
 80: }
 81:
 82: /* get_slave_pty() returns an integer file descriptor.
 83:  * If it returns < 0, an error has occurred.
 84:  * Otherwise, it has returned the slave file descriptor.
 85:  */
 86:
 87: int get_slave_pty(char *name) {
 88:     struct group *gptr;
 89:     gid_t gid;
 90:     int slave = -1;
 91:
 92:     if (strcmp(name, "/dev/pts/")) {
 93:         /* The Unix98 interface has not been used, special
 94:          * permission or ownership handling is necessary.
 95:          *
 96:          * chown/chmod the corresponding pty, if possible.
 97:          * This will work only if the process has root permissions.
 98:          * Alternatively, write and exec a small setuid program
 99:          * that does just this.
100:          *
101:          * Alternatively, just ignore this and use only the Unix98
102:          * interface.
103:          */
104:         if ((gptr = getgrnam("tty")) != 0) {
105:             gid = gptr->gr_gid;
106:         } else {
107:             /* if the tty group does not exist, don't change the
108:              * group on the slave pty, only the owner
109:              */
110:             gid = -1;
111:         }
112:
113:         /* Note that we do not check for errors here.  If this is
114:          * code where these actions are critical, check for errors!
115:          */
116:         chown(name, getuid(), gid);
117:
118:         /* This code makes the slave read/writeable only for the
119:          * user. If this is for an interactive shell that will
120:          * want to receive "write" and "wall" messages, OR S_IWGRP
121:          * into the second argument below.  In that case, you will
122:          * want to move this line outside the if() clause so that
123:          * it is run for * both BSD-style and Unix98-style
124:          * interfaces.
125:          */
126:         chmod(name, S_IRUSR|S_IWUSR);
127:     }
128:
129:     /* open the corresponding slave pty */
130:     slave = open(name, O_RDWR);
131:
132:     return (slave);
133: }

The get_slave_pty() function does nothing new. Every function in it is described elsewhere in this book, so we do not explain it here.

Pseudo tty Examples

Perhaps one of the simplest programs that can be written to use ptys is a program that opens a pty pair and runs a shell on the slave pty, connecting it to the master pty. Having written that program, you can expand it in any way that you wish. forkptytest.c is an example using the forkpty() function; ptytest.c is an example that uses the functions defined in ptypair.c, and it is by necessity more complicated.

  1: /* forkptytest.c */
  2:
  3: #include <errno.h>
  4: #include <signal.h>
  5: #include <stdio.h>
  6: #include <stdlib.h>
  7: #include <sys/ioctl.h>
  8: #include <sys/poll.h>
  9: #include <termios.h>
 10: #include <unistd.h>
 11: #include <pty.h>
 12:
 13:
 14: volatile int propagate_sigwinch = 0;
 15:
 16: /* sigwinch_handler
 17:  * propagate window size changes from input file descriptor to
 18:  * master side of pty.
 19:  */
 20: void sigwinch_handler(int signal) {
 21:     propagate_sigwinch = 1;
 22: }
 23:
 24:
 25: /* forkptytest tries to open a pty pair with a shell running
 26:  * underneath the slave pty.
 27:  */
 28: int main (void) {
 29:     int master;
 30:     int pid;
 31:     struct pollfd ufds[2];
 32:     int i;
 33: #define BUFSIZE 1024
 34:     char buf[1024];
 35:     struct termios ot, t;
 36:     struct winsize ws;
 37:     int done = 0;
 38:     struct sigaction act;
 39:
 40:     if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
 41::        perror("ptypair: could not get window size");
 42:         exit(1);
 43:     }
 44:
 45:     if ((pid = forkpty(&master, NULL, NULL, &ws)) < 0) {
 46:         perror("ptypair");
 47:         exit(1);
 48:     }
 49:
 50:     if (pid == 0) {
 51:         /* start the shell */
 52:         execl("/bin/sh", "/bin/sh", 0);
 53:
 54:         /* should never be reached */
 55:         exit(1);
 56:     }
 57:
 58:     /* parent */
 59:     /* set up SIGWINCH handler */
 60:     act.sa_handler = sigwinch_handler;
 61:     sigemptyset(&(act.sa_mask));
 62:     act.sa_flags = 0;
 63:     if (sigaction(SIGWINCH, &act, NULL) < 0) {
 64:         perror("ptypair: could not handle SIGWINCH ");
 65:         exit(1);
 66:     }
 67:
 68:     /* Note that we only set termios settings for standard input;
 69:      * the master side of a pty is NOT a tty.
 70:      */
 71:     tcgetattr(STDIN_FILENO, &ot);
 72:     t = ot;
 73:     t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | 
 74:                    ECHOK | ECHOKE | ECHONL | ECHOPRT );
 75:     t.c_iflag |= IGNBRK;
 76:     t.c_cc[VMIN] = 1;
 77:     t.c_cc[VTIME] = 0;
 78:     tcsetattr(STDIN_FILENO, TCSANOW, &t);
 79:
 80:     /* This code comes nearly verbatim from robin.c
 81:      * If the child exits, reading master will return -1
 82:      * and we exit.
 83:      */
 84:     ufds[0].fd = STDIN_FILENO;
 85:     ufds[0].events = POLLIN;
 86:     ufds[1].fd = master;
 87:     ufds[1].events = POLLIN;
 88:
 89:     do {
 90:         int r;
 91:
 92:         r = poll(ufds, 2, -1);
 93:         if ((r < 0) && (errno != EINTR)) {
 94:             done = 1;
 95:             break;
 96:         }
 97:
 98:         /* First check for an opportunity to exit */
 99:         if ((ufds[0].revents | ufds[1].revents) &
100:             (POLLERR | POLLHUP | POLLNVAL)) {
101:             done = 1;
102:             break;
103:         }
104:
105:         if (propagate_sigwinch) {
106:             /* signal handler has asked for SIGWINCH propagation */
107:             if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
108:                 perror("ptypair: could not get window size");
109:             }
110:             if (ioctl(master, TIOCSWINSZ, &ws) < 0) {
111:                 perror("could not restore window size");
112:             }
113:
114:             /* now do not do this again until next SIGWINCH */
115:             propagate_sigwinch = 0;
116:
117:             /* poll may have been interrupted by SIGWINCH,
118:              * so try again.
119:              */
120:             continue;
121:         }
122:
123:         if (ufds[1].revents & POLLIN) {
124:             i = read(master, buf, BUFSIZE);
125:             if (i >= 1) {
126:                 write(STDOUT_FILENO, buf, i);
127:             } else {
128:                 done = 1;
129:             }
130:         }
131:
132:         if (ufds[0].revents & POLLIN) {
133:             i = read(STDIN_FILENO, buf, BUFSIZE);
134:             if (i >= 1) {
135:                 write(master, buf, i);
136:             } else {
137:                 done = 1;
138:             }
139:         }
140:
141:     } while (!done);
142:
143:     tcsetattr(STDIN_FILENO, TCSANOW, &ot);
144:     exit(0);
145: }

forkptytest.c does very little that we have not seen before. Signal handling is described in Chapter 12, and the poll() loop is almost exactly straight from robin.c on page 355 (minus the escape-character processing), as is the code modifying termios settings.

This leaves propagating window-size changes to be explained here.

On line 105, after poll() exits, we check to see if the reason that poll() exited was the SIGWINCH signal being delivered to the sigwinch_handler function on line 20. If it was, we need to get the new current window size from standard input and propagate it to the slave’s pty. By setting the window size, SIGWINCH is sent automatically to the process running on the pty; we should not explicitly send a SIGWINCH to that process.

Now, by contrast, see how much more complicated this code is when you have to use the functions we have defined in ptypair.c:

  1: /* ptytest.c */
  2:
  3: #include <errno.h>
  4: #include <fcntl.h>
  5: #include <signal.h>
  6: #include <stdio.h>
  7: #include <stdlib.h>
  8: #include <string.h>
  9: #include <sys/ioctl.h>
 10: #include <sys/poll.h>
 11: #include <sys/stat.h>
 12: #include <termios.h>
 13: #include <unistd.h>
 14: #include "ptypair.h"
 15:
 16:
 17: volatile int propagate_sigwinch = 0;
 18:
 19: /* sigwinch_handler
 20:  * propagate window size changes from input file descriptor to
 21:  * master side of pty.
 22:  */
 23: void sigwinch_handler(int signal) {
 24:     propagate_sigwinch = 1;
 25: }
 26:
 27:
 28: /* ptytest tries to open a pty pair with a shell running
 29:  * underneath the slave pty.
 30:  */
 31: int main (void) {
 32:     int master;
 33:     int pid;
 34:     char *name;
 35:     struct pollfd ufds[2];
 36:     int i;
 37: #define BUFSIZE 1024
 38:     char buf[1024];
 39:     struct termios ot, t;
 40:     struct winsize ws;
 41:     int done = 0;
 42:     struct sigaction act;
 43:
 44:     if ((master = get_master_pty(&name)) < 0) {
 45:         perror("ptypair: could not open master pty");
 46:         exit(1);
 47:     }
 48:
 49:     /* set up SIGWINCH handler */
 50:     act.sa_handler = sigwinch_handler;
 51:     sigemptyset(&(act.sa_mask));
 52:     act.sa_flags = 0;
 53:     if (sigaction(SIGWINCH, &act, NULL) < 0) {
 54:         perror("ptypair: could not handle SIGWINCH ");
 55:         exit(1);
 56:     }
 57:
 58:     if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
 59:         perror("ptypair: could not get window size");
 60:         exit(1);
 61:     }
 62:
 63:     if ((pid = fork()) < 0) {
 64:         perror("ptypair");
 65:         exit(1);
 66:     }
 67:
 68:     if (pid == 0) {
 69:         int slave; /* file descriptor for slave pty */
 70:
 71:         /* We are in the child process */
 72:         close(master);
 73:
 74:         if ((slave = get_slave_pty(name)) < 0) {
 75:             perror("ptypair: could not open slave pty");
 76:             exit(1);
 77:         }
 78:         free(name);
 79:
 80:         /* We need to make this process a session group leader,
 81:          * because it is on a new PTY, and things like job control
 82:          * simply will not work correctly unless there is a session
 83:          * group leader and process group leader (which a session
 84:          * group leader automatically is). This also disassociates
 85:          * us from our old controlling tty.
 86:          */
 87:         if (setsid() < 0) {
 88:             perror("could not set session leader");
 89:         }
 90:
 91:         /* Tie us to our new controlling tty. */
 92:         if (ioctl(slave, TIOCSCTTY, NULL)) {
 93:             perror("could not set new controlling tty");
 94:         }
 95:
 96:         /* make slave pty be standard in, out, and error */
 97:         dup2(slave, STDIN_FILENO);
 98:         dup2(slave, STDOUT_FILENO);
 99:         dup2(slave, STDERR_FILENO);
100:
101:         /* at this point the slave pty should be standard input */
102:         if (slave > 2) {
103:             close(slave);
104:         }
105:
106:         /* Try to restore window size; failure isn't critical */
107:         if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0) {
108:             perror("could not restore window size");
109:         }
110:
111:         /* now start the shell */
112:         execl("/bin/sh", "/bin/sh", 0);
113:
114:         /* should never be reached */
115:         exit(1);
116:     }
117:
118:     /* parent */
119:     free(name);
120:
121:     /* Note that we only set termios settings for standard input;
122:      * the master side of a pty is NOT a tty.
123:      */
124:     tcgetattr(STDIN_FILENO, &ot);
125:     t = ot;
126:     t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | 
127:                    ECHOK | ECHOKE | ECHONL | ECHOPRT);
128:     t.c_iflag |= IGNBRK;
129:     t.c_cc[VMIN] = 1;
130:     t.c_cc[VTIME] = 0;
131:     tcsetattr(STDIN_FILENO, TCSANOW, &t);
132:
133:     /* This code comes nearly verbatim from robin.c
134:      * If the child exits, reading master will return -1 and
135:      * we exit.
136:      */
137:     ufds[0].fd = STDIN_FILENO;
138:     ufds[0].events = POLLIN;
139:     ufds[1].fd = master;
140:     ufds[1].events = POLLIN;
141:
142:     do {
143:         int r;
144:
145:         r = poll(ufds, 2, -1);
146:         if ((r < 0) && (errno != EINTR)) {
147:             done = 1;
148:             break;
149:         }
150:
151:         /* First check for an opportunity to exit */
152:         if ((ufds[0].revents | ufds[1].revents) &
153:             (POLLERR | POLLHUP | POLLNVAL)) {
154:             done = 1;
155:             break;
156:         }
157:
158:         if (propagate_sigwinch) {
159:             /* signal handler has asked for SIGWINCH propagation */
160:             if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
161:                 perror("ptypair: could not get window size");
162:             }
163:             if (ioctl(master, TIOCSWINSZ, &ws) < 0) {
164:                 perror("could not restore window size");
165:             }
166:
167:             /* now do not do this again until next SIGWINCH */
168:             propagate_sigwinch = 0;
169:
170:             /* poll may have been interrupted by SIGWINCH,
171:              * so try again. */
172:             continue;
173:         }
174:
175:         if (ufds[1].revents & POLLIN) {
176:             i = read(master, buf, BUFSIZE);
177:             if (i >= 1) {
178:                 write(STDOUT_FILENO, buf, i);
179:             } else {
180:                 done = 1;
181:             }
182:         }
183:
184:         if (ufds[0].revents & POLLIN) {
185:             i = read(STDIN_FILENO, buf, BUFSIZE);
186:             if (i >= 1) {
187:                 write(master, buf, i);
188:             } else {
189:                 done = 1;
190:             }
191:         }
192:     } while (!done);
193:
194:     tcsetattr(STDIN_FILENO, TCSANOW, &ot);
195:     exit(0);
196: }

All the added complexity of ptytest.c beyond forkptytest.c is to handle the complexity of the old interface. This has been described in this chapter, except for starting a child process, which is introduced in Chapter 10.



[1] That is, devices used both for input and for output.

[2] Older implementations of Unix provided this functionality through the TIOCSPGRP ioctl(), which is still supported in Linux. For comparison, tcsetpgrp() could be implemented as ioctl(ttyfd, TIOCSPGRP, &pgrp).

[3] For more information on signals and their interaction with job control, see Chapter 12.

[4] See man setserial for a Linux-specific way to get around this limitation on a limited basis.

[5] Note: “bits per second” or “bps,” not “baud.” Bits per second indicates the rate at which information is sent. Baud is an engineering term that describes phase changes per second. Baud is irrelevant to termios, but the word baud unfortunately has made its way into some termios flags that are not covered in this book.

[6] Such as setting up networking protocols that communicate through tty devices.

[7] B134 is really 134.5 bps, a rate used by an obsolete IBM terminal.

[8] Linux also uses c_cflag to hold the speed, but relying on that is completely nonportable. Use cfsetospeed() and cfsetispeed() instead.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset