7.1 Terminal line disciplines and modes
Between a terminal device and the processes reading it sits the kernel's terminal line discipline — the layer that turns raw keystrokes into a usable input stream:
- echoes characters back to the screen;
- buffers a line and handles editing (erase ^H, kill ^U);
- translates signals: ^C → SIGINT, ^Z → SIGTSTP, ^\ → SIGQUIT;
- maps newlines (NL ↔ CR-NL), controls flow (^S/^Q).
Terminal modes (set via tcgetattr/tcsetattr on struct termios):
| Mode | Behaviour | Used by |
|---|---|---|
| Canonical (cooked) | line-at-a-time, editing & signals in kernel | shells, line tools |
| Raw | byte-at-a-time, no echo, no special chars | editors (vi), ssh/telnet clients |
| Cbreak | byte-at-a-time but signals still work | more, games |
A remote-login client must go raw: every keystroke (even ^C!) belongs to the remote side, so nothing may be interpreted locally.
Control terminals & job control: every session has at most one controlling terminal; the foreground process group receives its signals; background groups that touch the terminal get SIGTTIN/SIGTTOU. (The daemon lesson's double-fork was exactly about escaping this machinery.)
7.2 Pseudo-terminals — the trick that makes remote login possible
The remote shell expects a terminal; the network delivers a socket. The adapter is the pseudo-terminal (pty): a kernel device pair —
- slave side: looks exactly like a terminal (line discipline and all) — the shell sits here, fooled completely;
- master side: whatever is written/read here appears as keyboard-input/screen-output on the slave — the server daemon sits here, splicing to the socket.
Server setup: open master (posix_openpt/grantpt/unlockpt/ptsname) → fork → child: setsid, open slave as controlling terminal, dup2 onto 0/1/2, exec login shell → parent: select-loop copying socket ↔ master. Ptys power far more than remote login: ssh, xterm and every GUI terminal, script, expect, tmux/screen — any program that must drive another program "as if from a keyboard".
7.3 rlogin overview
The BSD r-command for remote login (port 513, server rlogind) — simpler than telnet because both ends are Unix:
- Startup negotiation: client sends null-terminated strings — local username, remote username, terminal type/speed ("xterm/38400"); server (historically) trusted
.rhostsfor password-less entry. - Data flow: keystrokes → server → pty master → shell; output back the same path. Client raw; the slave's line discipline does the real terminal work — the architecture above, verbatim.
- In-band control from server: byte 0x02 + command in the TCP urgent data: flush output, toggle raw, request window size. Urgent mode lets these bypass a full pipe (output flooding) — the textbook's showcase for OOB data.
- Out-of-band-ish control from client: window-size changes (the client catches SIGWINCH) sent as a magic in-band sequence; the server pushes it to the pty with ioctl(TIOCSWINSZ) so
vire-draws at the new size. - Flow control (^S/^Q) handled client-side for responsiveness (when the server permits).
Security postscript: rlogin/telnet send everything in cleartext, and .rhosts trust is address-based — both are museum pieces replaced by ssh, which keeps exactly this pty architecture and adds crypto. Exams ask rlogin because the architecture, not the protocol, is the lesson.
7.4 RPC transparency issues
RPC (remote procedure call) dresses a network exchange as a local function call. The classic question: can the disguise be perfect? No — the famous transparency issues:
| Issue | Local call | Remote call |
|---|---|---|
| Parameter passing | by-reference possible (shared memory) | only by-value/copy — pointers, graphs, shared structures don't travel |
| Data representation | one machine's formats | byte order, sizes, alignment, float formats differ → need XDR-style neutral encoding |
| Failure modes | call either completes or process dies | call may fail partway: request lost? server crashed before or after executing? reply lost? |
| Execution semantics | exactly once, always | at-least-once (retry — only safe if idempotent), at-most-once (dedup cache), exactly-once is unattainable over failures |
| Performance | nanoseconds | 10³–10⁶× slower, variable, can hang |
| Authentication/identity | same UID | caller may be anyone, anywhere |
The crash-timing taxonomy (request lost / server dies before executing / server dies after executing / reply lost) and the resulting at-least-once vs at-most-once choice is the expected exam answer. These issues — first catalogued for Sun RPC — apply unchanged to gRPC, REST retries and every microservice you will ever build: the network can be hidden from the syntax, never from the semantics. A fitting last sentence for the course.
7.5 Going raw, in code — the termios fragment
"Set a terminal to raw mode" is a standard short program; the shape matters more than every flag:
struct termios raw, saved;
tcgetattr(STDIN_FILENO, &saved); /* 1: fetch & KEEP the old state */
raw = saved;
raw.c_lflag &= ~(ECHO | ICANON | ISIG); /* 2: no echo, no line-editing,
no ^C→SIGINT translation */
raw.c_iflag &= ~(IXON | ICRNL); /* 3: no ^S/^Q, no CR→NL mapping */
raw.c_oflag &= ~OPOST; /* 4: no output processing */
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 5: read returns per byte */
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
/* ... the client's copy loop runs here ... */
tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved); /* 6: ALWAYS restore on exit */
The two viva-bait lines: clearing ISIG is what lets ^C travel to the remote host as an ordinary byte (the whole point of §7.1's "raw client" rule); and step 6 — restore on every exit path, including signals — is why a crashed ssh leaves your terminal garbled until you type reset: someone skipped it.
7.6 The rlogin data path, end to end — one keystroke's journey
Worked trace for "explain rlogin's operation": the user types v (running vi remotely):
- client terminal is raw → the byte reaches the client unprocessed, no local echo;
- client write()s it to the TCP connection (TCP_NODELAY territory — single-byte segments are the canonical Nagle case study);
- rlogind read()s it from the socket and write()s it to the pty master;
- the byte "appears typed" on the pty slave; the slave's line discipline is in whatever mode vi requested (raw too, as it happens) and hands it to vi;
- vi redraws; its output written to the slave appears as readable data on the master; rlogind copies master → socket;
- the client read()s the screen update and write()s it to the real terminal.
Count the echo's round trip: the character you see was echoed by the remote application, a full RTT later — why typing over a slow link feels the way it does. Every hop in this trace names a piece of machinery from §7.1–§7.3; reproducing it is the synthesis answer for this lesson.
7.7 rlogin vs telnet vs ssh — the protocol family table
| Criterion | rlogin | telnet | ssh |
|---|---|---|---|
| scope | Unix↔Unix only | any↔any (NVT abstraction) | any↔any |
| option negotiation | none — fixed assumptions | elaborate (WILL/WONT/DO/DONT) | key/cipher negotiation |
| encryption | none — cleartext | none — cleartext | everything encrypted |
| authentication | .rhosts address trust / password | password (cleartext!) | keys, passwords (encrypted), agent |
| server-side pty architecture | yes | yes | identical — the lesson's diagram unchanged |
| port | 513 | 23 | 22 |
| status | museum | museum (still a debugging tool) | the living descendant |
The table's moral, worth stating: the security layer was replaced wholesale; the terminal architecture — raw client, server splicing socket↔pty-master, line discipline at the slave — survived intact from rlogin to ssh. Architecture outlives protocols.
7.8 Structuring the RPC answer — semantics under each failure
The §7.4 taxonomy becomes a full-marks answer when arranged as a matrix — failure × retry policy:
| Failure moment | Client retries (at-least-once) | Client gives up (at-most-once flavour) |
|---|---|---|
| request lost in network | executes once — correct | executes zero times |
| server crashed before executing | executes once — correct | zero times |
| server crashed after executing | executes twice ⚠ | once |
| reply lost | executes twice ⚠ | once (but client doesn't know!) |
The two ⚠ cells are the whole argument: the client cannot distinguish "request never arrived" from "reply never returned", so blind retry risks re-execution — harmless for idempotent operations (read block N of a file), catastrophic for non-idempotent ones (append; debit ₹500). At-most-once implementations therefore carry request IDs + a server-side duplicate-reply cache; exactly-once under crashes would additionally require the server to make "execute + record" atomic across reboots — which is why it's described as unattainable in general. Define idempotent, give one example of each kind, and place them in this matrix: that is the complete expected answer.
Exam pointers
- "What does the terminal line discipline do? Distinguish canonical, raw and cbreak modes" — the §7.1 list + table; add which flag bits (ICANON, ECHO, ISIG) §7.5 clears for each property.
- "What is a pseudo-terminal? How does it enable remote login?" — the master/slave pair, who sits where (diagram), then the server-setup call sequence; name three non-login users of ptys (xterm, script, tmux/expect).
- "Explain RPC transparency issues" — the §7.4 table for breadth, the §7.8 matrix for the semantics question; always define idempotence — it's the hinge of the marking scheme.
- "How does the rlogin server flush output urgently?" — the 0x02-command-in-urgent-data mechanism, and why OOB: the command must overtake a pipe full of normal output.
Check yourself
- In the keystroke trace of §7.6, which component performs echo when running a canonical-mode shell remotely — and what would double-echo indicate?
- Why must the client be raw while the slave may be in any mode? Who decides the slave's mode?
- A program wants to drive an interactive password prompt of another program. Why does a pipe fail where a pty succeeds? (What does the driven program check on its stdin?)
- Classify: NFS read-block, DNS query, bank transfer, "append line to log". Which retry policy suits each, and why?
- ssh keeps rlogin's architecture but added one byte-stream complication: everything is encrypted, including window-size messages. Where in the §7.6 path must encryption/decryption sit?