Linux Terminals Local Flags (c_lflag), Echo Behavior & BRKINT

 

Linux Terminals โ€“ Chapter 62.5
Terminal Flags | Part 3: Local Flags (c_lflag), Echo Behavior & BRKINT
๐Ÿ“š Part 3 of 4
๐Ÿ”Š Echo Flags
โšก BRKINT & Signals

What is c_lflag?

The c_lflag field controls the line discipline โ€” the high-level behaviors that make the terminal feel like a terminal rather than a raw byte pipe. This is where things like:

  • Echo (seeing what you type)
  • Canonical mode (line-by-line editing with backspace, kill-line)
  • Signal generation (Ctrl+C generating SIGINT)
  • Extended processing

… are all controlled. In this part we also cover the BRKINT flag from c_iflag in detail, since it relates closely to signal generation behavior.

Keywords in This Part:

c_lflag ECHO ECHOCTL ECHOE ECHOK ECHOKE ECHONL ECHOPRT ICANON ISIG IEXTEN NOFLSH TOSTOP BRKINT

Local Flags โ€” c_lflag: Complete Reference
Flag Default What it does (plain English)
ECHO ON Echo typed characters back to the terminal. Disable to hide input (e.g., passwords).
ECHOCTL ON When ECHO is ON: echo control characters (like Ctrl+A) visually as ^A notation instead of raw bytes.
ECHOE ON In canonical mode: visually erase a character when ERASE key (usually Backspace/DEL) is pressed, using backspace-space-backspace sequence.
ECHOK ON In canonical mode: visually erase the entire line when KILL (Ctrl+U) is pressed.
ECHOKE ON Controls how KILL is echoed. If ON: don’t output a newline after visual KILL erase. Works with ECHOK.
ECHONL OFF In canonical mode: echo the NL (newline) character even when ECHO is disabled. Useful to see line boundaries during password input.
ECHOPRT OFF For hardcopy terminals: when erasing a character, echo the deleted character between \ and / to show what was removed on paper.
FLUSHO Status flag: indicates output is currently being flushed. Not a controllable flag; read-only status.
ICANON ON Canonical (line-by-line) mode. When ON, input is buffered by line โ€” program only gets data after Enter is pressed. When OFF, noncanonical (raw) mode.
IEXTEN ON Enable extended processing of implementation-defined special characters. Also required for IUCLC to work on input.
ISIG ON Enable signal-generating characters. When ON, Ctrl+C โ†’ SIGINT, Ctrl+\ โ†’ SIGQUIT, Ctrl+Z โ†’ SIGTSTP.
NOFLSH OFF Normally, INTR/QUIT/SUSP signals flush the input and output queues. Setting NOFLSH disables that flush.
PENDIN (OFF) Re-display pending input at next read. Not implemented on Linux.
TOSTOP OFF Generate SIGTTOU when a background process tries to write to the terminal. Causes background processes to stop if they try to produce output.
XCASE (OFF) Uppercase/lowercase presentation for uppercase-only terminals. Unimplemented on Linux.

Echo Flags Explained in Depth

ECHO โ€” The Master Echo Switch

When ECHO is ON, every character you type is immediately sent back to the terminal screen. This is what makes your typing visible. Without it, you’d be typing blind.

Turning off ECHO is exactly how password input works โ€” the terminal driver stops echoing characters while getpass() or similar functions are running. The characters still reach the program; they just aren’t shown on screen.

ECHOCTL โ€” Making Control Characters Readable

Control characters (ASCII codes < 32, plus DEL = 127) are normally invisible or cause side effects if echoed literally. ECHOCTL causes them to be displayed as ^X notation:

