Linux Terminal Modes Cooked, Cbreak, Raw & MIN/TIME Portability

 

Linux Terminal Modes
Chapter 62 ยท Part 6A โ€” Cooked, Cbreak, Raw & MIN/TIME Portability
๐Ÿ“– Theory Deep-Dive
โšก Embedded Critical
๐ŸŽฏ Interview Ready

What You Will Learn

When a Linux process reads from a terminal, the terminal driver can process characters in very different ways depending on the mode it is operating in. This section explains the three classic modes โ€” Cooked, Cbreak, and Raw โ€” and a subtle portability trap around the VMIN and VTIME noncanonical settings. Understanding these modes is essential for writing any interactive terminal application: serial consoles, embedded shells, editors, and BLE device configuration CLIs all depend on correct mode switching.

Key Terms

Cooked Mode Cbreak Mode Raw Mode Canonical Mode Noncanonical Mode VMIN / VTIME VEOF / VEOL termios c_cc array ICANON ISIG ECHO tcgetattr tcsetattr

1. The VMIN / VEOF Portability Trap

In noncanonical mode the c_cc array has two important slots: c_cc[VMIN] (minimum bytes before read returns) and c_cc[VTIME] (timeout in tenths of a second). In canonical mode the same array holds c_cc[VEOF] (the EOF character, default Ctrl-D) and c_cc[VEOL] (additional line-delimiter character).

The problem: SUSv3 (the POSIX standard) allows VMIN and VEOF to share the same array index, and similarly VTIME and VEOL may share an index. On Linux these constants have distinct values, so there is no overlap. But on some older or embedded UNIX systems they are the same index.

c_cc Index Canonical Mode Name Noncanonical Mode Name Risk on Portable UNIX?
e.g. index 4 VEOF (Ctrl-D = 0x04) VMIN (e.g. set to 1) โš  Same slot on some UNIX
e.g. index 11 VEOL (extra line end) VTIME (timeout) โš  Same slot on some UNIX
Linux: VMIN โ‰  VEOF index and VTIME โ‰  VEOL index โ€” safe, but write portable code anyway.

Why does this matter in practice? Suppose your program:

  1. Reads the original terminal settings with tcgetattr().
  2. Switches to noncanonical mode and sets c_cc[VMIN] = 1.
  3. Later tries to return to canonical mode by toggling the ICANON flag.

On a system where VMIN == VEOF (same constant value), step 2 silently overwrote the EOF character with the value 1 (ASCII SOH, not Ctrl-D). When canonical mode is restored in step 3, the user presses Ctrl-D and nothing happens โ€” EOF is now value 1, which is nearly impossible to type.

The portable, correct solution:

/* Save BEFORE switching to noncanonical */
struct termios saved, t;
tcgetattr(STDIN_FILENO, &saved);   /* snapshot of canonical settings */

/* Switch to noncanonical */
tcgetattr(STDIN_FILENO, &t);
t.c_lflag &= ~(ICANON | ECHO);
t.c_cc[VMIN]  = 1;
t.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);

/* ... do work ... */

/* Restore using the SAVED structure, not by fiddling flags */
tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved);  /* safe on all UNIX */

The saved structure still has the original VEOF = 0x04 (Ctrl-D), so restoring it puts the terminal back exactly as it was.

Golden Rule: Always save a full copy of struct termios before making any mode change, and use that copy to restore. Never reconstruct the original settings by toggling individual flags.

2. The Three Classic Terminal Modes

Seventh Edition UNIX (1979) and the early BSD terminal driver defined three named modes. Although modern POSIX replaced the single-bit RAW/CBREAK switches with the full termios structure, the concepts remain central to terminal programming. Every interactive application โ€” from vi to a BLE configuration shell โ€” maps to one of these modes.

Feature ๐Ÿณ Cooked ๐Ÿ’ฅ Cbreak ๐Ÿ”ฉ Raw
Input available Line by line Char by char Char by char
Line editing (Backspace etc.)? โœ“ Yes โœ— No โœ— No
Signal chars (Ctrl-C/Z/\)? โœ“ Yes โœ“ Yes โœ— No
START/STOP (Ctrl-S/Q) flow? โœ“ Yes โœ“ Yes โœ— No
Other special chars (EOF, EOL)? โœ“ Yes โœ— No โœ— No
Other input processing (ICRNL etc.)? โœ“ Yes โœ“ Yes โœ— No
Output processing (OPOST etc.)? โœ“ Yes โœ“ Yes โœ— No
Input echoed? โœ“ Yes โš  Maybe โœ— No

โš  Cbreak echoing: typically disabled by the application (e.g. password input), but the mode itself does not mandate it.

