Project 1: PTY Echo Chamber
Build a PTY bridge that proxies a shell and logs all I/O.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 1 week |
| Language | C (Alternatives: Rust, Go) |
| Prerequisites | File descriptors, fork/exec |
| Key Topics | PTYs, raw mode, terminal control |
1. Learning Objectives
By completing this project, you will:
- Allocate PTY master/slave pairs correctly.
- Create a controlling terminal for a child shell.
- Proxy I/O between terminal and PTY safely.
- Capture and log raw terminal output.
- Handle window resize propagation.
2. Theoretical Foundation
2.1 Core Concepts
- PTYs: A master/slave pair; the slave looks like a real terminal to the child.
- Controlling Terminal: The child process needs a controlling terminal to behave normally.
- Raw Mode: Disables line buffering and echo so you see keystrokes directly.
- Window Size: SIGWINCH events must update PTY size via ioctl.
2.2 Why This Matters
tmux is essentially a PTY broker. If you can build this bridge, tmux becomes understandable, not magic.
2.3 Historical Context / Background
GNU Screen and tmux were built to keep sessions alive across disconnects. PTY multiplexing is the core trick that makes that possible.
2.4 Common Misconceptions
- “PTYs are just pipes”: They emulate terminal semantics, not just byte streams.
- “Raw mode is optional”: Without it, special keys are handled by the terminal, not your program.
3. Project Specification
3.1 What You Will Build
A program pty_echo that:
- Spawns a shell in a PTY slave
- Proxies I/O between your terminal and the PTY master
- Logs all output to a file
3.2 Functional Requirements
- PTY allocation:
posix_openpt,grantpt,unlockpt. - Child process: fork, setsid, set controlling terminal, exec shell.
- I/O proxy: Bidirectional read/write between STDIN and PTY master.
- Logging: Save raw output to a log file.
- Resize handling: Handle SIGWINCH and resize the PTY.
3.3 Non-Functional Requirements
- Reliability: No terminal corruption after exit.
- Usability: Clean restore of terminal modes.
- Performance: Minimal overhead.
3.4 Example Usage / Output
$ ./pty_echo
3.5 Real World Outcome
You run the program and see a normal shell prompt. Everything you type appears normally, but the raw output is captured:
$ ./pty_echo
$ ls
$ exit
$ head -n 5 pty_log.txt
# includes escape codes and command output
4. Solution Architecture
4.1 High-Level Design
Terminal <-> PTY master <-> PTY slave <-> Shell
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| PTY Setup | Allocate master/slave | posix_openpt |
| Child Setup | setsid + TIOCSCTTY | controlling terminal |
| Proxy Loop | forward I/O | select/poll |
| Logger | write output | append mode |
4.3 Data Structures
struct termios orig;
4.4 Algorithm Overview
Key Algorithm: Proxy loop
- Use select/poll on STDIN and PTY master.
- If STDIN readable, forward to PTY master.
- If PTY readable, write to STDOUT and log.
Complexity Analysis:
- Time: O(N) for N bytes
- Space: O(B) for buffer
5. Implementation Guide
5.1 Development Environment Setup
cc -Wall -Wextra -o pty_echo pty_echo.c
5.2 Project Structure
pty-echo/
├── pty_echo.c
└── README.md
5.3 The Core Question You Are Answering
“How can a program sit between a terminal and a shell and make both think nothing changed?”
5.4 Concepts You Must Understand First
- PTY master/slave semantics
- Controlling terminal and sessions
- Raw vs cooked terminal modes
5.5 Questions to Guide Your Design
- Where do you store original termios settings?
- How do you restore terminal on exit or crash?
- How will you handle EOF from the shell?
5.6 Thinking Exercise
Draw a diagram of data flow for a single keypress. Trace from keyboard to shell and back.
5.7 The Interview Questions They Will Ask
- What is a PTY and why does it exist?
- Why does the child need a controlling terminal?
- What happens if you forget to restore terminal state?
5.8 Hints in Layers
Hint 1: Start with PTY allocation and shell spawn.
Hint 2: Add select/poll for bidirectional I/O.
Hint 3: Add SIGWINCH handling and resize.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| PTYs | “The Linux Programming Interface” | Ch. 64 |
| Terminal I/O | “Advanced Programming in the UNIX Environment” | Ch. 18 |
5.10 Implementation Phases
Phase 1: PTY allocation
- posix_openpt, grantpt, unlockpt
Phase 2: Fork and exec
- setsid, TIOCSCTTY, exec shell
Phase 3: Proxy loop
- select/poll, logging, cleanup
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | none | minimal C program |
| Integration Tests | interactive | run and type |
| Edge Cases | resize | SIGWINCH |
6.2 Critical Test Cases
- Terminal restored after exit.
- SIGWINCH updates PTY size.
- Log file contains output.
7. Common Pitfalls and Debugging
| Pitfall | Symptom | Solution |
|---|---|---|
| No raw mode | special keys fail | set termios |
| Terminal left broken | no echo | restore termios |
| Missing controlling terminal | shell behaves oddly | setsid + TIOCSCTTY |
8. Extensions and Challenges
8.1 Beginner Extensions
- Add timestamped logging
8.2 Intermediate Extensions
- Add file playback mode
8.3 Advanced Extensions
- Add ANSI escape code parsing
9. Real-World Connections
- tmux/screen internals
- Terminal recording tools
10. Resources
- TLPI Ch. 64 (PTYs)
man termios
11. Self-Assessment Checklist
- I can explain PTY master vs slave
- I can handle raw mode safely
12. Submission / Completion Criteria
Minimum Viable Completion:
- Shell runs inside PTY and I/O is proxied
Full Completion:
- Logging + resize handling
Excellence (Going Above and Beyond):
- Playback or escape parsing