Key pressed ASCII value ECHOCTL shows Calculation (x XOR 64)
Ctrl+A 1 ^A 1 XOR 64 = 65 = ‘A’
Ctrl+C 3 ^C 3 XOR 64 = 67 = ‘C’
Ctrl+L 12 ^L 12 XOR 64 = 76 = ‘L’
DEL (Backspace) 127 ^? 127 XOR 64 = 63 = ‘?’
How the XOR trick works: For control characters (ASCII 1โ€“26), adding 64 gives the corresponding uppercase letter (‘A’ to ‘Z’). So the ECHOCTL notation ^X is produced by outputting a caret (^) followed by the character at (x XOR 64). For DEL (127), subtracting 64 gives 63 which is ‘?’, hence ^?.

ECHOE โ€” Visual Erase (Backspace)

In canonical mode, when you press Backspace (the ERASE character), two things can happen:

  • If ECHOE is ON: the driver sends backspace-space-backspace to the terminal. This visually erases the character by moving back, writing a space over it, then moving back again.
  • If ECHOE is OFF: the ERASE character is still functional (deletes from buffer) but visually it shows something like ^? instead of actually erasing the character on screen.

ECHOK / ECHOKE โ€” Kill Line (Ctrl+U)

When you press Ctrl+U (the KILL character) in canonical mode, the entire current line is discarded. The flags ECHOK and ECHOKE control how this is shown:

ECHOK ECHOKE Visual behavior on Ctrl+U
ON ON (default) Line is visually erased (using backspace-space-backspace per character). No newline added.
ON OFF Line is visually erased, then a newline is output (cursor moves to next line).
OFF any No visual erase โ€” KILL character is echoed (e.g., ^U). Buffer is still discarded.

ECHOPRT โ€” For Hardcopy (Paper) Terminals

This was designed for teletype and paper-based terminals where you couldn’t erase what was already printed. When ECHOPRT is ON and you press Backspace, the deleted character appears between \ and /:

/* Typing "hello" then pressing Backspace twice with ECHOPRT ON: */
hello\o/
/* The \o/ shows "o" was deleted, then pressing another backspace: */
hello\o\l//
/* Each deleted character is shown in this notation */

This is essentially useless on modern screen terminals.

ISIG โ€” Signal-Generating Characters

When ISIG is ON (default), pressing certain key combinations causes the terminal driver to send a signal to the foreground process group rather than passing the character to the program:

Key Character termios name Signal sent Default action
Ctrl+C ASCII 3 c_cc[VINTR] SIGINT Terminate program
Ctrl+\ ASCII 28 c_cc[VQUIT] SIGQUIT Core dump + terminate
Ctrl+Z ASCII 26 c_cc[VSUSP] SIGTSTP Suspend (stop) process

When ISIG is OFF, these key combinations are treated as regular characters and passed to the program’s read(). This is needed when implementing interactive programs like text editors (vi, emacs) or terminal emulators that want to handle Ctrl+C themselves.

Important: When ISIG characters trigger a signal, the terminal driver normally also flushes the input and output queues. This can be disabled by setting NOFLSH.

BRKINT โ€” BREAK Condition and SIGINT

A BREAK condition is a special serial-line event โ€” not a regular character. It is generated when the serial line is held in a “spacing” state (logical 0) for longer than the time it takes to send one full byte. Typically 0.25 to 0.5 seconds.

On old physical terminals, there was a physical BREAK key. On virtual consoles, you can generate a BREAK by pressing Ctrl+Break.

Flag combination What happens on BREAK
IGNBRK ON BREAK is completely ignored โ€” nothing happens
IGNBRK OFF, BRKINT ON SIGINT is sent to foreground process group; input/output queues are flushed
IGNBRK OFF, BRKINT OFF A single NUL byte (0x00) is delivered to the reading program; or if PARMRK is set, \377\0\0 is delivered

On historical multi-speed serial systems, the BREAK key told the remote host to try a different baud rate. The user would press BREAK repeatedly until a valid login prompt appeared at a baud rate the terminal could handle.

ICANON โ€” Canonical vs. Noncanonical Mode

This is one of the most important flags. It controls how input is delivered to your program:

