STREAMS Programming Guide

STREAMS-based Pseudo-Terminal Subsystem

The STREAMS-based pseudo-terminal subsystem provides the user with an interface that is identical to the STREAMS-based terminal subsystem described earlier in this chapter. The pseudo-terminal subsystem (pseudo-TTY) supports a pair of STREAMS-based devices called the master device and slave device. The slave device provides processes with an interface that is identical to the terminal interface. However, where all devices, which provide the terminal interface, have some kind of hardware device behind them, the slave device has another process manipulating it through the master half of the pseudo terminal. Anything written on the master device is given to the slave as an input, and anything written on the slave device is presented as an input on the master side.

Figure 14-2 illustrates the architecture of the STREAMS-based pseudo-terminal subsystem. The master driver called ptm is accessed through the clone driver and is the controlling part of the system. The slave driver called pts works with the line discipline module and the hardware emulation module to provide a terminal interface to the user process. An optional packetizing module called pckt is also provided. It can be pushed on the master side to support packet mode (this is discussed later).

The number of pseudo-TTY devices that can be installed on a system depends on available memory.

Line-Discipline Module

In the pseudo-TTY subsystem, the line discipline module is pushed on the slave side to present the user with the terminal interface.

ldterm(7M) can turn off the processing of the c_iflag, c_oflag, and c_lflag fields to allow processing to take place elsewhere. The ldterm(7M) module can also turn off all canonical processing when it receives an M_CTL message with the MC_NO_CANON command to support remote mode. Although ldterm(7M) passes through messages without processing them, the appropriate flags are set when an ioctl(2), such as TCGETA or TCGETS, is issued to indicate that canonical processing is being performed.

Figure 14-2 Pseudo-TTY Subsystem Architecture


Pseudo-TTY Emulation Module - ptem(7M)

Since the pseudo-TTY subsystem has no hardware driver downstream from the ldterm(7M) module to process the terminal ioctl(2) calls, another module that understands the ioctl commands is placed downstream from the ldterm(7M). This module, ptem(7M), processes all of the terminal ioctl(2) and mediates the passage of control information downstream.

ldterm(7M) and ptem(7M) together behave like a real terminal. Since there is no real terminal or modem in the pseudo-TTY subsystem, some of the ioctl(2) commands are ignored and cause only an acknowledgment of the command. ptem(7M) keeps track of the terminal parameters set by the various set commands such as TCSETA or TCSETAW but does not usually perform any action. For example, if a "set" ioctl(2) is called, none of the bits in the c_cflag field of termio(7I) has any effect on the pseudo-terminal unless the baud rate is set to 0. When setting the baud rate to 0, it has the effect of hanging up the pseudo-terminal.

The pseudo-terminal does not recognize parity, so none of the flags in the c_iflag that control the processing of parity errors have any effect. The delays specified in the c_oflag field are not also supported.

ptem(7M) does the following:

Data Structure

SunOS 5 reserves the right to change ptem(7M) internal implementation. This structure should be relevant only to people wanting to change the module.

Each instantiation of ptem(7M) is associated with a local area. These data are held in a structure called ptem that has the following format:

struct ptem
	long cflags;							/* copy of c_flags */
	mblk_t *dack_ptr;							/* pointer to preallocated msg blk
									used to send disconnect */
	queue_t *q_ptr;							/* pointer to ptem's read queue */
	struct winsize wsz;				/*struct to hold windowing info*/
	unsigned short state; /* state of ptem entry */

When ptem(7M) is pushed onto the slave side Stream, a search of the ptem structure is made for a free entry (state is not set to INUSE). The c_cflags of the termio(7I) structure and the windowing variables are stored in cflags and wsz respectively. The dack_ptr is a pointer to a message block used to send a zero-length message whenever a hang-up occurs on the slave side.

Open and Close Routines

In the open routine of ptem(7M) a STREAMS message block is allocated for a zero-length message for delivering a hangup message; this allocation of a buffer is done before it is needed to ensure that a buffer is available. An M_SETOPTS message is sent upstream to set the read side Stream head queues, to assign high-water and low-water marks (1024 and 256 respectively), and to establish a controlling terminal.

The same default values as for the line discipline module are assigned to cflags, and INUSE to the state field.

Note -

These default values are currently being examined and may change in the future.

The open routine fails if:

The close routine is called on the last close of the slave side Stream. Pointers to read and write queues are cleared and the buffer for the zero-length message is freed.

Remote Mode

A feature known as remote mode is available with the pseudo-TTY subsystem. This feature is used for applications that perform the canonical function normally done by ldterm(7M) and TTY driver. The remote mode allows applications on the master side to turn off the canonical processing. An TIOCREMOTE ioctl(2) with a nonzero parameter (ioctl(fd, TIOCREMOTE, 1)) is issued on the master side to enter the remote mode. When this occurs, an M_CTL message with the command MC_NO_CANON is sent to ldterm(7M) indicating that data should be passed when received on the read side and that no canonical processing is to take place. The remote mode may be disabled by ioctl(fd, TIOCREMOTE, 0).

Packet Mode

The STREAMS-based pseudo-terminal subsystem also supports a feature called packet mode. This is used to inform the process on the master side when state changes have occurred in the pseudo-TTY. Packet mode is enabled by pushing the pckt module on the master side. Data written on the master side is processed normally. When data is written on the slave side, or when other messages are encountered by the pckt module, a header is added to the message so it can be subsequently retrieved by the master side with a getmsg operation.

pckt(7M) does the following:

Pseudo-TTY Drivers - ptm(7D) and pts(7D)

To use the pseudo-TTY subsystem, a node for the master side driver /dev/ptmx and N number of slave drivers (N is determined at installation ) must be installed. 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 root 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.

Note -

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 flag 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. Since 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 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:


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 descripto; 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.

Pseudo-TTY Streams

Drivers and modules can make the Stream head act as a terminal Stream by sending an M_SETOPTS message with the SO_ISTTY flag set upstream. This state may be changed by sending an M_SETOPTS message with the SO_ISNTTY flag set upstream.

Controlling terminals are allocated with the open(2) interface. The device must tell the Stream head that it is acting as a terminal.

The TOSTOP flag is set on reception of an M_SETOPTS message with the SO_TOSTOP flag set in the so_flags field. It is cleared on reception of an M_SETOPTS message with the SO_TONSTOP flag set.

Stream head processing is isolated from modules and drivers by using several message types, such as M_ERROR, M_HANGUP and M_SETOPS, which only affect the Stream in which they are sent.