3. Cooked Mode (Canonical + Full Processing)

Cooked mode is simply canonical mode with all the default special-character processing left enabled. This is what you get on a fresh terminal โ€” the normal shell prompt experience.

Keyboard
keystroke
โ†“ line
discipline
ECHO back
to screen
Editing buffer
(Backspace works)
Process gets
full line on Enter

What is active in Cooked mode:

  • ICANON โ€” input collected line by line; backspace erases.
  • ECHO โ€” typed characters echoed to screen.
  • ISIG โ€” Ctrl-C sends SIGINT, Ctrl-Z sends SIGTSTP, Ctrl-\ sends SIGQUIT.
  • ICRNL โ€” incoming CR (0x0D from Enter key) mapped to NL (0x0A).
  • OPOST โ€” output processing: NL expanded to CR+NL for display.
  • Special chars โ€” EOF (Ctrl-D), EOL all work normally.

When to use: Normal shell, simple line-input programs (scanf, fgets). The kernel does all the work for you.

4. Cbreak Mode (Char-by-Char with Signals)

Cbreak mode sits between Cooked and Raw. The application receives each keystroke immediately (no waiting for Enter), but signals (Ctrl-C, Ctrl-Z) and some I/O transformations still work.

Keyboard
keystroke
โ†“ ICANON OFF
no line buffer
ISIG ON
Ctrl-C works โœ“
Process gets
char immediately

What is ON in Cbreak:

  • ISIG โ€” signal-generating characters still active (Ctrl-C, Ctrl-Z, Ctrl-\).
  • Input/output transformations (ICRNL, OPOST) can still be configured independently.

What is OFF in Cbreak:

  • ICANON โ€” no line buffering, no backspace editing.
  • ECHO โ€” usually disabled (application echoes manually if needed).
  • Special chars beyond signal chars (VEOF, VEOL, VSTART, VSTOP, etc.) are ignored.

Classic use cases:

  • less, more โ€” page through output one key at a time, Ctrl-C still quits.
  • vim command mode โ€” immediate response to h/j/k/l, but Ctrl-Z suspends as expected.
  • Any full-screen TUI app using ncurses: the library’s cbreak() function sets this mode.

Key flag to set: disable ICANON and ECHO, keep ISIG.

t.c_lflag &= ~(ICANON | ECHO);   /* OFF: line-by-line, echo */
t.c_lflag |=  ISIG;              /* ON:  Ctrl-C/Z/\ still work */
t.c_iflag &= ~ICRNL;            /* optional: stop CRโ†’NL mapping */
t.c_cc[VMIN]  = 1;              /* return after 1 character */
t.c_cc[VTIME] = 0;              /* block indefinitely */

5. Raw Mode (Complete Control, Zero Processing)

Raw mode is the opposite extreme of Cooked. The terminal driver becomes a transparent pipe: every byte typed reaches your program unchanged, and every byte your program writes goes to the terminal unchanged.

Keyboard
byte
NO ICANON
NO ISIG
NO ECHO
NO input
transformations
NO output
transformations
Process gets
raw byte

Everything that gets turned OFF in Raw mode:

Flag What it controls Effect of turning OFF
ICANON Canonical (line) mode Chars available one at a time immediately
ISIG Signal generation Ctrl-C, Ctrl-Z, Ctrl-\ pass as raw bytes
IEXTEN Extended input processing LNEXT (Ctrl-V), DISCARD (Ctrl-O) disabled
ECHO Input echoing Typed chars not echoed; app must do it
BRKINT BREAK โ†’ SIGINT BREAK condition passes as null byte
ICRNL CR โ†’ NL mapping on input CR (0x0D) reaches process as-is
IXON START/STOP flow control Ctrl-S/Ctrl-Q pass as raw bytes
OPOST Output post-processing NL not expanded to CR+NL; exact bytes sent
INPCK / ISTRIP / PARMRK Parity checking & 8th-bit strip All 8 bits pass through unmodified

Raw mode is used for:

  • Serial line forwarding / pass-through (modem communication).
  • Full-screen editors (vim itself uses raw for its internal I/O layer).
  • Embedded firmware update tools โ€” every byte matters, no transformations allowed.
  • BLE HCI sniffer tools that read raw bytes from a UART-attached dongle.
  • Binary protocol terminals (XMODEM, ZMODEM file transfer over serial).

6. Choosing the Right Mode โ€” Decision Guide

Do you need byte-by-byte (immediate) input?
NO
Use Cooked Mode
shell, fgets(), scanf()
YES
Do you need Ctrl-C/Z to send signals?
YES
Use Cbreak Mode
less, ncurses, pagers
NO
Use Raw Mode
serial relay, editors, binary protocols

