To use the pseudo-TTY subsystem, a node for the master side driver /dev/ptmx and N number of slave drivers must be installed (N is determined at installation). The names of the slave devices are /dev/pts/M where M has the values 0 through N-1. A user accesses a pseudo-TTY device through the master device (called ptm) that in turn is accessed through the clone driver. The master device is set up as a clone device where its major device number is the major for the clone device and its minor device number is the major for the ptm(7D) driver.
The master pseudo driver is opened by calling open(2) with /dev/ptmx as the device to be opened. The clone open finds the next available minor device for that major device. A master device is available only if it, and its corresponding slave device, are not already open. There are no nodes in the file system for master devices.
When the master device is opened, the corresponding slave device is automatically locked out. No user may open that slave device until it is unlocked. A user may invoke a function grantpt to change the owner of the slave device to that of the user who is running this process, change the group ID to TTY, and change the mode of the device to 0620. Once the permissions have been changed, the device may be unlocked by the user. Only the owner or the root user can access the slave device. The user must then invoke the unlockpt function to unlock the slave device. Before opening the slave device, the user must call the ptsname function to obtain the name of the slave device. The functions grantpt, unlockpt, and ptsname are called with the file descriptor of the master device. The user may then invoke the open system call with the name that was returned by the ptsname function to open the slave device.
The following example shows how a user may invoke the pseudo-TTY subsystem:
int fdm fds; char *slavename; extern char *ptsname(); fdm = open("/dev/ptmx", O_RDWR); /* open master */ grantpt(fdm); /* change permission of slave */ unlockpt(fdm); /* unlock slave */ slavename = ptsname(fdm); /* get name of slave */ fds = open(slavename, O_RDWR); /* open slave */ ioctl(fds, I_PUSH, "ptem"); /* push ptem */ ioctl(fds, I_PUSH, "ldterm"); /* push ldterm */
Unrelated processes may open the pseudo device. The initial user may pass the master file descriptor using a STREAMS-based pipe or a slave name to another process to enable it to open the slave. After the slave device is open, the owner is free to change the permissions.
Certain programs such as write and wall are set group ID (setgid(2)) to TTY and are also able to access the slave device.
After both the master and slave have been opened, the user has two file descriptors that provide full-duplex communication using two streams. The two streams are automatically connected. The user may then push modules onto either side of the stream. The user also needs to push the ptem and ldterm modules onto the slave side of the pseudo-terminal subsystem to get terminal semantics.
The master and slave drivers pass all STREAMS messages to their adjacent queues. Only the M_FLUSH needs some processing. Because the read queue of one side is connected to the write queue of the other, the FLUSHR flag is changed to FLUSHW and vice versa.
When the master device is closed, an M_HANGUP message is sent to the slave device to render the device unusable. The process on the slave side gets the errno ENXIO when attempting to write on that stream, but it will be able to read any data remaining on the stream head read queue. When all the data has been read, read(2) returns 0, indicating that the stream can no longer be used.
On the last close of the slave device, a zero-length message is sent to the master device. When the application on the master side issues a read or getmsg and 0 is returned, the user of the master device decides whether to issue a close that dismantles the pseudo-terminal subsystem. If the master device is not closed, the pseudo-TTY subsystem is available to another user to open the slave device.
Since zero-length messages are used to indicate that the process on the slave side has closed (and should be interpreted that way by the process on the master side), applications on the slave side should not write zero-length messages. If that occurs, the write returns 0, and the zero-length message is discarded by the ptem module.
The standard STREAMS system calls can access the pseudo-TTY devices. The slave devices support the O_NDELAY and O_NONBLOCK flags. Because the master side does not act like the terminal, if O_NONBLOCK or O_NDELAY is set, read on the master side returns -1 with errno set to EAGAIN if no data is available, and write(2) returns -1 with errno set to EAGAIN if there is internal flow control.
The master driver supports the ISPTM and UNLKPT ioctl(2) that are used by the functions grantpt(3C), unlockpt(3C), and ptsname(3C). The ISPTM ioctl(2) determines whether the file descriptor is that of an open master device. On success, it returns the major/minor number (type dev_t) of the master device which can be used to determine the name of the corresponding slave device. The UNLKPT ioctl(2) unlocks the master and slave devices. It returns 0 on success. On failure, the errno is set to EINVAL indicating that the master device is not open.
The format of these commands is:
int ioctl (int fd, int command, int arg)
where command is either ISPTM or UNLKPT and arg is 0. On failure, -1 is returned.
When data is written to the master side, the entire block of data written is treated as a single line. The slave-side process reading the terminal receives the entire block of data. Data is not edited by the ldterm module at input, regardless of the terminal mode. The master-side application is responsible for detecting an interrupt character and sending an interrupt signal SIGINT to the process in the slave side. This can be done as follows:
ioctl (fd, TIOCSIGNAL, SIGINT)
where SIGINT is defined in the file signal.h. When a process on the master side issues this ioctl(2), the argument is the number of the signal that should be sent. The specified signal is then sent to the process group on the slave side.
grantpt(3C) changes the mode and the ownership of the slave device that is associated with the given master device. Given a file descriptor fd, grantpt(3C) first checks that the file descriptor is that of the master device. If so, it obtains the name of the associated slave device and sets the user ID to that of the user running the process and the group ID to TTY. The mode of the slave device is set to 0620.
If the process is already running as root, the permission of the slave can be changed directly without invoking this function. grantpt(3C) returns 0 on success and -1 on failure. It fails if one or more of the following occurs: fd is not an open file descriptor, fd is not associated with a master device, the corresponding slave could not be accessed, or a system call failed because no more processes could be created.
unlockpt(3C) clears a lock flag associated with a master/slave device pair. unlockpt(3C) returns 0 on success and -1 on failure. It fails if one or more of the following occurs: fd is not an open file descriptor or fd is not associated with a master device.
ptsname(3C) returns the name of the slave device that is associated with the given master device. It first checks that the file descriptor is that of the master. If it is, it then determines the name of the corresponding slave device /dev/pts/M and returns a pointer to a string containing the null-terminated path name. The return value points to static data whose content is overwritten by each call. ptsname(3C) returns a non-NULL path name upon success and a NULL pointer upon failure. It fails if one or more of the following occurs: fd is not an open file descriptor or fd is not associated with the master device.