Exam Rank 04 Solutions
Download Subject Files:
Level 1: Process Exercises
Section titled “Level 1: Process Exercises”picoshell
Section titled “picoshell”Execute a pipeline of commands, connecting stdout of each to stdin of the next.
#include <unistd.h>#include <sys/wait.h>#include <stdlib.h>
int picoshell(char **cmds[]){ int i = 0; int prev_fd = -1; // Read end of 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++) { // 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; }
if (pid == 0) { // Child process // Redirect stdin from previous pipe (if not first command) if (prev_fd != -1) { dup2(prev_fd, STDIN_FILENO); close(prev_fd); }
// Redirect stdout to current pipe (if not last command) if (cmds[i + 1]) { close(pipefd[0]); // Close read end dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); }
execvp(cmds[i][0], cmds[i]); exit(1); // exec failed }
// Parent process if (prev_fd != -1) close(prev_fd);
if (cmds[i + 1]) { close(pipefd[1]); // Close write end prev_fd = pipefd[0]; // Save read end for next command } }
// Wait for all children int status; for (i = 0; i < cmd_count; i++) { wait(&status); }
return 0;}Key Points:
- Create pipe BEFORE fork (so both processes have access)
- Child: redirect stdin from previous pipe, stdout to current pipe
- Parent: close write end, keep read end for next command
- Close ALL unused file descriptors in BOTH parent and child
- Wait for ALL children to avoid zombies
ft_popen
Section titled “ft_popen”Implement a simplified version of the standard popen() function.
#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; }
if (pid == 0) { // Child process if (type == 'r') { // Parent wants to READ child's output // Child writes to pipe (redirect stdout) close(pipefd[0]); // Close read end dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); } else // type == 'w' { // Parent wants to WRITE to child's input // Child reads from pipe (redirect stdin) close(pipefd[1]); // Close write end dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); }
execvp(file, argv); exit(1); // exec failed }
// Parent process if (type == 'r') { // Return read end, close write end close(pipefd[1]); return pipefd[0]; } else // type == 'w' { // Return write end, close read end close(pipefd[0]); return pipefd[1]; }}Key Points:
- ‘r’ mode: parent reads from child’s stdout -> return pipe read end
- ‘w’ mode: parent writes to child’s stdin -> return pipe write end
- Child redirects the OPPOSITE end (if parent reads, child writes)
- Always close the unused end in both parent and child
- Return -1 for invalid type or any error
sandbox
Section titled “sandbox”Test if a function is “nice” (exits 0) or “bad” (signal, non-zero exit, timeout).
#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;
// Validate input 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) { // Child: run the function f(); exit(0); }
// Parent: set timeout and wait if (timeout > 0) alarm(timeout);
if (waitpid(pid, &status, 0) == -1) { alarm(0); return -1; }
alarm(0); // Cancel alarm
// Check if timed out if (g_timeout) { kill(pid, SIGKILL); waitpid(pid, NULL, 0); // Reap the killed child if (verbose) printf("Bad function: timed out after %u seconds\n", timeout); return 0; }
// Check exit status if (WIFEXITED(status)) { int exit_code = WEXITSTATUS(status); if (exit_code == 0) { if (verbose) printf("Nice function!\n"); return 1; } else { if (verbose) printf("Bad function: exited with code %d\n", exit_code); return 0; } } else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); if (verbose) printf("Bad function: %s\n", strsignal(sig)); return 0; }
return -1; // Should not reach here}Key Points:
- Fork child to run function in isolation
- Use
alarm()to schedule timeout - Use
sigaction()(notsignal()) for reliable signal handling - Check
WIFEXITEDvsWIFSIGNALEDto determine how child terminated - Use
strsignal()to get human-readable signal description - Cancel alarm with
alarm(0)after wait returns - Must
waitpid()for killed child to avoid zombie
Level 2: Parsing Exercises
Section titled “Level 2: Parsing Exercises”argo (JSON Parser)
Section titled “argo (JSON Parser)”Parse a simplified JSON format (numbers, strings, objects only, no whitespace).
#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; // For objects };};
// Forward declarationsstatic int parse_value(FILE *f, json *dst);
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;}
static int parse_string(FILE *f, json *dst){ // Opening " already consumed 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;}
static int parse_object(FILE *f, json *dst){ // Opening { already consumed 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) { // Parse key (must be string) 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;
// Expect colon 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; }
// Parse value json *value = malloc(sizeof(json)); if (parse_value(f, value) == -1) { free(key_json.string); free(value); return -1; }
// Add pair to list json_pair *pair = malloc(sizeof(json_pair)); pair->key = key_json.string; pair->value = value; pair->next = NULL; *tail = pair; tail = &pair->next;
// Check for comma or closing brace 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;}
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;}
int argo(json *dst, FILE *stream){ return parse_value(stream, dst);}Key Points:
- Recursive descent:
parse_valuecallsparse_string,parse_number, orparse_object - Objects recursively contain values
- Use
getc()to read one character at a time - Use
ungetc()to “peek” and put character back - Handle escape sequences
\"and\\only - Exact error messages:
"Unexpected token '%c'\n"or"Unexpected end of input\n"
vbc (Expression Calculator)
Section titled “vbc (Expression Calculator)”Evaluate math expressions with +, *, and parentheses, respecting operator precedence.
#include <stdio.h>#include <stdlib.h>#include <ctype.h>
// Forward declarationsstatic 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);}
// 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}
// 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;}
// 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;}
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 characters if (*s != '\0') error_token(*s);
printf("%d\n", result); return 0;}Key Points:
- Operator Precedence:
*binds tighter than+3+4*5 = 3+(4*5) = 23- Achieved by parsing
*interm(inner level)
- Grammar:
expr = term (('+') term)*term = factor (('*') factor)*factor = '(' expr ')' | DIGIT
- Recursive for parentheses:
factorcallsexprfor(expr) - Single digits only:
0-9, no multi-digit numbers - Pass string pointer by reference (
const char **s) to advance position - Check for unexpected trailing characters after parsing completes
Testing Tips
Section titled “Testing Tips”For Process Exercises
Section titled “For Process Exercises”# Test picoshell./picoshell echo hello "|" cat./picoshell /bin/ls "|" grep .c "|" wc -l
# Check for fd leakslsof -c picoshell
# Check for zombiesps aux | grep defunctFor Parsing Exercises
Section titled “For Parsing Exercises”# Test vbc./vbc '3+4*5' # Should be 23./vbc '(3+4)*5' # Should be 35./vbc '1+2+3+4+5' # Should be 15
# Test argoecho -n '{"a":1}' | ./argo /dev/stdinecho -n '"hello"' | ./argo /dev/stdinecho -n '{"nested":{"deep":42}}' | ./argo /dev/stdinCommon Exam Mistakes to Avoid
Section titled “Common Exam Mistakes to Avoid”- Forgetting
exit(1)after failedexecvp() - Not closing all pipe ends (causes hangs)
- Wrong operator precedence in vbc (
*before+) - Wrong error message format (must match exactly)
- Not handling empty input / EOF
- Zombie processes (always
wait()for all children)