Pseudoterminals Packet Mode (TIOCPKT)

 

Chapter 64: Pseudoterminals
Part 3 of 4 โ€” Packet Mode (TIOCPKT)
TIOCPKT
ioctl
Ctrl-S/Q
Flow Control
POLLPRI
poll() event

๐Ÿ“‚ Tutorial Series Navigation

๐Ÿท Key Terms in This Part
packet mode TIOCPKT software flow control XON/XOFF Control-S / Control-Q TIOCPKT_FLUSHREAD TIOCPKT_FLUSHWRITE TIOCPKT_STOP TIOCPKT_START POLLPRI exceptfds (select) telnet rlogin

What is Packet Mode?

Normally, a read from the PTY master gives you pure data bytes โ€” the bytes that the shell or program wrote to the slave. But sometimes the master side needs to know about control events happening on the slave, not just data.

Examples of such events: the user pressed Control-S (stop output), or Control-Q (restart output), or a program flushed the terminal queues. These events relate to software flow control (also called XON/XOFF flow control).

Packet mode is a feature where the PTY master, when enabled, can detect these flow-control events and react to them. Without packet mode, the master sees nothing when Control-S is pressed โ€” the output just quietly stops. With packet mode, the master is told about it via a special control byte.

This is important for network services like telnet and rlogin that carry terminal sessions over a network. They need to know when output is paused or flushed so they can synchronise the remote display correctly.

โ–ถ Software Flow Control: Control-S and Control-Q

Software flow control is a mechanism that lets the terminal (or a program) pause and resume output without dropping data.

โธ Control-S (XOFF / Stop)

Pressing Ctrl-S sends an XOFF character to the terminal driver. The driver stops sending output to the terminal. Characters accumulate in the output buffer.

โ–ถ Control-Q (XON / Start)

Pressing Ctrl-Q sends an XON character. The driver resumes output from where it stopped. All buffered characters are sent to the screen.

In a normal terminal emulator, when you press Ctrl-S, the screen freezes. When you press Ctrl-Q, it unfreezes. The PTY slave handles this internally. But if you are forwarding this session over a network (like telnet/rlogin), the remote side needs to know when output stopped, so it does not keep sending data that cannot be displayed.

๐Ÿ“„ How to Enable Packet Mode

Packet mode is enabled on the master fd using the TIOCPKT ioctl:

#include <sys/ioctl.h>

int masterFd;   /* opened earlier with posix_openpt */
int arg;

/* Enable packet mode */
arg = 1;
if (ioctl(masterFd, TIOCPKT, &arg) == -1) {
    perror("ioctl TIOCPKT enable");
    return 1;
}

/* Disable packet mode */
arg = 0;
if (ioctl(masterFd, TIOCPKT, &arg) == -1) {
    perror("ioctl TIOCPKT disable");
    return 1;
}
๐Ÿ’ก Important: TIOCPKT is applied to the master fd, not the slave fd. Only the master side runs in packet mode. The slave side is unaffected.

๐Ÿ”„ How read() Works Differently in Packet Mode

In normal mode, read(masterFd, buf, N) returns N bytes of data from the slave.

In packet mode, each read() from the master returns one of two things:

Type of Read Result What it Means How to Identify
Control packet A flow-control event happened on the slave (e.g., flush, stop, start) buf[0] != 0 (nonzero control byte, rest is empty)
Data packet Actual data written by the program on the slave buf[0] == 0, then buf[1..n] contains the data

So in packet mode, every read from the master starts with one extra byte. If that byte is zero, the rest is data. If it is nonzero, it is a bit mask of control events.

char buf[4096];
int n;

n = read(masterFd, buf, sizeof(buf));
if (n <= 0) {
    /* error or EOF */
    return;
}

if (buf[0] != 0) {
    /* Control event - check which bits are set */
    if (buf[0] & TIOCPKT_FLUSHREAD)
        printf("Input queue was flushed (Ctrl-C effect)\n");
    if (buf[0] & TIOCPKT_FLUSHWRITE)
        printf("Output queue was flushed\n");
    if (buf[0] & TIOCPKT_STOP)
        printf("Output stopped (Ctrl-S pressed)\n");
    if (buf[0] & TIOCPKT_START)
        printf("Output restarted (Ctrl-Q pressed)\n");
    if (buf[0] & TIOCPKT_IOCTL)
        printf("Terminal ioctl happened on slave\n");
} else {
    /* Data event - buf[1..n-1] contains actual data */
    printf("Data received (%d bytes): %.*s\n", n-1, n-1, buf+1);
}

