builds / shell / stage-2SHEET 2 / 6 · REV ASIGN IN
ASSEMBLY DIAGRAM — YOUR SHELLSCALE: LEARNING
keyboardthe outside worldCOMMAND LOOP✓ builtPROCESS LAUNCHER⚙ buildingFD PATCH PANELstage 3PIPE MANIFOLDstage 4SIGNAL BUSstage 5JOB BOARDstage 6
BUILTUNDER CONSTRUCTIONNOT YET IMAGINED INTO EXISTENCE
STAGE 2 · THE PROCESS LAUNCHER

Breath of a New Process

There is no run-a-program syscall. There is fork, exec, and wait.

What you're wiring up

Every process on Unix is born by copying an existing one. fork() clones your shell, giving you two nearly identical processes that differ only in what fork returned. exec() then replaces the clone's entire program image with a new one, keeping the process ID and the open file descriptors. waitpid() is the parent standing at the door until the child is finished. That trio is process creation, in full.

An exit code is not a convention your program invents. It is one byte the kernel hands to the parent when a child dies, packed into an integer alongside signal information. You never read that integer directly; you ask it questions through macros, starting with WIFEXITED and WEXITSTATUS.

Two numbers every shell user has seen come from this stage. 127 means the shell searched $PATH and never found the program. 126 means it found the file and was not allowed to execute it. Both are decided by inspecting errno after exec fails, which is the only way you learn exec failed at all.

Assembly steps

[ 01 ]
For any non-builtin command, fork(); in the child call execvp() with the argv; in the parent waitpid() for that specific child.
hint

If execvp returns at all, it failed. Print the error and call _exit(127), not exit(), so the child does not double-flush inherited stdio buffers.

[ 02 ]
Record the child's exit status into last_status using the WIFEXITED and WEXITSTATUS macros.
hint

The raw integer from waitpid is a packed struct in disguise. Never store or compare it directly.

[ 03 ]
Wire up $?: the tokenizer expands the exact token $? into the last status. Full variable expansion is out of scope.
hint

Expand after quote handling, so '$?' stays literal while "$?" expands.

[ 04 ]
Distinguish the failure numbers: command not found gives 127, found but not executable gives 126.
hint

Check errno immediately after execvp: ENOENT versus EACCES.

Go deeper (after it passes)

Status codes as a public API, and errno discipline instead of exceptions, are exactly the case study in Clean Code M5 — Errors Without the Clutter. Read your platform's fork(2) and wait(2) man pages once end to end; they are shorter and better written than most tutorials.