Mode ICANON Behavior Use case
Canonical ON (default) Input is buffered line by line. The program’s read() blocks until the user presses Enter. Backspace, Ctrl+U, etc. work. Shell, normal command input
Noncanonical OFF Input is delivered character-by-character (or N characters) based on VMIN/VTIME settings. No line buffering, no special character processing. Text editors, games, terminal emulators, embedded device I/O

TOSTOP โ€” Stopping Background Output

When TOSTOP is ON and a background process tries to write to the terminal, the terminal driver sends SIGTTOU to that process, which stops it. The user must then bring the process to the foreground (fg) for it to write.

This is part of the job control system. By default TOSTOP is OFF, meaning background processes can freely write to the terminal โ€” which can cause jumbled output if a background process outputs text while you’re typing.

Code Example: Reading a Password Without Echo

This is the most common real-world use of c_lflag manipulation โ€” disabling echo to read a password securely.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#define MAX_PASS 128

/*
 * read_password: reads from stdin with echo disabled.
 * buf: caller-supplied buffer
 * len: size of buffer
 * Returns: number of characters read (excluding NUL)
 */
int read_password(char *buf, size_t len)
{
    struct termios orig, noecho;

    /* Save original settings */
    if (tcgetattr(STDIN_FILENO, &orig) != 0) {
        perror("tcgetattr");
        return -1;
    }

    /* Copy and disable ECHO */
    noecho = orig;
    noecho.c_lflag &= ~ECHO;       /* Turn off echo */
    noecho.c_lflag &= ~ECHONL;     /* Don't echo newline either */

    /* Apply no-echo settings */
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &noecho) != 0) {
        perror("tcsetattr");
        return -1;
    }

    printf("Enter password: ");
    fflush(stdout);

    /* Read the password (user presses Enter, canonical mode still active) */
    if (fgets(buf, len, stdin) == NULL) {
        /* Restore before returning on error */
        tcsetattr(STDIN_FILENO, TCSANOW, &orig);
        return -1;
    }

    /* Remove trailing newline */
    int n = strlen(buf);
    if (n > 0 && buf[n - 1] == '\n') {
        buf[--n] = '\0';
    }

    /* Print newline since we didn't echo it */
    printf("\n");

    /* Restore original echo settings */
    if (tcsetattr(STDIN_FILENO, TCSANOW, &orig) != 0) {
        perror("tcsetattr restore");
    }

    return n;
}

int main(void)
{
    char password[MAX_PASS];

    int n = read_password(password, sizeof(password));
    if (n < 0) {
        fprintf(stderr, "Failed to read password\n");
        return 1;
    }

    /* In real code, you would verify the password here, not print it! */
    printf("Password length: %d characters\n", n);

    /* Securely clear the password from memory when done */
    memset(password, 0, sizeof(password));

    return 0;
}
Security note: Always call memset() to clear the password buffer after use. This prevents the password from remaining in memory where it could be read by a later process or in a core dump.

Code Example: Raw Mode (Disable ICANON, ECHO, ISIG)

Raw mode is used by terminal applications like vim, less, and games. In raw mode, every keypress is immediately available to the program without waiting for Enter.

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

struct termios saved_termios;

void enable_raw_mode(void)
{
    struct termios raw;

    /* Save current settings for restoration later */
    if (tcgetattr(STDIN_FILENO, &saved_termios) != 0) {
        perror("tcgetattr");
        exit(1);
    }

    raw = saved_termios;

    /* c_lflag: disable canonical mode, echo, signal generation */
    raw.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ISIG | IEXTEN);

    /* c_iflag: disable CR/NL translation, flow control */
    raw.c_iflag &= ~(ICRNL | IXON | IXOFF | INLCR);

    /* c_oflag: disable output post-processing */
    raw.c_oflag &= ~OPOST;

    /* Noncanonical read: return after each single character */
    raw.c_cc[VMIN]  = 1;    /* Read returns as soon as 1 byte is available */
    raw.c_cc[VTIME] = 0;    /* No timeout */

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) {
        perror("tcsetattr");
        exit(1);
    }
}

