All projects
posix-mini-shell.cpublic
Solo projectOct - Dec 2025
POSIX Mini Shell
An interactive shell implementing process spawning, foreground/background execution with zombie reaping, signal handling that survives Ctrl-C, and command history, validated by a GoogleTest suite covering adversarial and interactive cases, not just happy paths.
- Role
- Solo project
- Timeline
- Oct - Dec 2025
- Impact
- A Unix-like shell built on fork/exec/wait, with signal handling, background jobs, and a 32-test suite that's larger than the shell itself.
CC++GoogleTestPOSIX
process lifecycle: fork, exec, wait, reap
Concepts demonstrated
- Process management
- Signal handling
- Operating systems fundamentals
- Test-driven development
Highlights
- Implemented fork/execvp/waitpid process control with error-path handling on every syscall, plus background execution (&) with non-blocking zombie reaping so finished background jobs don't accumulate.
- Trapped SIGINT so Ctrl-C interrupts a running foreground command without killing the shell itself or leaving the terminal in a broken state.
- Wrote a 1,190-line, 32-case GoogleTest suite, roughly 3x the size of the shell's own implementation, covering background execution, history recall (!!, !n), invalid history indices, and Ctrl-C during execution.
Problem
A shell looks simple from the outside but touches some of the sharpest edges of POSIX process management: spawning children correctly, not leaking zombie processes, and handling signals without corrupting the shell's own state.
Engineering approach
- Build the core fork/exec/wait loop first, with explicit error handling on every syscall rather than assuming success.
- Add background execution and a reaping loop specifically so long-running or forgotten background jobs don't pile up as zombies.
- Treat the test suite as a first-class deliverable: write GoogleTest cases for the adversarial paths (bad history index, invalid command, Ctrl-C mid-execution) alongside the happy path.
Architecture
- src/main.c: the read-eval loop, built-ins (cd, pwd, exit, help, history), and history recall (!!, !n).
- Background jobs tracked and reaped via a non-blocking waitpid(-1, NULL, WNOHANG) loop rather than blocking the shell on every backgrounded process.
- gtest/shell_test.cpp: 32 GoogleTest cases spanning fork/wait behavior, background execution, Ctrl-C handling, and invalid-input recovery.
Technical challenges
- Getting Ctrl-C right is deceptively hard: the signal has to interrupt a running foreground child without also killing the shell process that's supposed to keep running afterward.
- Reaping background processes without blocking required a non-blocking wait loop rather than the more obvious blocking wait, an easy detail to get wrong in a way that only shows up under load.
Decisions
- Scoped deliberately to a 'mini' shell (no pipes or redirection) to keep the process-management and signal-handling core correct and well-tested rather than spreading effort thin across a larger feature set.
- Invested test-suite size roughly 3x the implementation size, treating correctness under adversarial input as the actual point of the project.
Result
A small, correct shell with process control, signal handling, and history recall backed by a test suite thorough enough to catch regressions in the sharp-edged parts of POSIX process management.
Learnings
- Scoping out pipes and redirection early kept the fork/exec/signal core small enough to actually test exhaustively instead of spreading effort thin across a larger feature set.
- Pipes and redirection are the natural next feature, deliberately deferred so the process-management core could be gotten right first. That would be the first thing added in a v2.