7. ncurses: cbreak() and raw() Shortcuts

Applications that use the ncurses library do not need to manipulate termios directly. ncurses provides wrapper functions that handle all the flag setting for you:

#include <ncurses.h>

int main(void)
{
    initscr();        /* initialise ncurses, opens terminal */
    cbreak();         /* equivalent to Cbreak mode: char-by-char, signals kept */
    /* OR */
    raw();            /* equivalent to Raw mode: all processing off */

    noecho();         /* turn off echo (ncurses manages display itself) */
    keypad(stdscr, TRUE);  /* enable F-keys, arrow keys */

    int ch = getch(); /* blocks until one character typed */
    endwin();         /* restore terminal */
    return 0;
}

Behind the scenes cbreak() calls tcsetattr() after clearing ICANON and ECHO while keeping ISIG, just like our manual implementation in Part 6B. raw() additionally clears ISIG and all input processing flags.

Tip: If you are writing a production TUI application, prefer ncurses over manual termios manipulation โ€” ncurses handles portability, terminal capability detection (via terminfo), and restores the terminal correctly even on crash via endwin().

๐ŸŽฏ Interview Questions

Q1. What is the difference between Cooked mode and Cbreak mode?

In Cooked mode input is buffered line by line; the process receives a full line only when the user presses Enter, and line-editing (Backspace) works. In Cbreak mode (ICANON off), each keystroke is delivered immediately to the process without waiting for Enter and without backspace editing. However, signal- generating characters (Ctrl-C โ†’ SIGINT, Ctrl-Z โ†’ SIGTSTP) still work in Cbreak, whereas in Raw mode they are disabled too.

Q2. Why is it unsafe to toggle only the ICANON flag when returning from noncanonical mode?

On some UNIX implementations VMIN and VEOF share the same c_cc array index. Setting c_cc[VMIN] = 1 in noncanonical mode therefore silently overwrites the EOF character. When you later toggle ICANON back on, EOF is no longer Ctrl-D (0x04) and the terminal becomes unusable. The safe approach is to save the entire struct termios before making any change and restore that saved copy.

Q3. Which flags must be cleared to put a terminal into Raw mode?

c_lflag: ICANON | ISIG | IEXTEN | ECHO
c_iflag: BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK
c_oflag: OPOST
Additionally set c_cc[VMIN]=1 and c_cc[VTIME]=0 for blocking single-character input.

Q4. What does OPOST control and why must it be disabled in Raw mode?

OPOST enables output post-processing โ€” the most common effect is expanding a newline (0x0A) to a CR+NL pair (0x0D 0x0A) for display. In Raw mode the application is responsible for sending exact byte sequences (e.g., binary firmware images, XMODEM packets, BLE HCI commands) so any automatic character substitution would corrupt the data. Clearing OPOST ensures the kernel writes exactly what the application sends.

Q5. When would you choose Cbreak mode over Raw mode in an embedded Linux application?

Choose Cbreak when you need immediate character-by-character input but still want the OS to handle process control signals. For example, a BLE device configuration CLI on an embedded board: the user presses single keys to navigate menus (requiring immediate delivery, so not Cooked), but pressing Ctrl-C should abort the program cleanly via SIGINT rather than passing the 0x03 byte to the application logic. Use Raw mode only when you need to intercept all input including control characters โ€” for instance, when implementing your own signal handling or forwarding bytes over a serial link.

Q6. What is the difference between VMIN/VTIME and VEOF/VEOL in the c_cc array?

VEOF and VEOL are indices used in canonical mode: c_cc[VEOF] holds the EOF character (default Ctrl-D) and c_cc[VEOL] holds an optional additional line delimiter. VMIN and VTIME are indices used in noncanonical mode: c_cc[VMIN] is the minimum number of bytes before a read() returns, and c_cc[VTIME] is a timeout in tenths of a second. SUSv3 allows these pairs to share the same physical array slot because the two sets are never active simultaneously, but on Linux they have distinct constant values.

Q7. What does ncurses cbreak() do that differs from ncurses raw()?

Both disable line buffering (ICANON) for character-by-character input. cbreak() keeps ISIG active so Ctrl-C/Z/\ still send signals. raw() additionally clears ISIG and all input/output processing, giving the application complete control over every byte โ€” no automatic signal generation, no CR/NL mapping, no flow control.

Continue Learning

Next: See how to implement ttySetCbreak() and ttySetRaw() in C with full signal handling

โ†’ Part 6B: ttySetCbreak & ttySetRaw Code EmbeddedPathashala Home

Leave a Reply

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