19.3. Opening Pseudo-Terminal DevicesThe way we open a pseudo-terminal device differs among platforms. The Single UNIX Specification includes several functions as XSI extensions in an attempt to unify the methods. These extensions are based on the functions originally provided to manage STREAMS-based pseudo terminals in System V Release 4.The posix_openpt function is provided as a portable way to open an available pseudo-terminal master device.
19.3.1. STREAMS-Based Pseudo TerminalsThe details of the STREAMS implementation of pseudo terminals under Solaris are covered in Appendix C of Sun Microsystems [2002]. The next available PTY master device is accessed through a STREAMS clone device . A clone device is a special device that returns an unused device when it is opened. (STREAMS clone opens are discussed in detail in Rago [1993].)The STREAMS-based PTY master clone device is /dev/ptmx. When we open it, the clone open routine automatically determines the first unused PTY master device and opens that unused device. (We'll see in the next section that, under BSD-based systems, we have to find the first unused PTY master ourselves.) Figure 19.8. STREAMS-based pseudo-terminal open functions#include "apue.h" #include <errno.h> #include <fcntl.h> #include <stropts.h> int ptym_open(char *pts_name, int pts_namesz) { char *ptr; int fdm; /* * Return the name of the master device so that on failure * the caller can print an error message. Null terminate * to handle case where strlen("/dev/ptmx") > pts_namesz. */ strncpy(pts_name, "/dev/ptmx", pts_namesz); pts_name[pts_namesz - 1] = '\0'; if ((fdm = open(pts_name, O_RDWR)) < 0) return(-1); if (grantpt(fdm) < 0) { /* grant access to slave */ close(fdm); return(-2); } if (unlockpt(fdm) < 0) { /* clear slave's lock flag */ close(fdm); return(-3); } if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */ close(fdm); return(-4); } /* * Return name of slave. Null terminate to handle * case where strlen(ptr) > pts_namesz. */ strncpy(pts_name, ptr, pts_namesz); pts_name[pts_namesz - 1] = '\0'; return(fdm); /* return fd of master */ } int ptys_open(char *pts_name) { int fds, setup; /* * The following open should allocate a controlling terminal. */ if ((fds = open(pts_name, O_RDWR)) < 0) return(-5); /* * Check if stream is already set up by autopush facility. */ if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0) { close(fds); return(-6); } if (setup == 0) { if (ioctl(fds, I_PUSH, "ptem") < 0) { close(fds); return(-7); } if (ioctl(fds, I_PUSH, "ldterm") < 0) { close(fds); return(-8); } if (ioctl(fds, I_PUSH, "ttcompat") < 0) { close(fds); return(-9); } } return(fds); } Figure 14.18), we push it onto the slave's stream.The reason that we might not need to push these three modules is that they might be there already. The STREAMS system supports a facility known as autopush , which allows an administrator to configure a list of modules to be pushed onto a stream whenever a particular device is opened (see Rago [1993] for more details). We use the I_FIND ioctl command to see whether ldterm is already on the stream. If so, we assume that the stream has been configured by the autopush mechanism and avoid pushing the modules a second time.The result of calling ptym_open and ptys_open is two file descriptors open in the calling process: one for the master and one for the slave. 19.3.2. BSD-Based Pseudo TerminalsUnder BSD-based systems and Linux-based systems, we provide our own versions of the XSI functions, which we can optionally include in our library, depending on which functions (if any) are provided by the underlying platform.In our version of posix_openpt, we have to determine the first available PTY master device. To do this, we start at /dev/ptyp0 and keep trying until we successfully open a PTY master or until we run out of devices. We can get two different errors from open: EIO means that the device is already in use; ENOENT means that the device doesn't exist. In the latter case, we can terminate the search, as all pseudo terminals are in use. Once we are able to open a PTY master, say /dev/ptyMN , the name of the corresponding slave is /dev/ttyMN . On Linux, if the name of the PTY master is /dev/pty/mXX , then the name of the corresponding PTY slave is /dev/pty/sXX .Section 19.4, we'll see how to allocate the controlling terminal under BSD-based systems. Figure 19.9. Pseudo-terminal open functions for BSD and Linux#include "apue.h" #include <errno.h> #include <fcntl.h> #include <grp.h> #ifndef _HAS_OPENPT int posix_openpt(int oflag) { int fdm; char *ptr1, *ptr2; char ptm_name[16]; strcpy(ptm_name, "/dev/ptyXY"); /* array index: 0123456789 (for references in following code) */ for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1 != 0; ptr1++) { ptm_name[8] = *ptr1; for (ptr2 = "0123456789abcdef"; *ptr2 != 0; ptr2++) { ptm_name[9] = *ptr2; /* * Try to open the master. */ if ((fdm = open(ptm_name, oflag)) < 0) { if (errno == ENOENT) /* different from EIO */ return(-1); /* out of pty devices */ else continue; /* try next pty device */ } return(fdm); /* got it, return fd of master */ } } errno = EAGAIN; return(-1); /* out of pty devices */ } #endif #ifndef _HAS_PTSNAME char * ptsname(int fdm) { static char pts_name[16]; char *ptm_name; ptm_name = ttyname(fdm); if (ptm_name == NULL) return(NULL); strncpy(pts_name, ptm_name, sizeof(pts_name)); pts_name[sizeof(pts_name) - 1] = '\0'; if (strncmp(pts_name, "/dev/pty/", 9) == 0) pts_name[9] = 's'; /* change /dev/pty/mXX to /dev/pty/sXX */ else pts_name[5] = 't'; /* change "pty" to "tty" */ return(pts_name); } #endif #ifndef _HAS_GRANTPT int grantpt(int fdm) { struct group *grptr; int gid; char *pts_name; pts_name = ptsname(fdm); if ((grptr = getgrnam("tty")) != NULL) gid = grptr->gr_gid; else gid = -1; /* group tty is not in the group file */ /* * The following two calls won't work unless we're the superuser. */ if (chown(pts_name, getuid(), gid) < 0) return(-1); return(chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP)); } #endif #ifndef _HAS_UNLOCKPT int unlockpt(int fdm) { return(0); /* nothing to do */ } #endif int ptym_open(char *pts_name, int pts_namesz) { char *ptr; int fdm; /* * Return the name of the master device so that on failure * the caller can print an error message. Null terminate * to handle case where string length > pts_namesz. */ strncpy(pts_name, "/dev/ptyXX", pts_namesz); pts_name[pts_namesz - 1] = '\0'; if ((fdm = posix_openpt(O_RDWR)) < 0) return(-1); if (grantpt(fdm) < 0) { /* grant access to slave */ close(fdm); return(-2); } if (unlockpt(fdm) < 0) { /* clear slave's lock flag */ close(fdm); return(-3); } if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */ close(fdm); return(-4); } /* * Return name of slave. Null terminate to handle * case where strlen(ptr) > pts_namesz. */ strncpy(pts_name, ptr, pts_namesz); pts_name[pts_namesz - 1] = '\0'; return(fdm); /* return fd of master */ } int ptys_open(char *pts_name) { int fds; if ((fds = open(pts_name, O_RDWR)) < 0) return(-5); return(fds); }Our version of posix_openpt tries 16 different groups of 16 PTY master devices: /dev/ptyp0 tHRough /dev/ptyTf. The actual number of PTY devices available depends on two factors: (a) the number configured into the kernel, and (b) the number of special device files that have been created in the /dev directory. The number available to any program is the lesser of (a) or (b). 19.3.3. Linux-Based Pseudo TerminalsLinux supports the BSD method for accessing pseudo terminals, so the same functions shown in Figure 19.9 will also work on Linux. However, Linux also supports a clone-style interface to pseudo terminals using /dev/ptmx (but this is not a STREAMS device). The clone interface requires extra steps to identify and unlock a slave device. The functions we can use to access these pseudo terminals on Linux are shown in Figure 19.10. Figure 19.10. Pseudo-terminal open functions for Linux#include "apue.h" #include <fcntl.h> #ifndef _HAS_OPENPT int posix_openpt(int oflag) { int fdm; fdm = open("/dev/ptmx", oflag); return(fdm); } #endif #ifndef _HAS_PTSNAME char * ptsname(int fdm) { int sminor; static char pts_name[16]; if (ioctl(fdm, TIOCGPTN, &sminor) < 0) return(NULL); snprintf(pts_name, sizeof(pts_name), "/dev/pts/%d", sminor); return(pts_name); } #endif #ifndef _HAS_GRANTPT int grantpt(int fdm) { char *pts_name; pts_name = ptsname(fdm); return(chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP)); } #endif #ifndef _HAS_UNLOCKPT int unlockpt(int fdm) { int lock = 0; return(ioctl(fdm, TIOCSPTLCK, &lock)); } #endif int ptym_open(char *pts_name, int pts_namesz) { char *ptr; int fdm; /* * Return the name of the master device so that on failure * the caller can print an error message. Null terminate * to handle case where string length > pts_namesz. */ strncpy(pts_name, "/dev/ptmx", pts_namesz); pts_name[pts_namesz - 1] = '\0'; fdm = posix_openpt(O_RDWR); if (fdm < 0) return(-1); if (grantpt(fdm) < 0) { /* grant access to slave */ close(fdm); return(-2); } if (unlockpt(fdm) < 0) { /* clear slave's lock flag */ close(fdm); return(-3); } if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */ close(fdm); return(-4); } /* * Return name of slave. Null terminate to handle case * where strlen(ptr) > pts_namesz. */ strncpy(pts_name, ptr, pts_namesz); pts_name[pts_namesz - 1] = '\0'; return(fdm); /* return fd of master */ } int ptys_open(char *pts_name) { int fds; if ((fds = open(pts_name, O_RDWR)) < 0) return(-5); return(fds); }On Linux, the PTY slave device is already owned by group tty, so all we need to do in grantpt is ensure that the permissions are correct. |