Linux Application Development (paperback) (2nd Edition)

   

16.6. 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.

16.6.1. 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.)

16.6.2. 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);

16.6.3. 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.

16.6.4. 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.


       
     

    Категории