Exam Rank 04 Tutorial
Prerequisites: Review Exam Rank 04 Concepts first.
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.
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 i = 0; int prev_fd = -1; int pipefd[2]; pid_t pid;
while (cmds[i]) i++; int cmd_count = i;
for (i = 0; cmds[i]; i++) { 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; }
if (pid == 0) { if (prev_fd != -1) { dup2(prev_fd, 0); close(prev_fd); } if (cmds[i + 1]) { close(pipefd[0]); dup2(pipefd[1], 1); close(pipefd[1]); } execvp(cmds[i][0], cmds[i]); exit(1); }
if (prev_fd != -1) close(prev_fd); if (cmds[i + 1]) { close(pipefd[1]); prev_fd = pipefd[0]; } }
for (i = 0; i < cmd_count; i++) wait(NULL);
return 0;}ft_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){ int pipefd[2]; pid_t pid;
if (type != 'r' && type != 'w') return -1;
if (pipe(pipefd) == -1) return -1;
pid = fork(); if (pid == -1) { close(pipefd[0]); close(pipefd[1]); return -1; }
if (pid == 0) { if (type == 'r') { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); } else { close(pipefd[1]); dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); } execvp(file, argv); exit(1); }
if (type == 'r') { close(pipefd[1]); return pipefd[0]; } close(pipefd[0]); return pipefd[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
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Signal handler setup
#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 alarm_handler(int sig){ (void)sig; g_timeout = 1;}Stage 2: Main function structure
int sandbox(void (*f)(void), unsigned int timeout, bool verbose){ pid_t pid; int status; struct sigaction sa;
if (!f) return -1;
// Setup alarm handler sa.sa_handler = alarm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGALRM, &sa, NULL) == -1) return -1;
g_timeout = 0;
pid = fork(); if (pid == -1) return -1;
if (pid == 0) { f(); // Run the function exit(0); // Normal exit }
// Parent continues...}Stage 3: Wait with timeout
// Parent: set timeout and waitif (timeout > 0) alarm(timeout);
if (waitpid(pid, &status, 0) == -1){ alarm(0); return -1;}
alarm(0); // Cancel alarm
// Check timeoutif (g_timeout){ kill(pid, SIGKILL); waitpid(pid, NULL, 0); // Reap killed child if (verbose) printf("Bad function: timed out after %u seconds\n", timeout); return 0;}Stage 4: Check exit status
if (WIFEXITED(status)){ int code = WEXITSTATUS(status); if (code == 0) { if (verbose) printf("Nice function!\n"); return 1; } if (verbose) printf("Bad function: exited with code %d\n", code); return 0;}
if (WIFSIGNALED(status)){ int sig = WTERMSIG(status); if (verbose) printf("Bad function: %s\n", strsignal(sig)); return 0;}
return -1;Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Status macros:
| Macro | Returns |
|---|---|
WIFEXITED(status) | true if process called exit() |
WEXITSTATUS(status) | exit code (0-255) |
WIFSIGNALED(status) | true if killed by signal |
WTERMSIG(status) | signal number that killed it |
Signal handling:
// sigaction is safer than signal()struct sigaction sa;sa.sa_handler = alarm_handler; // Function to callsigemptyset(&sa.sa_mask); // No blocked signalssa.sa_flags = 0; // Default behaviorsigaction(SIGALRM, &sa, NULL); // Install handlerCommon Pitfalls
Section titled “Common Pitfalls”1. Not reaping killed child:
// WRONG: Zombie if timed outif (g_timeout) kill(pid, SIGKILL); return 0;
// RIGHT: Wait after killif (g_timeout){ kill(pid, SIGKILL); waitpid(pid, NULL, 0); // Reap zombie return 0;}2. Not canceling alarm:
// WRONG: Alarm fires later, crasheswaitpid(pid, &status, 0);// ... forgot alarm(0) ...
// RIGHT: Always cancelwaitpid(pid, &status, 0);alarm(0); // Cancel pending alarmFinal 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 alarm_handler(int sig) { (void)sig; g_timeout = 1; }
int sandbox(void (*f)(void), unsigned int timeout, bool verbose){ pid_t pid; int status; struct sigaction sa;
if (!f) return -1;
sa.sa_handler = alarm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGALRM, &sa, NULL) == -1) return -1;
g_timeout = 0; pid = fork(); if (pid == -1) return -1;
if (pid == 0) { f(); exit(0); }
if (timeout > 0) alarm(timeout); if (waitpid(pid, &status, 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(status)) { int code = WEXITSTATUS(status); if (code == 0) { if (verbose) printf("Nice function!\n"); return 1; } if (verbose) printf("Bad function: exited with code %d\n", code); return 0; }
if (WIFSIGNALED(status)) { if (verbose) printf("Bad function: %s\n", strsignal(WTERMSIG(status))); return 0; }
return -1;}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
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Error handling
#include <stdio.h>#include <stdlib.h>#include <ctype.h>
static void error_token(char c){ if (c == '\0') printf("Unexpected end of input\n"); else printf("Unexpected token '%c'\n", c); exit(1);}Stage 2: Parse factor (highest precedence)
static int parse_expr(const char **s); // Forward declaration
// factor = '(' expr ')' | DIGITstatic int parse_factor(const char **s){ if (**s == '(') { (*s)++; // Skip '(' int result = parse_expr(s); if (**s != ')') error_token(**s); (*s)++; // Skip ')' return result; }
if (isdigit(**s)) { int num = **s - '0'; (*s)++; return num; }
error_token(**s); return 0; // Never reached}Stage 3: Parse term (multiplication)
// term = factor (('*') factor)*static int parse_term(const char **s){ int result = parse_factor(s);
while (**s == '*') { (*s)++; // Skip '*' result *= parse_factor(s); }
return result;}Stage 4: Parse expr (addition - lowest precedence)
// expr = term (('+') term)*static int parse_expr(const char **s){ int result = parse_term(s);
while (**s == '+') { (*s)++; // Skip '+' result += parse_term(s); }
return result;}Stage 5: Main function
int main(int argc, char **argv){ if (argc != 2) { printf("Usage: %s 'expression'\n", argv[0]); return 1; }
const char *s = argv[1]; int result = parse_expr(&s);
// Check for trailing junk if (*s != '\0') error_token(*s);
printf("%d\n", result); return 0;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”How precedence works:
Input: "3+4*5"
parse_expr: parse_term: parse_factor: 3 (no '*') result = 3 (sees '+') (*s)++ (skip '+') parse_term: parse_factor: 4 (sees '*') (*s)++ (skip '*') parse_factor: 5 4 * 5 = 20 result = 20 3 + 20 = 23
Final: 23Why pointer to pointer?
// Pass address of pointer so function can advance itconst char *s = "3+4";parse_expr(&s);// After parsing, s points past what was consumedCommon Pitfalls
Section titled “Common Pitfalls”1. Wrong precedence (left-to-right):
// WRONG: Parses left-to-rightint result = parse_factor(s);while (**s == '+' || **s == '*') { char op = **s; (*s)++; int right = parse_factor(s); result = (op == '+') ? result + right : result * right;}// "3+4*5" = 35 (wrong!)
// RIGHT: Separate levels for each precedenceparse_expr handles +parse_term handles *// "3+4*5" = 23 (correct!)2. Forgetting to check trailing characters:
// WRONG: Accepts "3+4xxx"result = parse_expr(&s);printf("%d\n", result);
// RIGHT: Check for junkresult = parse_expr(&s);if (*s != '\0') error_token(*s);3. Multi-digit numbers:
// WRONG: Subject says single digits onlywhile (isdigit(**s)) { num = num * 10 + (**s - '0'); (*s)++;}
// RIGHT: Single digitint num = **s - '0';(*s)++;Testing 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 ' 'Final Code
Section titled “Final Code”#include <stdio.h>#include <stdlib.h>#include <ctype.h>
static int parse_expr(const char **s);
static void error_token(char c){ if (c == '\0') printf("Unexpected end of input\n"); else printf("Unexpected token '%c'\n", c); exit(1);}
static int parse_factor(const char **s){ if (**s == '(') { (*s)++; int result = parse_expr(s); if (**s != ')') error_token(**s); (*s)++; return result; } if (isdigit(**s)) { int num = **s - '0'; (*s)++; return num; } error_token(**s); return 0;}
static int parse_term(const char **s){ int result = parse_factor(s); while (**s == '*') { (*s)++; result *= parse_factor(s); } return result;}
static int parse_expr(const char **s){ int result = parse_term(s); while (**s == '+') { (*s)++; result += parse_term(s); } return result;}
int main(int argc, char **argv){ if (argc != 2) { printf("Usage: %s 'expression'\n", argv[0]); return 1; } const char *s = argv[1]; int result = parse_expr(&s); if (*s != '\0') error_token(*s); printf("%d\n", result); return 0;}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
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
Recursive descent:
parse_valuedispatches based on first character"→ string, digit → number,{→ object
-
Object structure:
{key:value,key:value,...}- Keys are always strings
- Values can be any type (including nested objects)
-
String handling:
- Read until closing
" - Handle
\"as literal quote,\\as literal backslash
- Read until closing
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Data structures
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>
typedef enum { JSON_NUMBER, JSON_STRING, JSON_OBJECT } json_type;
typedef struct json json;typedef struct json_pair json_pair;
struct json_pair { char *key; json *value; json_pair *next;};
struct json { json_type type; union { int number; char *string; json_pair *pairs; };};Stage 2: Parse number
static int parse_number(FILE *f, json *dst){ int num; if (fscanf(f, "%d", &num) != 1) return -1; dst->type = JSON_NUMBER; dst->number = num; return 1;}Stage 3: Parse string
static int parse_string(FILE *f, json *dst){ char buffer[4096]; int i = 0; int c;
while ((c = getc(f)) != EOF && c != '"') { if (c == '\\') { c = getc(f); if (c == '"' || c == '\\') buffer[i++] = c; else if (c == EOF) { printf("Unexpected end of input\n"); return -1; } else { 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'; dst->type = JSON_STRING; dst->string = strdup(buffer); return 1;}Stage 4: Parse value (dispatcher)
static int parse_value(FILE *f, json *dst);static int parse_object(FILE *f, json *dst);
static 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;}Stage 5: Parse object
static int parse_object(FILE *f, json *dst){ dst->type = JSON_OBJECT; dst->pairs = NULL; json_pair **tail = &dst->pairs;
int c = getc(f); if (c == '}') return 1; // Empty object
ungetc(c, f);
while (1) { c = getc(f); if (c != '"') { if (c == EOF) printf("Unexpected end of input\n"); else printf("Unexpected token '%c'\n", c); return -1; }
json key_json; if (parse_string(f, &key_json) == -1) return -1;
c = getc(f); if (c != ':') { free(key_json.string); if (c == EOF) printf("Unexpected end of input\n"); else printf("Unexpected token '%c'\n", c); return -1; }
json *value = malloc(sizeof(json)); if (parse_value(f, value) == -1) { free(key_json.string); free(value); return -1; }
json_pair *pair = malloc(sizeof(json_pair)); pair->key = key_json.string; pair->value = value; pair->next = NULL; *tail = pair; tail = &pair->next;
c = getc(f); if (c == '}') break; if (c != ',') { if (c == EOF) printf("Unexpected end of input\n"); else printf("Unexpected token '%c'\n", c); return -1; } }
return 1;}
int argo(json *dst, FILE *stream){ return parse_value(stream, dst);}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 stringExam 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!)