void disable_raw_mode(void)
{
    /* Restore saved settings */
    if (tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios) != 0) {
        perror("tcsetattr restore");
    }
}

int main(void)
{
    enable_raw_mode();

    printf("Raw mode ON. Type characters (press 'q' to quit):\r\n");

    char c;
    while (1) {
        /* Each read() returns immediately after 1 character (VMIN=1, VTIME=0) */
        if (read(STDIN_FILENO, &c, 1) <= 0)
            break;

        /* Print hex value of each character pressed */
        printf("Key: 0x%02X  (%c)\r\n", (unsigned char)c,
               (c >= 32 && c < 127) ? c : '.');

        if (c == 'q')
            break;
    }

    disable_raw_mode();
    printf("\nRaw mode OFF. Back to normal.\n");
    return 0;
}
Why \r\n instead of \n in raw mode: Because we disabled OPOST, the automatic NLโ†’CR+NL conversion is gone. We must manually write \r\n to get proper line behavior on screen. This is the raw mode “gotcha” that catches many beginners.

Interview Questions โ€” c_lflag, Echo & BRKINT

Q1. What does the ECHO flag control and what is a practical use of disabling it?

ECHO controls whether typed characters are immediately echoed back to the terminal. The most common use of disabling it is reading passwords โ€” the user’s keystrokes are received by the program normally, but nothing appears on screen. This is how commands like sudo and ssh implement password prompts.

Q2. How does the terminal driver echo a control character like Ctrl+C when ECHOCTL is on?

The terminal driver outputs a caret (^) followed by the character obtained by XOR-ing the control character’s ASCII value with 64. For Ctrl+C (ASCII 3): 3 XOR 64 = 67, which is ‘C’, so it is shown as ^C. For DEL (ASCII 127): 127 XOR 64 = 63, which is ‘?’, shown as ^?.

Q3. What is canonical mode and when would you disable it?

Canonical mode (ICANON ON) means the terminal driver buffers input line by line, delivering data to the program only when the user presses Enter. The driver also processes special characters like Backspace and Ctrl+U in this mode. You disable it for applications that need character-by-character input: text editors (vim, emacs), terminal emulators, games, or any interactive program where you don’t want to wait for Enter.

Q4. What is the ISIG flag and which signals can it generate?

ISIG enables signal-generating key combinations. When ON: Ctrl+C โ†’ SIGINT, Ctrl+\ โ†’ SIGQUIT, Ctrl+Z โ†’ SIGTSTP. These signals are sent to the foreground process group. When ISIG is OFF (as in raw/vi mode), these keystrokes are just passed as regular characters to the program, which handles them itself.

Q5. What is a BREAK condition and how does BRKINT affect it?

A BREAK condition is a serial-line event where the line is held in a spacing state (logical 0) for longer than one full byte time (typically 0.25โ€“0.5 seconds). It is not a data character. When BRKINT is ON and IGNBRK is OFF, a BREAK condition causes the terminal driver to send SIGINT to the foreground process group and flush queues. When IGNBRK is ON, BREAK is completely ignored.

Q6. In raw mode, why do you need to write \r\n explicitly instead of just \n?

In raw mode, OPOST is typically disabled, which turns off all output processing including the ONLCR flag. Normally ONLCR automatically converts \n to \r\n so the cursor returns to column 0. Without this, writing just \n only moves the cursor down one line but not back to column 0, causing staircase-shaped output. You must manually write \r\n to get proper new-line behavior.

Q7. What does NOFLSH do and when would you set it?

Normally, when ISIG causes a signal (INTR, QUIT, SUSP), the terminal driver flushes both input and output queues. NOFLSH disables this flushing. You might set it in a program that wants to handle signals but also continue processing whatever was already in the input buffer โ€” for example, a debugger or a custom shell that wants to recover cleanly after Ctrl+C rather than losing pending input.

Leave a Reply

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