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.
| 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 โ 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 = ‘?’ |
^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
ECHOEis 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
ECHOEis 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.
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.
NOFLSH.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.
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 |
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.
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;
}
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.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;
}
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.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.