๐Ÿ”‘ Packet Mode Control Byte Bit Masks

When the control byte (buf[0]) is nonzero, it is a bitmask. Multiple bits can be set at once. The common bit flags defined on Linux (from <sys/ioctl.h>):

Flag Triggered By Meaning
TIOCPKT_FLUSHREAD tcflush(TCIFLUSH) on slave Slave’s input queue was flushed
TIOCPKT_FLUSHWRITE tcflush(TCOFLUSH) on slave Slave’s output queue was flushed
TIOCPKT_STOP Ctrl-S pressed on slave terminal Slave output has been stopped
TIOCPKT_START Ctrl-Q pressed on slave terminal Slave output has been restarted
TIOCPKT_NOSTOP Flow control disabled on slave XON/XOFF flow control turned off
TIOCPKT_DOSTOP Flow control enabled on slave XON/XOFF flow control turned on
TIOCPKT_IOCTL ioctl on slave A terminal control change happened
โš  Portability note: Packet mode and these bit masks are not standardised in POSIX/SUSv3. Details vary across UNIX implementations. Always consult the tty_ioctl(4) man page for your platform.

โš™ Packet Mode with select() and poll()

In a real application, you do not just sit in a blocking read() loop. You multiplex between the master fd and other fds (like a network socket) using select() or poll().

Packet mode integrates with both:

select() integration

When a control event (STOP/START/FLUSH) occurs on the slave, select() signals an exceptional condition on the master fd. This means the master fd should be added to the exceptfds set in the select() call.

poll() integration

When a control event occurs, poll() sets the POLLPRI bit in the revents field for the master fd. Register the master fd with events = POLLIN | POLLPRI.

#include <poll.h>
#include <stdio.h>
#include <sys/ioctl.h>

void packet_mode_loop(int masterFd, int networkFd)
{
    struct pollfd fds[2];
    char buf[4096];
    int n, arg;

    /* Enable packet mode on PTY master */
    arg = 1;
    ioctl(masterFd, TIOCPKT, &arg);

    fds[0].fd     = masterFd;
    fds[0].events = POLLIN | POLLPRI;  /* data + control events */

    fds[1].fd     = networkFd;
    fds[1].events = POLLIN;

    while (1) {
        if (poll(fds, 2, -1) == -1) {
            perror("poll");
            break;
        }

        /* PTY master has something */
        if (fds[0].revents & (POLLIN | POLLPRI)) {
            n = read(masterFd, buf, sizeof(buf));
            if (n <= 0) break;  /* slave closed */

            if (buf[0] != 0) {
                /* Control event - handle flow control */
                if (buf[0] & TIOCPKT_STOP) {
                    /* Tell remote to pause its output */
                    /* send_xoff_to_network(networkFd); */
                }
                if (buf[0] & TIOCPKT_START) {
                    /* Tell remote to resume its output */
                    /* send_xon_to_network(networkFd); */
                }
            } else {
                /* Data: forward buf[1..n-1] to network */
                /* write(networkFd, buf + 1, n - 1); */
            }
        }

        /* Network socket has data */
        if (fds[1].revents & POLLIN) {
            n = read(networkFd, buf, sizeof(buf));
            if (n <= 0) break;  /* connection closed */
            /* Forward to PTY master */
            write(masterFd, buf, n);
        }
    }
}

๐Ÿ“„ Using select() with Packet Mode (exceptfds)
#include <sys/select.h>
#include <stdio.h>

