Exam Rank 04: Process & Parsing
Download Subject Files:
Why This Matters
Section titled “Why This Matters”Exam Rank 04 tests your ability to work with Unix system calls. These skills are essential for writing shells, daemons, and any program that needs to spawn other processes.
IMPORTANT: Exam Rank 04 is pure C, not C++. These exercises test Unix system programming fundamentals: processes, pipes, file descriptors, and parsing.
The exam has two levels: Level 1 tests process management, Level 2 tests parsing. You’ll get one exercise from each level.
Exam Structure:
- Level 1: picoshell, ft_popen, or sandbox (process-focused)
- Level 2: argo or vbc (parsing-focused)
Key Concepts:
- Process creation with
fork() - Inter-process communication with
pipe() - File descriptor manipulation with
dup2() - Signal handling with
sigaction() - Recursive descent parsing
- Expression evaluation with operator precedence
Understanding Unix System Operators
Section titled “Understanding Unix System Operators”This exam covers Unix system programming. Here’s the C operators you’ll use:
The fork() Return Value
Section titled “The fork() Return Value”pid_t pid = fork();if (pid == -1) { /* error */ }else if (pid == 0) { /* child */ }else { /* parent - pid is child's ID */ }fork()returns different values in parent and child-1means error0in child means “I am the child”- Positive number in parent is the child’s process ID (PID)
- This is how you differentiate parent and child code paths
The pipefd[2] Array
Section titled “The pipefd[2] Array”int pipefd[2];pipe(pipefd);// pipefd[0] = read end// pipefd[1] = write endpipefd[2]is an array of two file descriptorspipefd[0]is the read end (0 looks like an O - “open to read”)pipefd[1]is the write end (1 looks like a pen - “write”)- Mnemonic: 0 = input, 1 = output
The dup2() Function
Section titled “The dup2() Function”dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to pipedup2(old, new)makes file descriptornewpoint to the same file asold- After this, writing to
newactually writes toold’s destination STDOUT_FILENO= 1 (standard output)STDIN_FILENO= 0 (standard input)STDERR_FILENO= 2 (standard error)
The wait() and waitpid() Functions
Section titled “The wait() and waitpid() Functions”wait(NULL); // Wait for ANY childwaitpid(pid, &status, 0); // Wait for SPECIFIC childwait(NULL)pauses parent until any child process finisheswaitpid(pid, &status, 0)waits for specific child with PIDpid&statusstores information about how the child ended- Without
wait(), child processes become “zombies” (finished but not cleaned up)
The WIFEXITED and WEXITSTATUS Macros
Section titled “The WIFEXITED and WEXITSTATUS Macros”if (WIFEXITED(status)) { int exit_code = WEXITSTATUS(status);}WIFEXITED(status)checks if child exited normally (viaexit()or return frommain())WEXITSTATUS(status)extracts the exit code (0-255)WIFSIGNALED(status)checks if child was killed by a signalWTERMSIG(status)extracts which signal killed the child
The -> Operator (Pointer Access)
Section titled “The -> Operator (Pointer Access)”struct_ptr->member; // Same as (*struct_ptr).member->combines pointer dereference with member access- Used with pointers to structures
ptr->fieldis equivalent to(*ptr).field- The
*dereferences the pointer,.accesses the member
The ** Double Pointer
Section titled “The ** Double Pointer”char **argv; // Array of strings (array of char pointers)**means “pointer to pointer”char**is an array of strings (each string ischar*)argv[0]is the first string (program name)argv[1]is the second string (first argument)- Common in
main(int argc, char **argv)
The * Dereference Operator
Section titled “The * Dereference Operator”*s++; // Move pointer forward in recursive descent parsing*sdereferences the pointer to get the current character(*s)++gets current char then advances pointer- Used in parsing to consume characters one by one
1. Process Management Fundamentals
Section titled “1. Process Management Fundamentals”fork() - Creating Child Processes
Section titled “fork() - Creating Child Processes”fork() creates an exact copy of the current process.
#include <unistd.h>#include <sys/wait.h>
pid_t pid = fork();
if (pid == -1) { // Error - fork failed perror("fork"); exit(1);}else if (pid == 0) { // Child process // pid == 0 means "I am the child" printf("I am the child (PID: %d)\n", getpid()); exit(0);}else { // Parent process // pid == child's PID printf("I am the parent, child PID: %d\n", pid); wait(NULL); // Wait for child to finish}Key Points About fork()
Section titled “Key Points About fork()”| Parent | Child |
|---|---|
| Returns child’s PID | Returns 0 |
| Continues execution | Starts at same point |
| Has original file descriptors | Gets copies of file descriptors |
| Must wait() for children | Should exit() when done |
2. Pipes - Inter-Process Communication
Section titled “2. Pipes - Inter-Process Communication”How Pipes Work
Section titled “How Pipes Work”A pipe creates a unidirectional data channel:
- pipefd[0] = read end
- pipefd[1] = write end
int pipefd[2];
if (pipe(pipefd) == -1) { perror("pipe"); exit(1);}
// pipefd[0] - read from pipe// pipefd[1] - write to pipeConnecting Processes with Pipes
Section titled “Connecting Processes with Pipes”To connect cmd1 | cmd2:
- Create pipe
- Fork for cmd1: redirect stdout to pipe write end
- Fork for cmd2: redirect stdin from pipe read end
- Close unused pipe ends in all processes
int pipefd[2];pipe(pipefd);
pid_t pid1 = fork();if (pid1 == 0) { // Child 1 (cmd1): writes to pipe close(pipefd[0]); // Close read end dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to pipe close(pipefd[1]); // Close original write end execvp(cmd1[0], cmd1); exit(1);}
pid_t pid2 = fork();if (pid2 == 0) { // Child 2 (cmd2): reads from pipe close(pipefd[1]); // Close write end dup2(pipefd[0], STDIN_FILENO); // Redirect stdin from pipe close(pipefd[0]); // Close original read end execvp(cmd2[0], cmd2); exit(1);}
// Parent: close both ends and waitclose(pipefd[0]);close(pipefd[1]);waitpid(pid1, NULL, 0);waitpid(pid2, NULL, 0);Critical Rule: Close Unused Pipe Ends!
Section titled “Critical Rule: Close Unused Pipe Ends!”Why? A pipe’s read end returns EOF only when ALL write ends are closed. If you forget to close the write end in the reader process, it will hang forever waiting for input.
// WRONG - cmd2 will hang!pid_t pid2 = fork();if (pid2 == 0) { dup2(pipefd[0], STDIN_FILENO); // Forgot to close pipefd[1]! execvp(cmd2[0], cmd2);}
// CORRECTpid_t pid2 = fork();if (pid2 == 0) { close(pipefd[1]); // MUST close write end! dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); execvp(cmd2[0], cmd2);}3. dup2() - File Descriptor Redirection
Section titled “3. dup2() - File Descriptor Redirection”Basic Usage
Section titled “Basic Usage”dup2(oldfd, newfd) makes newfd point to the same file as oldfd.
// Redirect stdout to a fileint fd = open("output.txt", O_WRONLY | O_CREAT, 0644);dup2(fd, STDOUT_FILENO); // Now stdout writes to output.txtclose(fd); // Close original fd (stdout still works)printf("This goes to output.txt\n");Common Patterns
Section titled “Common Patterns”// Redirect stdout to pipe write enddup2(pipefd[1], STDOUT_FILENO);
// Redirect stdin from pipe read enddup2(pipefd[0], STDIN_FILENO);
// Redirect stderr to stdoutdup2(STDOUT_FILENO, STDERR_FILENO);4. execvp() - Executing Programs
Section titled “4. execvp() - Executing Programs”execvp vs execve
Section titled “execvp vs execve”| Function | Path Resolution | Environment |
|---|---|---|
execve(path, argv, envp) | Must be full path | You provide envp |
execvp(file, argv) | Searches PATH | Uses current environ |
// execvp - easier, searches PATHchar *argv[] = {"ls", "-la", NULL};execvp("ls", argv); // Will find /bin/ls automatically
// execve - more controlchar *envp[] = {"PATH=/bin", NULL};execve("/bin/ls", argv, envp);Important: execvp Never Returns on Success!
Section titled “Important: execvp Never Returns on Success!”execvp(cmd[0], cmd);// If we reach here, execvp failed!perror("execvp");exit(1); // Child must exit on exec failure5. Signal Handling
Section titled “5. Signal Handling”sigaction() - Modern Signal Handling
Section titled “sigaction() - Modern Signal Handling”#include <signal.h>
void handler(int sig) { // Handle signal write(1, "Caught signal\n", 14);}
int main() { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
alarm(5); // Send SIGALRM in 5 seconds pause(); // Wait for signal
return 0;}Using alarm() for Timeouts
Section titled “Using alarm() for Timeouts”alarm(timeout); // Schedule SIGALRM in 'timeout' seconds
// ... do work ...
alarm(0); // Cancel the alarmChecking How a Process Died
Section titled “Checking How a Process Died”int status;waitpid(pid, &status, 0);
if (WIFEXITED(status)) { // Process exited normally int exit_code = WEXITSTATUS(status); printf("Exited with code %d\n", exit_code);}else if (WIFSIGNALED(status)) { // Process killed by signal int sig = WTERMSIG(status); printf("Killed by signal: %s\n", strsignal(sig));}6. Recursive Descent Parsing
Section titled “6. Recursive Descent Parsing”The Grammar Approach
Section titled “The Grammar Approach”For expression parsing, define a grammar:
expr = term (('+') term)*term = factor (('*') factor)*factor = '(' expr ')' | NUMBERImplementation Pattern
Section titled “Implementation Pattern”int parse_expr(const char **s);int parse_term(const char **s);int parse_factor(const char **s);
// Factor: number or (expr)int parse_factor(const char **s) { if (**s == '(') { (*s)++; // Skip '(' int result = parse_expr(s); if (**s != ')') error("Expected ')'"); (*s)++; // Skip ')' return result; } if (isdigit(**s)) { int num = **s - '0'; (*s)++; return num; } error("Unexpected token"); return 0;}
// Term: factor (* factor)*int parse_term(const char **s) { int result = parse_factor(s); while (**s == '*') { (*s)++; result *= parse_factor(s); } return result;}
// Expr: term (+ term)*int parse_expr(const char **s) { int result = parse_term(s); while (**s == '+') { (*s)++; result += parse_term(s); } return result;}Why This Works for Operator Precedence
Section titled “Why This Works for Operator Precedence”*has higher precedence than+- By parsing
*interm(called first), it binds tighter 3+4*5parses as3+(4*5)= 23, not(3+4)*5= 35
7. JSON Parsing Basics
Section titled “7. JSON Parsing Basics”JSON Structure
Section titled “JSON Structure”value = string | number | objectobject = '{' (pair (',' pair)*)? '}'pair = string ':' valuestring = '"' characters '"'number = digitsParsing Approach
Section titled “Parsing Approach”int parse_value(FILE *f, json *dst) { int c = getc(f);
if (c == '"') { return parse_string(f, dst); } else if (isdigit(c)) { ungetc(c, f); return parse_number(f, dst); } else if (c == '{') { return parse_object(f, dst); } else if (c == EOF) { printf("Unexpected end of input\n"); return -1; } else { printf("Unexpected token '%c'\n", c); return -1; }}Handling Escape Sequences
Section titled “Handling Escape Sequences”int parse_string(FILE *f, json *dst) { // Already consumed opening " char buffer[1024]; int i = 0; int c;
while ((c = getc(f)) != EOF && c != '"') { if (c == '\\') { c = getc(f); if (c == '"' || c == '\\') { buffer[i++] = c; } else { // Invalid escape printf("Unexpected token '%c'\n", c); return -1; } } else { buffer[i++] = c; } }
if (c == EOF) { printf("Unexpected end of input\n"); return -1; }
buffer[i] = '\0'; // Store string in dst... return 1;}8. Common Pitfalls
Section titled “8. Common Pitfalls”File Descriptor Leaks
Section titled “File Descriptor Leaks”// WRONG - leaking fdsint pipefd[2];pipe(pipefd);pid_t pid = fork();if (pid == 0) { dup2(pipefd[1], 1); execvp(cmd[0], cmd);}// Parent forgot to close pipefd[0] and pipefd[1]!
// CORRECTif (pid == 0) { close(pipefd[0]); dup2(pipefd[1], 1); close(pipefd[1]); execvp(cmd[0], cmd); exit(1);}close(pipefd[0]);close(pipefd[1]);Zombie Processes
Section titled “Zombie Processes”// WRONG - creates zombiesfor (int i = 0; i < n; i++) { if (fork() == 0) { execvp(cmds[i][0], cmds[i]); exit(1); }}// Parent exits without waiting!
// CORRECTfor (int i = 0; i < n; i++) { pids[i] = fork(); if (pids[i] == 0) { execvp(cmds[i][0], cmds[i]); exit(1); }}for (int i = 0; i < n; i++) { waitpid(pids[i], NULL, 0);}execvp Failure Handling
Section titled “execvp Failure Handling”// WRONG - child continues after failed execif (fork() == 0) { execvp(cmd[0], cmd); // If execvp fails, child continues running parent's code!}
// CORRECTif (fork() == 0) { execvp(cmd[0], cmd); perror("execvp"); exit(1); // MUST exit!}Level 1: Process Management
Section titled “Level 1: Process Management”picoshell
Section titled “picoshell”Subject Analysis
Section titled “Subject Analysis”Implement a function that executes a pipeline of commands:
- Input: array of commands (each command is array of strings)
- Connect stdout of each command to stdin of the next
- Return 0 on success, 1 on error
- Close all file descriptors, wait for all children
Example pipeline: ls | grep .c | wc -l
char *cmd1[] = {"ls", NULL};char *cmd2[] = {"grep", ".c", NULL};char *cmd3[] = {"wc", "-l", NULL};char **cmds[] = {cmd1, cmd2, cmd3, NULL};picoshell(cmds);Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What is a pipe?
pipe(fd)creates two file descriptorsfd[0]= read end,fd[1]= write end- Data written to
fd[1]can be read fromfd[0]
-
Pipeline pattern:
cmd1 stdout --> [pipe1] --> stdin cmd2 stdout --> [pipe2] --> stdin cmd3 -
For each command:
- Fork a child process
- In child: redirect stdin/stdout, exec the command
- In parent: track file descriptors, wait for children
-
Critical rule: Close ALL unused file descriptors in both parent AND child!
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Basic structure
#include <unistd.h>#include <sys/wait.h>#include <stdlib.h>
int picoshell(char **cmds[]){ int i = 0; int prev_fd = -1; // Read end from previous pipe int pipefd[2]; pid_t pid;
// Count commands while (cmds[i]) i++; int cmd_count = i;
// Process each command for (i = 0; cmds[i]; i++) { // ... implementation here }
// Wait for all children for (i = 0; i < cmd_count; i++) wait(NULL);
return 0;}Stage 2: Create pipes and fork
for (i = 0; cmds[i]; i++){ // Create pipe if not the last command if (cmds[i + 1] && pipe(pipefd) == -1) { if (prev_fd != -1) close(prev_fd); return 1; }
pid = fork(); if (pid == -1) { if (prev_fd != -1) close(prev_fd); if (cmds[i + 1]) { close(pipefd[0]); close(pipefd[1]); } return 1; }
// Child or parent handling...}Stage 3: Child process - redirect and exec
if (pid == 0){ // Child process
// Redirect stdin from previous pipe (if not first) if (prev_fd != -1) { dup2(prev_fd, STDIN_FILENO); close(prev_fd); }
// Redirect stdout to current pipe (if not last) if (cmds[i + 1]) { close(pipefd[0]); // Close read end (won't use) dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); }
execvp(cmds[i][0], cmds[i]); exit(1); // exec failed - MUST exit!}Stage 4: Parent process - manage fds
// Parent processif (prev_fd != -1) close(prev_fd); // Done with previous read end
if (cmds[i + 1]){ close(pipefd[1]); // Close write end (child uses it) prev_fd = pipefd[0]; // Save read end for next command}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Understanding dup2:
| Call | Effect |
|---|---|
dup2(pipefd[1], STDOUT_FILENO) | stdout now writes to pipe |
dup2(prev_fd, STDIN_FILENO) | stdin now reads from previous pipe |
Pipe data flow:
Before dup2: stdin (0) --> keyboard stdout (1) --> terminal
After dup2 in middle command: stdin (0) --> previous pipe read end stdout (1) --> current pipe write endWhy close after dup2?
dup2(pipefd[1], STDOUT_FILENO); // Creates copy at fd 1close(pipefd[1]); // Original no longer needed
// Now: stdout (1) points to pipe write// pipefd[1] is closed (not leaked)Common Pitfalls
Section titled “Common Pitfalls”1. Forgetting to close pipe ends:
// WRONG: Pipe read end never closedif (fork() == 0) { dup2(pipefd[1], 1); execvp(cmd, argv);}
// RIGHT: Close all unused endsif (fork() == 0) { close(pipefd[0]); // Not reading dup2(pipefd[1], 1); close(pipefd[1]); // Closed after dup2 execvp(cmd, argv); exit(1);}2. Not exiting after failed exec:
// WRONG: Child continues as parent!execvp(cmd, argv);// If exec fails, continues here...
// RIGHT: Exit on failureexecvp(cmd, argv);exit(1); // Only reached if exec failed3. Creating zombie processes:
// WRONG: Not waiting for childrenfor (i = 0; cmds[i]; i++) { fork(); // No wait!}
// RIGHT: Wait for allfor (i = 0; i < cmd_count; i++) wait(NULL);4. Parent not closing write end:
// WRONG: Child blocks reading because parent holds write end// Parent keeps pipefd[1] open -> child's read never gets EOF
// RIGHT: Parent closes write endclose(pipefd[1]);prev_fd = pipefd[0];Testing Tips
Section titled “Testing Tips”# Compilegcc -Wall -Wextra -Werror picoshell.c main.c -o picoshell
# Test simple pipeline./picoshell echo hello "|" cat
# Test multiple pipes./picoshell ls "|" grep .c "|" wc -l
# Check for fd leakslsof -c picoshell
# Check for zombiesps aux | grep defunctFinal Code
Section titled “Final Code”#include <unistd.h>#include <sys/wait.h>#include <stdlib.h>
int picoshell(char **cmds[]) { int n = 0, pfd[2], prev = -1; while (cmds[n]) n++;
for (int i = 0; cmds[i]; i++) { if (cmds[i + 1] && pipe(pfd) == -1) return 1; pid_t pid = fork(); if (pid == -1) return 1;
if (pid == 0) { if (prev != -1) { dup2(prev, 0); close(prev); } if (cmds[i + 1]) { close(pfd[0]); dup2(pfd[1], 1); close(pfd[1]); } execvp(cmds[i][0], cmds[i]); exit(1); } if (prev != -1) close(prev); if (cmds[i + 1]) { close(pfd[1]); prev = pfd[0]; } } while (n--) wait(NULL); return 0;}Memory Pattern:
prev = -1for each cmd: if (not last) pipe() fork() child: dup2(prev→0), dup2(pfd[1]→1), exec parent: close prev, prev = pfd[0]wait allft_popen
Section titled “ft_popen”Subject Analysis
Section titled “Subject Analysis”Implement a simplified popen():
- Take command path, arguments, and mode (‘r’ or ‘w’)
- Return file descriptor connected to command’s stdin or stdout
- ‘r’ mode: return fd to read command’s output
- ‘w’ mode: return fd to write to command’s input
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What does ‘r’ mode mean?
- “I want to READ the command’s output”
- Child redirects stdout to pipe write end
- Parent gets pipe read end
-
What does ‘w’ mode mean?
- “I want to WRITE to the command’s input”
- Child redirects stdin from pipe read end
- Parent gets pipe write end
-
Visualization:
'r' mode: parent <--read-- pipe <--write-- child stdout'w' mode: parent --write--> pipe --read--> child stdin
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Setup and validation
#include <unistd.h>#include <stdlib.h>
int ft_popen(const char *file, char *const argv[], char type){ int pipefd[2]; pid_t pid;
// Validate type if (type != 'r' && type != 'w') return -1;
// Create pipe if (pipe(pipefd) == -1) return -1;
pid = fork(); if (pid == -1) { close(pipefd[0]); close(pipefd[1]); return -1; }
// ... child/parent handling}Stage 2: Child process
if (pid == 0){ if (type == 'r') { // Parent reads -> child writes to stdout close(pipefd[0]); // Child won't read dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); } else // type == 'w' { // Parent writes -> child reads from stdin close(pipefd[1]); // Child won't write dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); }
execvp(file, argv); exit(1);}Stage 3: Parent process
// Parent processif (type == 'r'){ close(pipefd[1]); // Parent won't write return pipefd[0]; // Return read end}else // type == 'w'{ close(pipefd[0]); // Parent won't read return pipefd[1]; // Return write end}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Mode direction table:
| Mode | Parent | Child | Parent returns |
|---|---|---|---|
| ’r’ | reads from pipe | writes to pipe (stdout) | pipefd[0] |
| ’w’ | writes to pipe | reads from pipe (stdin) | pipefd[1] |
Memory trick:
- ‘r’ = “I Read” = parent reads = child outputs = child’s stdout
- ‘w’ = “I Write” = parent writes = child inputs = child’s stdin
Common Pitfalls
Section titled “Common Pitfalls”1. Reversed direction:
// WRONG: 'r' mode but returning write endif (type == 'r') return pipefd[1]; // Wrong!
// RIGHT: 'r' mode returns read endif (type == 'r') return pipefd[0]; // Correct2. Child redirects wrong fd:
// WRONG: 'r' mode but child redirects stdinif (type == 'r') dup2(pipefd[0], STDIN_FILENO); // Wrong!
// RIGHT: 'r' mode, child redirects stdoutif (type == 'r') dup2(pipefd[1], STDOUT_FILENO); // CorrectFinal Code
Section titled “Final Code”#include <unistd.h>#include <stdlib.h>
int ft_popen(const char *file, char *const argv[], char type) { if (type != 'r' && type != 'w') return -1;
int pfd[2]; if (pipe(pfd) == -1) return -1;
pid_t pid = fork(); if (pid == -1) { close(pfd[0]); close(pfd[1]); return -1; }
if (pid == 0) { if (type == 'r') { close(pfd[0]); dup2(pfd[1], 1); close(pfd[1]); } else { close(pfd[1]); dup2(pfd[0], 0); close(pfd[0]); } execvp(file, argv); exit(1); } if (type == 'r') { close(pfd[1]); return pfd[0]; } else { close(pfd[0]); return pfd[1]; }}Memory Pattern:
'r' = I Read → child writes stdout → return pfd[0]'w' = I Write → child reads stdin → return pfd[1]sandbox
Section titled “sandbox”Subject Analysis
Section titled “Subject Analysis”Test if a function is “nice” or “bad”:
- Nice: exits with code 0, no signal, no timeout
- Bad: crashes (signal), exits non-zero, or times out
- Return 1 for nice, 0 for bad, -1 for sandbox error
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
Why fork?
- Run untrusted function in isolated process
- If it crashes, parent survives
-
How to detect problems?
WIFEXITED(status)- did it exit normally?WEXITSTATUS(status)- what exit code?WIFSIGNALED(status)- killed by signal?WTERMSIG(status)- which signal?
-
How to handle timeout?
alarm(seconds)sends SIGALRM after delay- In handler, set flag
- After wait, kill child if timed out
Final Code
Section titled “Final Code”#include <unistd.h>#include <sys/wait.h>#include <signal.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <stdbool.h>
static volatile sig_atomic_t g_timeout = 0;static void handler(int s) { (void)s; g_timeout = 1; }
int sandbox(void (*f)(void), unsigned int timeout, bool verbose) { if (!f) return -1;
struct sigaction sa = {.sa_handler = handler}; sigemptyset(&sa.sa_mask); if (sigaction(SIGALRM, &sa, NULL) == -1) return -1;
g_timeout = 0; pid_t pid = fork(); if (pid == -1) return -1; if (pid == 0) { f(); exit(0); }
if (timeout) alarm(timeout); int st; if (waitpid(pid, &st, 0) == -1) { alarm(0); return -1; } alarm(0);
if (g_timeout) { kill(pid, SIGKILL); waitpid(pid, NULL, 0); if (verbose) printf("Bad function: timed out after %u seconds\n", timeout); return 0; } if (WIFEXITED(st)) { int code = WEXITSTATUS(st); if (verbose) printf(code ? "Bad function: exited with code %d\n" : "Nice function!\n", code); return code == 0; } if (WIFSIGNALED(st)) { if (verbose) printf("Bad function: %s\n", strsignal(WTERMSIG(st))); return 0; } return -1;}Memory Pattern:
fork() → child runs f(), exit(0)parent: alarm(), waitpid(), alarm(0)check: g_timeout? WIFEXITED? WIFSIGNALED?Level 2: Parsing
Section titled “Level 2: Parsing”vbc (Expression Calculator)
Section titled “vbc (Expression Calculator)”Subject Analysis
Section titled “Subject Analysis”Evaluate mathematical expressions:
- Operators:
+and* - Parentheses:
(and) - Numbers: single digits 0-9
- Operator precedence:
*before+
Examples:
3+4*5 = 23 (not 35!)(3+4)*5 = 351+2+3 = 6Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
Why does
*bind tighter?3+4*5should be3+(4*5) = 23- Not
(3+4)*5 = 35
-
Grammar for precedence:
expr = term (('+') term)* // + is lowestterm = factor (('*') factor)* // * is higherfactor = '(' expr ')' | DIGIT // () and numbers highest -
Recursive descent:
parse_exprhandles+parse_termhandles*parse_factorhandles()and digits- Higher precedence = deeper in call stack
Final Code
Section titled “Final Code”#include <stdio.h>#include <stdlib.h>#include <ctype.h>
static int expr(const char **s);
static void err(char c) { printf(c ? "Unexpected token '%c'\n" : "Unexpected end of input\n", c); exit(1);}
static int factor(const char **s) { if (**s == '(') { (*s)++; int r = expr(s); if (**s != ')') err(**s); (*s)++; return r; } if (isdigit(**s)) return *(*s)++ - '0'; err(**s); return 0;}
static int term(const char **s) { int r = factor(s); while (**s == '*') { (*s)++; r *= factor(s); } return r;}
static int expr(const char **s) { int r = term(s); while (**s == '+') { (*s)++; r += term(s); } return r;}
int main(int ac, char **av) { if (ac != 2) { printf("Usage: %s 'expression'\n", av[0]); return 1; } const char *s = av[1]; int r = expr(&s); if (*s) err(*s); printf("%d\n", r); return 0;}Memory Pattern:
expr = term ('+' term)* ← + is lowestterm = factor ('*' factor)* ← * is higherfactor = '(' expr ')' | digit ← () highest
3+4*5 = 3+(4*5) = 23Testing Tips
Section titled “Testing Tips”./vbc '3+4*5' # 23./vbc '(3+4)*5' # 35./vbc '1+2+3+4+5' # 15./vbc '2*3*4' # 24./vbc '((1+2))' # 3./vbc '1+2)' # Error: Unexpected token ')'./vbc '(1+2' # Error: Unexpected end of input./vbc '1 + 2' # Error: Unexpected token ' 'argo (JSON Parser)
Section titled “argo (JSON Parser)”Subject Analysis
Section titled “Subject Analysis”Parse simplified JSON:
- Types: numbers, strings, objects (no arrays, booleans, null)
- No whitespace handling (spaces are errors)
- Handle escape sequences:
\"and\\only - Print exact error messages
Testing Tips
Section titled “Testing Tips”# Test numbersecho -n '42' | ./argo /dev/stdin
# Test stringsecho -n '"hello"' | ./argo /dev/stdin
# Test objectsecho -n '{"a":1}' | ./argo /dev/stdinecho -n '{"name":"test","value":42}' | ./argo /dev/stdin
# Test nestedecho -n '{"outer":{"inner":1}}' | ./argo /dev/stdin
# Test escapesecho -n '"hello\"world"' | ./argo /dev/stdin
# Test errorsecho -n '{"a":}' | ./argo /dev/stdin # Missing valueecho -n '{a:1}' | ./argo /dev/stdin # Key not string9. Quick Reference
Section titled “9. Quick Reference”Process Functions
Section titled “Process Functions”| Function | Purpose |
|---|---|
fork() | Create child process |
wait(NULL) | Wait for any child |
waitpid(pid, &status, 0) | Wait for specific child |
exit(code) | Terminate process |
Pipe & Redirection
Section titled “Pipe & Redirection”| Function | Purpose |
|---|---|
pipe(int fd[2]) | Create pipe (fd[0]=read, fd[1]=write) |
dup2(old, new) | Redirect new to old |
close(fd) | Close file descriptor |
Execution
Section titled “Execution”| Function | Purpose |
|---|---|
execvp(file, argv) | Execute with PATH search |
execve(path, argv, envp) | Execute with full path |
Signals
Section titled “Signals”| Function | Purpose |
|---|---|
sigaction(sig, &sa, NULL) | Set signal handler |
alarm(seconds) | Schedule SIGALRM |
kill(pid, sig) | Send signal to process |
Status Macros
Section titled “Status Macros”| Macro | Purpose |
|---|---|
WIFEXITED(status) | True if exited normally |
WEXITSTATUS(status) | Get exit code |
WIFSIGNALED(status) | True if killed by signal |
WTERMSIG(status) | Get signal number |
Exam Summary
Section titled “Exam Summary”| Level | Exercise | Key Concept |
|---|---|---|
| 1 | picoshell | Pipe chains, fork, exec, fd management |
| 1 | ft_popen | Pipe direction based on mode |
| 1 | sandbox | Signals, timeout, process status |
| 2 | vbc | Recursive descent, operator precedence |
| 2 | argo | Recursive parsing, escape sequences |
Common mistakes to avoid:
- Not closing all pipe ends
- Not exiting after failed execvp
- Wrong pipe direction for read/write mode
- Wrong operator precedence in vbc
- Wrong error message format (must be exact!)