Go to main content

STREAMS Programming Guide

Exit Print View

Updated: March 2019
 
 

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 devices that 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.

Pseudo-TTY Subsystem Architecture 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 in Packet Mode).

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 39  Pseudo-TTY Subsystem Architecture

image:Diagram shows the client and server streams in a pseudo-TTY subsystem.

Pseudo-TTY Emulation Module: ptem

Because 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)calls 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 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. Setting the baud rate to 0 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 also not supported.

ptem(7M) does the following:

  • Processes, if appropriate, and acknowledges receipt of the following ioctls on its write queue by sending an M_IOCACK message back upstream: TCSETA, TCSETAW, TCSETAF, TCSETS, TCSETSW, TCSETSF, TCGETA, TCGETS, and TCSBRK.

  • Keeps track of the window size; information needed for the TIOCSWINSZ, TIOCGWINSZ, and JWINSIZE ioctl.

  • When it receives an ioctl, other than for TIOCSWINSZ, TIOCGWINSZ, or JWINSIZE on its write queue, it sends an M_IOCNAK message upstream.

  • It passes downstream the following ioctls after processing them: TCSETA, TCSETAW, TCSETAF, TCSETS, TCSETSW, TCSETSF, TCSBRK, and TIOCSWINSZ.

  • Frees any M_IOCNAK messages it receives on its read queue in case the pckt module (pckt(7M) (described in the section Packet Mode ) is not on the pseudo-terminal subsystem and the TCSETA, TCSETAW, TCSETAF, TCSETS, TCSETSW, TCSETSF, TCSBRK, or TIOCSWINSZioctls get to the master's stream head which then sends an M_IOCNAK message.

  • In its open routine, ptem sends an M_SETOPTS message upstream requesting allocation of a controlling TTY.

  • When ptem receives an M_IOCTL message of type TCSBRK on its read queue, it sends an M_IOCACK message downstream and an M_BREAK message upstream.

  • When ptem receives an ioctl(2) message on its write queue to set the baud rate to 0 (TCSETAW with CBAUD set to B0), it sends an M_IOCACK message upstream and a zero-length message downstream.

  • When ptem receives an M_IOCTL of type TIOCSIGNAL on its read queue, it sends an M_IOCACK downstream and an M_PCSIG upstream, where the signal number is the same as in the M_IOCTL message.

  • When ptem receives an M_IOCTL of type TIOCREMOTE on its read queue, it sends an M_IOCACK message downstream and the appropriate M_CTL message upstream to enable or disable canonical processing.

  • When ptem receives an M_DELAY message on its read or write queue, it discards the message and does not act on it.

  • When ptem receives an M_IOCTL of type JWINSIZE on its write queue, and if the values in its jwinsize structure are not zero, it sends an M_IOCACK message upstream with the jwinsize structure. If the values are zero, it sends an M_IOCNAK message upstream.

  • When ptem receives an M_IOCTL message of type TIOCGWINSZ on its write queue and the values in the winsize structure are not zero, it sends an M_IOCACK message upstream with the winsize structure. If the values are zero, it sends an M_IOCNAK message upstream. It also saves the information passed to it in the winsize structure and sends a STREAMS signal message for signal SIGWINCH upstream to the slave process if the size changed.

  • When ptem(7M) receives an M_IOCTL message with type TIOCGWINSZ on its read queue and the values in the winsize structure are not zero, it sends an M_IOCACK message downstream with the winsize structure. If the values are zero, it sends an M_IOCNAK message downstream. It also saves the information passed to it in the winsize structure and sends a STREAMS signal message for signal SIGWINCH upstream to the slave process if the size changed.

  • All other messages are passed to the next module or driver.

ptem Data Structure

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.


Note - ptem(7M) internal implementation might change. This structure should be relevant only to people wanting to change the module.

open and close Routines


Caution

Caution  - The following information is implementation-dependent.


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:

  • No free entries are found when the ptem(7M) structure is searched

  • sflag is not set to MODOPEN

  • A zero-length message cannot be allocated (no buffer is available)

  • A stroptions(9S) structure cannot be allocated

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

Remote mode available with the pseudo-TTY subsystem, is used for applications that perform the canonical function normally done by ldterm(7M) and the TTY driver. The remote mode enables 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

In the STREAMS-based pseudo-terminal subsystem packet mode 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:

  • When a message is passed to this module on its write queue, the module does no processing and passes the message to the next module or driver.

  • pckt creates an M_PROTO message when one of the following messages is passed to it: M_DATA, M_IOCTL, M_PROTO/M_PCPROTO, M_FLUSH, M_READ, M_START/M_STOP, and M_STARTI/M_STOPI.

    All other messages are passed through. The M_PROTO message is passed upstream and retrieved when the user issues getmsg(2).

  • If the message is an M_FLUSH message, pckt(7M) does the following:

    • If the flag is FLUSHW, it is changed to FLUSHR (because FLUSHR was the original flag before the pts(7D) driver changed it), changed into an M_PROTO message, and passed upstream. To prevent the stream head's read queue from being flushed, the original M_FLUSH message must not be passed upstream.

    • If the flag is FLUSHR, it is changed to FLUSHW, packetized into an M_PROTO message, and passed upstream. To flush both of the write queues properly, an M_FLUSH message with the FLUSHW flag set is also sent upstream.

    • If the flag is FLUSHRW, the message with both flags set is packetized and passed upstream. An M_FLUSH message with the FLUSHW flag set is also sent upstream.

Pseudo-TTY Drivers: ptm and pts

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.


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 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 functionsgrantpt(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() Function

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() Function

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() Function

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.