// lesson: termios-intro
Terminal Settings with termios
Everything the line discipline does β echo, buffering, signals,
translation β is configurable, per terminal device, through one struct
and two functions declared in termios.h:
#include <termios.h>
struct termios t;
tcgetattr(fd, &t); /* read the device's current settings */
t.c_lflag &= ~ECHO; /* flip some bits */
tcsetattr(fd, TCSAFLUSH, &t); /* write them back */
This is the API. stty is a thin wrapper over it; so is everything
vim or ssh does to a terminal. Note that both calls take an fd: the
settings belong to the device, not to your process. Change them and
they stay changed for everyone using that device until something
changes them back β which is why "restore on exit" is a hard
requirement, not a courtesy.
The struct, field by field
struct termios {
tcflag_t c_iflag; /* input processing: what happens to arriving bytes */
tcflag_t c_oflag; /* output processing: what happens to departing bytes */
tcflag_t c_cflag; /* hardware-ish: baud, character size, parity */
tcflag_t c_lflag; /* "local": the line discipline's personality */
cc_t c_cc[NCCS]; /* control characters: which byte means what */
};
Each tcflag_t is a bitmask; you flip flags with the usual idioms:
t.c_lflag &= ~(ECHO | ICANON); /* clear (disable) */
t.c_cflag |= CS8; /* set (enable) */
c_lflag β the big personality switches
| Flag | When set (the default) | Raw mode |
|---|---|---|
ICANON |
line buffering + kernel line editing (Backspace, ^U) | clear |
ECHO |
kernel echoes input back to the display | clear |
ISIG |
^CβSIGINT, ^ZβSIGTSTP, ^\βSIGQUIT | clear |
IEXTEN |
extended input processing (^V literal-next, ^O) | clear |
ICANON and ECHO are the headliners. Clearing ISIG means Ctrl+C
becomes just a byte (0x03) delivered to you β your program decides what
"interrupt" means. Clearing IEXTEN closes the odd loopholes.
c_iflag β input translation
| Flag | When set | Raw mode |
|---|---|---|
IXON |
^S/^Q pause and resume output (software flow control) | clear |
ICRNL |
translate incoming \r (Enter) into \n |
clear |
BRKINT |
serial "break" sends SIGINT | clear |
INPCK |
parity checking (serial-line era) | clear |
ISTRIP |
strip input bytes to 7 bits | clear |
With ICRNL cleared you'll see Enter as it truly arrives: \r, byte
0x0D. Your key decoder (a few lessons from now) must know that.
BRKINT, INPCK, ISTRIP are fossils of real serial hardware β
clearing them is tradition and costs nothing.
c_oflag β output translation
| Flag | When set | Raw mode |
|---|---|---|
OPOST |
enable all output processing | clear |
ONLCR |
(under OPOST) translate outgoing \n to \r\n |
β |
Clearing OPOST turns off all output massaging β most visibly
ONLCR. From then on \n moves the cursor down one row without
returning to column 0, staircasing your text like
first line
second line
third line
That's not a bug; it's two motions you're now responsible for distinguishing. Full-screen programs don't mind β they position the cursor explicitly anyway.
c_cflag and c_cc
c_cflag matters to us only for CS8: 8-bit characters, please (yet
another serial fossil β 7-bit terminals were real). In c_cc, the
control-character array, two "characters" aren't characters at all but
read-timing knobs β and they're important enough to get their own
section.
VMIN and VTIME: how much, how long
With ICANON cleared, when does read() return? Two bytes in c_cc
decide:
VMINβ the minimum number of bytes beforereadmay return.VTIMEβ a timeout in tenths of a second.
The four quadrants:
| VMIN | VTIME | read() behavior |
|---|---|---|
| 0 | 0 | never blocks: returns whatever is there, possibly 0 |
| >0 | 0 | blocks until VMIN bytes exist (classic blocking read) |
| 0 | >0 | returns on first byte OR after VTIME expires with 0 |
| >0 | >0 | inter-byte timer: first byte starts the clock |
VMIN=1, VTIME=0 is the sane default for interactive programs: block
until there's something, deliver it immediately. VMIN=0, VTIME=1
(return within 0.1 s no matter what) is the poor-man's event loop β
kilo (antirez's ~1000-line C text editor, the tutorial in this
course's extended reading, and a name that will keep coming up)
uses it. We'll use VMIN=1 and get our timeouts a better way, with
poll(), in the next lesson.
Applying settings: the third argument
tcsetattr(fd, when, &t) β the when matters:
TCSANOWβ apply immediately, even mid-output.TCSADRAINβ wait for pending output to finish first. Use when changing output flags so queued text isn't garbled.TCSAFLUSHβ drain output and discard pending unread input. The right choice when entering/leaving raw mode: keystrokes typed before the switch die instead of leaking into the new regime.
One more trap, straight from the man page: tcsetattr returns success
if any requested change succeeded. Paranoid code calls tcgetattr
afterwards and compares. (Our tests do exactly that to your code.)
Raw mode, assembled
Putting the whole lesson in one function β this is cfmakeraw(3)
reimplemented by hand, and it's the next challenge:
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~OPOST;
raw.c_cflag |= CS8;
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
And the discipline that goes with it, which every serious tool
follows: save the original struct before touching anything, restore
it on every exit path β normal exit, error, fatal signal. A program
that dies in raw mode leaves the shell deaf and mute (reset fixes
it, but users shouldn't need to know that incantation).
βΊ Enable Raw Mode
15 ptsThree functions:
termios_make_raw(struct termios *t)β mutate a settings struct into the raw configuration above. Pure: no fd, no syscalls, no side effects. (Testable to the last bit, and reusable on any fd.)raw_mode_enable(int fd, struct termios *saved)β snapshot the device's current settings into*saved, then apply the raw configuration to it withTCSAFLUSH. Return 0, or -1 on error.raw_mode_restore(int fd, const struct termios *saved)β put the snapshot back (againTCSAFLUSH). Return 0 or -1.
The tests run against a real pseudoterminal: they check every flag bit before and after, and then do the behavioral proof β in raw mode a single keystroke byte (no newline!) written to the master must be immediately readable from the slave.
Log in to submit a solution and earn points.
βΊ Read Timeouts with VMIN and VTIME
15 ptsBefore we abandon VMIN/VTIME for poll(), prove you can drive them β
plenty of real code (including kilo) does its event timing this way,
and understanding the quadrant table beats memorizing it.
Implement term_set_read_timing(fd, vmin, vtime): fetch the device's
current termios, set c_cc[VMIN] and c_cc[VTIME], apply with
TCSANOW. The tests put a pty slave into raw mode, then:
VMIN=0, VTIME=0with no data βreadreturns 0 immediately (the polling read).VMIN=0, VTIME=3with no data βreadblocks ~0.3 s, then returns 0. The test asserts the elapsed time is at least 0.15 s (it really waited) and under 3 s (it didn't hang).VMIN=0, VTIME=5with data already waiting βreadreturns it immediately, well before the timeout.
Log in to submit a solution and earn points.