Terminal Line Discipline and termios: Canonical Input, Signals, Echo, and Raw Mode
A systems-level guide to POSIX terminal input processing, canonical and noncanonical reads, echo, signals, control characters, job control, and restoration.
A terminal is not just a byte stream between keyboard and process. The kernel terminal driver applies a line discipline controlled through termios: it can buffer complete lines, echo input, translate characters, generate signals, and perform output processing. Full-screen programs deliberately change these settings and must restore them across normal exit, failure, suspension, and resume.
Canonical mode edits a line in the kernel
With ICANON, input is assembled until an end-of-line or end-of-file condition. Configured erase and kill characters edit that pending line, and reads normally return completed lines. ECHO and related flags determine what appears on the terminal. The shell’s familiar line editing may add a userspace library such as Readline on top, but canonical discipline remains the baseline for many programs.
Without ICANON, bytes become available according to VMIN and VTIME. Their four combinations define whether a read waits for a minimum count, returns immediately, or uses an interbyte/overall timeout. Treating VTIME as a general millisecond timeout is wrong: its unit and semantics are defined by POSIX terminal behavior.
ISIG makes configured control characters such as interrupt, quit, and suspend generate signals for the foreground process group. Clearing ISIG in a raw-mode interface means Ctrl-C becomes an ordinary byte unless the application implements an exit binding. That increases the importance of a tested restoration path.
Raw mode is a collection of choices
Utilities and libraries often offer a “raw” helper that disables canonical input, echo, signals, input translations, and output processing. Those are separable flags. A password prompt may need no echo while retaining canonical line input and signals. A serial protocol may need raw bytes but is not necessarily an interactive controlling terminal.
From a shell, inspect a reproducible representation before experiments:
saved=$(stty -g) || exit
trap 'stty "$saved"' EXIT HUP INT TERM
stty -echo
IFS= read -r secret
printf '\n'
This simple example still needs care: the trap runs in the shell attached to the same terminal, the saved value must not be interpreted as arbitrary source, and concurrent programs must not fight over settings. Prefer a language/library password-input primitive for production.
Job control introduces suspension
The terminal tracks a controlling session and foreground process group. Background reads/writes may trigger SIGTTIN or SIGTTOU under job-control settings. A full-screen program should restore cooked settings before stopping itself on SIGTSTP, then re-enter its chosen mode and redraw after SIGCONT. Handling only process exit leaves the user’s shell in raw mode after Ctrl-Z.
Terminal dimensions arrive through ioctl and can change via SIGWINCH; escape sequences are separate bytes interpreted by the terminal emulator, not termios flags. Setting raw mode does not make arbitrary escape output safe or portable.
Own and restore exact prior state
Native programs call tcgetattr(), copy the entire structure, modify only intended flags/control characters, apply with tcsetattr(), and restore the saved copy. Do not restore to a hard-coded “sane” template on normal exit; the user may have intentional nondefault settings. Decide whether queued input/output should drain or flush through the documented action argument.
Test regular exit, initialization failure after mode change, Ctrl-C, quit, suspend/resume, terminal close, shell pipeline/non-TTY input, SSH disconnect, and nested tools. The correct terminal program treats termios as borrowed process-wide state with a precise owner and lifecycle, not a global switch called “raw.”
Related:
- Bracketed Paste Mode: How Terminals and Shells Mark Pasted Text Safely
- Why printf Is the Portable Shell Output Primitive and echo Is Not
Sources: