FreeBSD Pseudo-Terminal Operations: Allocation, Identity, and Cleanup
Trace FreeBSD pseudo-terminal allocation, /dev/pts identity, lifecycle cleanup, and the boundary between terminal I/O and session control.
A pseudo-terminal (PTY) is a pair of character devices that lets one process provide terminal-like input and output to another. SSH sessions, terminal emulators, shells, test harnesses, and programs such as script(1) rely on this boundary. On FreeBSD, the modern pts(4) driver creates a master/slave pair; the slave behaves like a terminal device, while a process controlling the master supplies bytes and receives output.
Understanding this model makes terminal incidents easier to diagnose. A process can remain alive after its visible terminal window disappears. A PTY path can be reused after a prior session ends. A session can have a controlling terminal and line discipline without being attached to a physical serial port. These are lifecycle and process relationships, not just filenames under /dev.
Understand the master/slave boundary
Data written to the PTY master is delivered as input to the slave side, and output written to the slave can be read from the master. The slave exposes the terminal interface described by tty(4), including terminal settings and job-control behavior. The master side is what terminal software uses to bridge a process to a user interface, remote transport, or recording program.
This design allows an application to run as if it had a terminal without requiring a physical keyboard or display. It also means that output may be buffered or transformed by terminal settings. When a program appears to stop responding, distinguish application buffering, terminal line discipline, flow control, and a disconnected master. Looking only at a tty pathname does not identify which side is blocked.
The canonical PTY interface uses functions such as posix_openpt(2), grantpt(3), unlockpt(3), and ptsname(3). New code should use these interfaces rather than hard-coded legacy device names. A simplified lifecycle is:
int master = posix_openpt(O_RDWR | O_NOCTTY);
if (master == -1) {
/* Handle allocation failure. */
}
if (grantpt(master) == -1 || unlockpt(master) == -1) {
/* Close master and report the error. */
}
char *slave_path = ptsname(master);
if (slave_path == NULL) {
/* Close master and report the error. */
}
This is an interface sketch, not a complete production function: it omits headers, error-string handling, descriptor cleanup, and race-safe use of the returned path. Follow the installed library documentation for ownership, permissions, and thread-safety requirements. Do not use /dev/ptmx assumptions from Linux documentation as a substitute for FreeBSD’s pts(4) and posix_openpt(2) contracts.
Inspect the PTY attached to a process
Start with a process listing and the terminal state exposed by the host:
ps -axo pid,ppid,sid,pgid,tty,stat,command
tty
tty reports the terminal device attached to its own standard input; it does not query another process or by itself prove that the caller has a controlling terminal. The TTY field in ps is the process-level controlling-terminal view. A value such as ?? in process output means that a process has no controlling terminal; it does not automatically mean the process is orphaned or malfunctioning. A service launched by rc or a supervisor commonly has no terminal by design.
For a specific PID, use process inspection tools to determine its open descriptors, session, and parent relationship. procstat -f PID can reveal descriptors held by that process, and ps can show its process group and session. Replace PID with a validated numeric process ID. If the process is inside a jail, inspect it from the correct host or jail context and note which view of /dev you are observing.
The slave pathname under /dev/pts may disappear when the PTY becomes unused. Do not treat absence of a node as proof that an old process never used it. Conversely, a path present now may be assigned to a different session than it was during an earlier incident. Correlate process ID, start time, session, and descriptor information rather than relying on a stale pts/N reference.
Distinguish a controlling terminal from a descriptor
A process may have a file descriptor open on a terminal device without having that terminal as its controlling terminal. A controlling terminal affects job control and signals such as hangup; a plain descriptor is only an open handle. Commands that detach a process, create a new session, or redirect standard input and output can change these relationships independently.
When a remote SSH connection closes, the PTY master may close and the session can receive a hangup-related event. The exact result depends on the process tree, shell behavior, session management, and whether a supervisor reparented the workload. Do not promise that every descendant terminates or survives. Inspect ps and open descriptors after reproducing the behavior under the same login method and shell.
For long-running jobs, use a service manager, job supervisor, or deliberate session tool instead of relying on accidental PTY lifetime. A terminal multiplexer has its own server process and reattachment model; it is not the same as a PTY being immortal. Ensure the supervisor records exit status and logs output if process survival matters operationally.
Diagnose allocation failures without guessing at a limit
An application may fail to allocate a PTY because the system is under resource pressure, the relevant device or driver is unavailable, access policy blocks the operation, or the library call failed for another reason. Capture the actual errno and context. Do not assume a single global max_ptys tunable without checking the installed pts(4), kernel configuration, and release-specific implementation.
Inspect the device tree and kernel messages:
ls -ld /dev/pts
ls -l /dev/ptmx /dev/pts 2>/dev/null
dmesg | tail -80
kldstat
The presence and mode of nodes depend on the driver and devfs configuration. Do not create terminal device nodes manually with mknod as a persistent repair. Device-node ownership and lifecycle belong to the system’s device management. If a jail has a restricted devfs ruleset, verify that the intended terminal nodes are visible there without broadening access to unrelated device classes.
Collect resource evidence such as process counts, open descriptor counts, kernel messages, and the precise allocation error. Compare a successful allocation by a controlled test user with the failing service account. This can distinguish permissions and jail visibility from system-wide pressure. Avoid launching a loop that creates many PTYs on a production host just to find a limit; a bounded test on an isolated system is safer.
Use PTY tools without altering the session unexpectedly
script(1) can record a terminal session, but recording does not make command output a structured log or capture all external state. If you need reproducibility, record the command, environment, system release, timestamps, and exit status in addition to the transcript. Terminal control sequences may affect how a transcript renders, and secrets entered interactively may be written to the recording.
When an interactive application misbehaves, compare a real terminal run with a non-interactive invocation only if the program supports both modes. Tools may change output buffering or feature selection when isatty(3) changes. A pipe is not a PTY; adding script or a pseudo-terminal wrapper can alter signals, buffering, echo, and job-control behavior.
When debugging a stuck process, identify the owner of the master side before terminating anything. Killing a terminal emulator, SSH process, shell, or application can have different consequences. Confirm which process owns each endpoint and which process group is foreground before sending signals. Prefer an orderly application shutdown, then verify that the session and descriptors have been released.
Acceptance checks
For a terminal service or automation harness, verify a successful PTY allocation, correct slave ownership and mode, expected line settings, signal behavior, and cleanup after normal exit and abrupt client loss. Record whether the process should have a controlling terminal. In jails, test the exact devfs policy and execution account. In a production incident, preserve process and descriptor state before sending signals.
The practical benefit of the PTY model is isolation between terminal-facing software and the process using a terminal. The operational risk is assuming that a path alone describes session ownership. Trace the master, slave, process, and session as separate pieces, then make cleanup decisions from their live state.
Related:
- FreeBSD fdescfs Operations: Expose Open Descriptors Through /dev/fd
- FreeBSD script(1): Record Terminal Sessions for Repeatable Operations
Sources: