In order 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 time.) 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 (see clone(7)). 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 driver.
The master pseudo-driver is opened by the open system call 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 that will 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 superuser 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 (no error checking is included to keep the example simple):
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.
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 flag and vice versa.
When the master device is closed, an M_HANGUP message is sent to the slave device that will 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 returns 0 indicating that the Stream can no longer be used.
On the last close of the slave device, a 0-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 will be available to another user to open the slave device.
Because 0-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 0-length messages. If that occurs, the write returns 0, and the 0-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 returns -1 with errno set to EAGAIN if there is internal flow control.
The master driver supports the ISPTM and UNLKPT ioctls that are used by the functions grantpt, unlockpt, and ptsname (see grantpt(3C), unlockpt(3C), ptsname(3C)). The ioctl ISPTM 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 that can be used to determine the name of the corresponding slave device. The ioctl UNLKPT 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 input-edited by the ldterm module 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, 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.
To summarize, the master driver and slave driver have the following characteristics:
The grantpt function changes the mode and the ownership of the slave device that is associated with the given master device. Given a file descriptor fd, grantpt 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. The interface is
grantpt (int fd)
The grantpt function 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.
The unlockpt function clears a lock flag associated with a master/slave device pair. Its interface is
unlockpt (int fd)
The unlockpt 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.
The ptsname function 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 pathname. The return value points to static data whose content is overwritten by each call. The interface is
char *ptsname (int fd)The ptsname function returns a non-NULL pathname on 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.