Teaching Notes: Exam Rank 04
Tutor Mindset
Section titled “Tutor Mindset”Rank 04 tests Unix system programming fundamentals. Students must understand:
- How processes work (fork, exec, wait)
- How data flows between processes (pipes, fd redirection)
- How to handle errors gracefully
- Basic parsing techniques
The exam has two levels. Level 1 focuses on process management, Level 2 on parsing. Guide students through concepts, don’t give direct answers.
Level 1 Exercises
Section titled “Level 1 Exercises”Exercise: picoshell
Section titled “Exercise: picoshell”What It Tests
Section titled “What It Tests”- Pipeline execution with
pipe()andfork() - File descriptor management with
dup2() - Process synchronization with
wait() - Error handling and cleanup
Subject Summary
Section titled “Subject Summary”int picoshell(char **cmds[]);// Execute pipeline: cmd1 | cmd2 | cmd3 | ...// Return 0 on success, 1 on any error// Must close all fds before returningCommon Mistakes to Watch For
Section titled “Common Mistakes to Watch For”-
Not closing pipe ends in child processes
// WRONGif (fork() == 0) {dup2(pipefd[1], 1);execvp(cmd[0], cmd);}// CORRECTif (fork() == 0) {close(pipefd[0]); // Close read end!dup2(pipefd[1], 1);close(pipefd[1]); // Close after dup2!execvp(cmd[0], cmd);exit(1); // Exit if exec fails!} -
Parent not closing pipe ends
- After forking all children, parent must close all pipe fds
- Otherwise children reading from pipe will hang
-
Not waiting for all children
- Creates zombie processes
- Must
wait()orwaitpid()for every forked child
-
Forgetting to exit after failed execvp
- If
execvpfails, child continues running parent’s code - Always
exit(1)after failed exec
- If
-
Wrong pipe direction
pipefd[0]= read,pipefd[1]= write- Previous command writes to
pipefd[1] - Next command reads from
pipefd[0]
Guiding Questions
Section titled “Guiding Questions”- “What does pipe() create? Which end is for reading?”
- “After fork(), who needs to close which pipe ends?”
- “What happens if you don’t close the write end of a pipe?”
- “How do you redirect stdout to a pipe?”
- “What happens if execvp fails? Does it return?”
Red Flags During Evaluation
Section titled “Red Flags During Evaluation”- Process hangs (forgot to close pipe ends)
- Zombie processes (forgot to wait)
- File descriptor leaks (check with
lsof -c picoshell)
Mental Model to Explain
Section titled “Mental Model to Explain”For cmd1 | cmd2:
1. Create pipe2. Fork for cmd1: - Close pipe read end (won't read) - dup2 pipe write end to stdout - Close original pipe write end - exec cmd13. Fork for cmd2: - Close pipe write end (won't write) - dup2 pipe read end to stdin - Close original pipe read end - exec cmd24. Parent: - Close both pipe ends - Wait for both childrenExercise: ft_popen
Section titled “Exercise: ft_popen”What It Tests
Section titled “What It Tests”- Understanding of
popen()system call behavior - Pipe creation and direction based on mode
- Fork/exec pattern
- File descriptor management
Subject Summary
Section titled “Subject Summary”int ft_popen(const char *file, char *const argv[], char type);// type 'r': return fd connected to command's stdout (read from it)// type 'w': return fd connected to command's stdin (write to it)// Return -1 on errorCommon Mistakes to Watch For
Section titled “Common Mistakes to Watch For”-
Wrong pipe direction for read/write mode
// For 'r' mode (read command's output):// Parent reads from pipefd[0]// Child writes to pipefd[1] (redirect stdout)// For 'w' mode (write to command's input):// Parent writes to pipefd[1]// Child reads from pipefd[0] (redirect stdin) -
Returning wrong fd to caller
- ‘r’ mode: return read end, close write end
- ‘w’ mode: return write end, close read end
-
Not validating type parameter
- Must check
type == 'r' || type == 'w' - Return -1 for invalid type
- Must check
-
Forgetting child process handling
- Must close unused pipe end in child
- Must
exit(1)if exec fails
Guiding Questions
Section titled “Guiding Questions”- “If you want to read a command’s output, which pipe end do you return?”
- “In ‘r’ mode, what should the child redirect? stdin or stdout?”
- “What fd should the parent keep open? What should it close?”
Key Insight
Section titled “Key Insight”'r' mode: I want to READ what the command outputs - Child: redirect stdout -> pipe write - Parent: return pipe read, close pipe write
'w' mode: I want to WRITE to the command's input - Child: redirect stdin <- pipe read - Parent: return pipe write, close pipe readExercise: sandbox
Section titled “Exercise: sandbox”What It Tests
Section titled “What It Tests”- Signal handling with
sigaction() - Timeouts with
alarm() - Process status analysis (WIFEXITED, WIFSIGNALED)
- Error detection and reporting
Subject Summary
Section titled “Subject Summary”int sandbox(void (*f)(void), unsigned int timeout, bool verbose);// Return 1 if f is "nice" (exits 0, no signal, no timeout)// Return 0 if f is "bad" (signal, non-zero exit, timeout)// Return -1 on error in sandbox itselfCommon Mistakes to Watch For
Section titled “Common Mistakes to Watch For”-
Not handling all “bad” cases
- Killed by signal (WIFSIGNALED)
- Exited with non-zero code (WEXITSTATUS != 0)
- Timed out (SIGALRM)
-
Signal handler issues
// WRONG - handler modifies global state unsafelyvoid handler(int sig) {timed_out = true; // Race condition!}// BETTER - use volatile sig_atomic_t or check in parentvolatile sig_atomic_t timed_out = 0; -
Zombie processes
- If timeout kills child, still need to wait() for it
- Must reap ALL children
-
Wrong alarm() usage
// Set alarm BEFORE fork, or in parent after fork// Child inherits parent's alarms - be careful!pid_t pid = fork();if (pid == 0) {// Childf(); // Run the functionexit(0);}// Parentalarm(timeout); // Set timeoutwaitpid(pid, &status, 0);alarm(0); // Cancel alarm -
Wrong verbose messages
- Must match exact format from subject
- Use
strsignal()for signal description
Guiding Questions
Section titled “Guiding Questions”- “How do you detect if a process was killed by a signal?”
- “What macro tells you the exit code?”
- “How does alarm() work? What signal does it send?”
- “What happens to the alarm when waitpid returns?”
Process Status Cheat Sheet
Section titled “Process Status Cheat Sheet”int status;waitpid(pid, &status, 0);
if (WIFEXITED(status)) { int code = WEXITSTATUS(status); if (code == 0) // Nice! else // Bad: exited with code}else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); // Bad: killed by signal // Use strsignal(sig) for description}Level 2 Exercises
Section titled “Level 2 Exercises”Exercise: argo (JSON Parser)
Section titled “Exercise: argo (JSON Parser)”What It Tests
Section titled “What It Tests”- Recursive descent parsing
- Character-by-character input processing
- Escape sequence handling
- Error reporting with specific messages
Subject Summary
Section titled “Subject Summary”int argo(json *dst, FILE *stream);// Parse JSON into AST structure// Handle: numbers, strings, objects (maps)// Return 1 on success, -1 on failure// Print "Unexpected token '%c'\n" or "Unexpected end of input\n"Common Mistakes to Watch For
Section titled “Common Mistakes to Watch For”-
Not handling escape sequences correctly
// Only handle \\ and \"// Other escapes (\n, \t, etc.) are NOT requiredif (c == '\\') {c = getc(f);if (c == '\\' || c == '"') {// Valid escape} else {// Invalid - but subject may not test this}} -
Treating spaces as valid
- Subject says: “Don’t handle spaces -> consider them as invalid tokens”
- Space should trigger “Unexpected token ’ ’”
-
Wrong error messages
- Must be exactly:
"Unexpected token '%c'\n" - For EOF:
"Unexpected end of input\n"
- Must be exactly:
-
Not handling recursive objects
{"a":{"b":{"c":1}}}- Must recursively parse nested objects
-
Memory leaks
- If parsing fails mid-way, must free allocated memory
Guiding Questions
Section titled “Guiding Questions”- “What are the three types of values you need to parse?”
- “How do you detect a string vs a number vs an object?”
- “What function lets you ‘peek’ at the next character without consuming it?”
- “How do you handle escaped quotes inside strings?”
Parsing Pattern
Section titled “Parsing Pattern”int parse_value(FILE *f, json *dst) { int c = getc(f);
if (c == '"') return parse_string(f, dst); if (isdigit(c)) { ungetc(c, f); return parse_number(f, dst); } if (c == '{') return parse_object(f, dst); if (c == EOF) { printf("Unexpected end of input\n"); return -1; } printf("Unexpected token '%c'\n", c); return -1;}Exercise: vbc (Expression Calculator)
Section titled “Exercise: vbc (Expression Calculator)”What It Tests
Section titled “What It Tests”- Recursive descent parsing with operator precedence
- Parenthesis handling
- Error detection and reporting
- Basic arithmetic evaluation
Subject Summary
Section titled “Subject Summary”vbc '3+4*5' -> 23 (not 35!)vbc '(3+4)*5' -> 35Handle: +, *, (), digits 0-9Error: "Unexpected token '%c'\n" or "Unexpected end of input\n"Common Mistakes to Watch For
Section titled “Common Mistakes to Watch For”-
Wrong operator precedence
// WRONG - left to right3+4*5 = 35 // (3+4)*5// CORRECT - * before +3+4*5 = 23 // 3+(4*5)The trick: parse
*at a deeper level than+. -
Not handling nested parentheses
(((3))) should work((1+2)*3) should work -
Wrong error for unmatched parentheses
Input: "1+2)"Error: "Unexpected token ')'\n"Input: "(1+2"Error: "Unexpected end of input\n" -
Not handling multi-digit… wait, subject says 0-9 only!
- Each digit is a single number
12would be1then2(unexpected token ‘2’ after expression)
Guiding Questions
Section titled “Guiding Questions”- “If
*has higher precedence than+, which should you parse first?” - “In the grammar, what’s the difference between
expr,term, andfactor?” - “How do parentheses affect the grammar?”
- “What happens when you see an unexpected character?”
Grammar to Explain
Section titled “Grammar to Explain”expr = term (('+') term)* // Addition (lowest precedence)term = factor (('*') factor)* // Multiplication (higher precedence)factor = '(' expr ')' | DIGIT // Parentheses or number (highest)Code Structure
Section titled “Code Structure”int parse_expr(const char **s); // Handles +int parse_term(const char **s); // Handles *int parse_factor(const char **s); // Handles () and digits
// factor is called first for each operand// This ensures * binds tighter than +General Exam Tips
Section titled “General Exam Tips”Time Management
Section titled “Time Management”- Level 1: ~45 min (process exercise)
- Level 2: ~45 min (parsing exercise)
- Leave 20 min for testing and debugging
Testing Strategy
Section titled “Testing Strategy”- Test happy path first
- Test edge cases (empty input, single element)
- Test error cases
- Check for fd leaks:
lsof -c program_name - Check for zombies:
ps aux | grep defunct
Common Exam-Day Mistakes
Section titled “Common Exam-Day Mistakes”- Forgetting to
exit(1)after failedexecvp - Closing fds in wrong order
- Wrong error message format
- Not handling EOF
What to Tell Struggling Students
Section titled “What to Tell Struggling Students”- “Draw the pipe diagram on paper first”
- “Which process needs to read? Which needs to write?”
- “What happens to data after close()?”
- “Trace through your code with a simple example”