void select_packet_mode(int masterFd)
{
    fd_set readfds, exceptfds;
    char buf[4096];
    int n;

    while (1) {
        FD_ZERO(&readfds);
        FD_ZERO(&exceptfds);

        FD_SET(masterFd, &readfds);    /* normal data */
        FD_SET(masterFd, &exceptfds);  /* packet mode control events */

        if (select(masterFd + 1, &readfds, NULL, &exceptfds, NULL) == -1) {
            perror("select");
            break;
        }

        /* Either data or control event - read() handles both */
        if (FD_ISSET(masterFd, &readfds) ||
            FD_ISSET(masterFd, &exceptfds)) {

            n = read(masterFd, buf, sizeof(buf));
            if (n <= 0) break;

            if (buf[0] != 0) {
                printf("Control event: 0x%02x\n", (unsigned char)buf[0]);
            } else {
                printf("Data (%d bytes): %.*s\n", n-1, n-1, buf+1);
            }
        }
    }
}
Note: Even when packet mode fires a control event (exceptional condition), you still call read() on the master in the normal way. The read returns a single nonzero control byte. You do not need a separate API to fetch the control data.

๐ŸŒ Real Use Case: Why telnet/rlogin Need Packet Mode

Consider a user connecting via telnet from a remote machine:

Without Packet Mode With Packet Mode
User presses Ctrl-S on remote end User presses Ctrl-S on remote end
Local PTY slave stops output internally Local PTY slave stops output internally
telnet daemon does not know output stopped telnet daemon sees TIOCPKT_STOP control byte
telnet keeps sending data, filling network buffers telnet sends XOFF to remote, pausing remote output too
Data accumulates, poor user experience Clean pause and resume across the network

This is the exact problem packet mode was designed to solve. Modern SSH does not use XON/XOFF flow control this way (it uses its own windowing), but older protocols like telnet and rlogin relied on packet mode for correct flow-control forwarding.

๐ŸŽ“ Interview Questions โ€” Packet Mode
Q1. What is packet mode in a pseudoterminal and why was it introduced?
Packet mode is a PTY master feature that notifies the master-side process when software flow-control events (flush, stop, start) occur on the slave side. It was introduced to let network services like telnet and rlogin correctly forward XON/XOFF flow control signals across a network connection, so the remote side can pause and resume in sync with the local terminal.
Q2. Which ioctl is used to enable packet mode and on which fd?
ioctl(masterFd, TIOCPKT, &arg) with arg = 1 to enable, arg = 0 to disable. It is applied to the master fd, not the slave.
Q3. How does read() from the master behave differently in packet mode vs normal mode?
In normal mode, read returns raw data bytes. In packet mode, read returns one extra leading byte. If that byte is zero, the rest is data. If it is nonzero, it is a bitmask of control events (TIOCPKT_STOP, TIOCPKT_START, TIOCPKT_FLUSHREAD, etc.) and there is no data in that read.
Q4. What does TIOCPKT_STOP signal and when is it generated?
TIOCPKT_STOP is set in the control byte when the user presses Ctrl-S on the slave terminal. It means output to the slave has been paused by XON/XOFF flow control. A telnet daemon would respond by sending XOFF to the remote client to pause it as well.
Q5. How does poll() indicate a packet mode control event?
poll() sets the POLLPRI bit in the revents field for the master fd when a control event occurs. The master fd must be registered with events = POLLIN | POLLPRI to receive both data and control notifications.
Q6. How does select() indicate a packet mode control event?
select() signals an exceptional condition on the master fd. The master fd must be included in the exceptfds fd_set. When select returns with the master fd set in exceptfds, a read will return a nonzero control byte.
Q7. Is packet mode standardised in POSIX or SUSv3?
No. Packet mode and the TIOCPKT ioctl are Linux and BSD specific and are not part of POSIX or SUSv3. Behaviour and bit-mask values may differ between operating systems. Applications using packet mode are not portable to all UNIX variants.
Q8. What is the difference between TIOCPKT_FLUSHREAD and TIOCPKT_FLUSHWRITE?
TIOCPKT_FLUSHREAD means the slave’s input queue was flushed (e.g., tcflush with TCIFLUSH), discarding unread input. TIOCPKT_FLUSHWRITE means the slave’s output queue was flushed (TCOFLUSH), discarding data waiting to be sent to the master.

Next: The script(1) Program โ€” Putting It All Together
See how ptyFork() and PTY I/O combine to implement the classic script program that records your terminal session to a file.

Part 4: script(1) Program โ†’ โ† Part 2: ptyFork()

Leave a Reply

Your email address will not be published. Required fields are marked *