/* * 989_sepscratch_run (#59) — the driver must not LEAK its per-build * `.sepwork` scratch dir. Pre-fix, `ww run` / `ww test` removed only * the built binary (unlink) and left `/tmp/ww_run_.sepwork/` behind * every invocation — the tmpfs filler. The fix (build_one_sep keepscratch * param + guarded rm at the wrapper choke-point) removes run/test scratch * while KEEPING build -o scratch (the byte-id gates read it). * * Two deterministic, self-scoped checks per driver stage (ww + ww_ww), so * they never flake under parallel phase-2 (no global /tmp glob): * A. KEEP control: ` build -o /prog ` → assert * `/prog.sepwork` STILL EXISTS (keepscratch 1). Proves the test is * non-vacuous AND that the gate-read build scratch survives. * B. CLEAN: fork a child that exec's ` run `; the child's * pid P fixes the driver temp at `/tmp/ww_run_

.sepwork` EXACTLY * (do_run / makeruntmp both key on getpid()). After the child exits, * assert that dir is GONE (keepscratch 0). Self-scoped to P → immune to * concurrent builds. * * run/test share ONE cleanup choke-point (the build_one_sep wrapper), so * proving `run` proves the `test` path by construction. Revert the wrapper * rm → check B reddens (the mandatory non-vacuity revert-experiment). * * Light wwstage-driver test (CLAUDE.md rule 14): the trivial no-import root * builds fast; all artifacts live under a per-pid /tmp td or the driver's * own /tmp temp, so it is phase-1 parallel-safe. Models 989_sepbuild_run.c. */ #include #include #include #include #include #include static const char * absbin(void) { const char *b = getenv("BIN"); if (!b) b = "out/bin"; if (b[0] == '/') return b; static char buf[2048]; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return NULL; snprintf(buf, sizeof buf, "%s/%s", cwd, b); return buf; } static int write_file(const char *path, const char *body) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(body, f); fclose(f); return 0; } /* fork+exec `/ run ` with output muted; return the child's * pid via *outpid so the caller can name the driver's /tmp scratch. */ static int run_child(const char *bin, const char *drv, const char *root, pid_t *outpid) { char drvpath[2048]; snprintf(drvpath, sizeof drvpath, "%s/%s", bin, drv); pid_t pid = fork(); if (pid < 0) return -1; if (pid == 0) { int dn = open("/dev/null", O_WRONLY); if (dn >= 0) { dup2(dn, 1); dup2(dn, 2); } /* argv[0] MUST be the full path: the driver derives self_dir * (to locate w6c/w6a/w6l/libwwrt) from argv[0]. */ execl(drvpath, drvpath, "run", root, (char *)NULL); _exit(127); } *outpid = pid; int status = 0; waitpid(pid, &status, 0); if (WIFEXITED(status)) return WEXITSTATUS(status); return 1; } /* A no-import root: builds fast, still produces a real `__root` scratch. */ static const char *root_src = "package main;\n" "fn main() i32 = { return 0; };\n"; int main(void) { const char *bin = absbin(); if (!bin) return 1; char td[64], cmd[4096], rootww[1024]; int fail = 0; snprintf(td, sizeof td, "/tmp/wwscratch_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); if (system(cmd) == -1) return 1; mkdir(td, 0755); snprintf(rootww, sizeof rootww, "%s/root.ww", td); if (write_file(rootww, root_src)) { fail++; goto out; } const char *drvs[] = { "ww", "ww_ww" }; for (int s = 0; s < 2; s++) { /* A. KEEP control — build -o scratch must SURVIVE. */ char prog[1024], buildscr[1100]; snprintf(prog, sizeof prog, "%s/prog_%s", td, drvs[s]); snprintf(cmd, sizeof cmd, "timeout 240 %s/%s build -o %s %s >/dev/null 2>&1", bin, drvs[s], prog, rootww); int brc = system(cmd); if (brc == -1 || (WIFEXITED(brc) && WEXITSTATUS(brc) != 0)) { fprintf(stderr, "sepscratch FAIL: %s build -o\n", drvs[s]); fail++; } snprintf(buildscr, sizeof buildscr, "%s.sepwork", prog); if (access(buildscr, 0) != 0) { fprintf(stderr, "sepscratch FAIL: %s build -o scratch %s was " "removed (gates read it; keepscratch must be 1)\n", drvs[s], buildscr); fail++; } /* B. CLEAN — run scratch must be GONE post-exit (self-scoped P). */ pid_t p = 0; int rrc = run_child(bin, drvs[s], rootww, &p); if (rrc != 0) { fprintf(stderr, "sepscratch FAIL: %s run exit=%d (expected 0; " "build must have created+run, proving non-vacuity)\n", drvs[s], rrc); fail++; } char runscr[64]; snprintf(runscr, sizeof runscr, "/tmp/ww_run_%d.sepwork", (int)p); if (access(runscr, 0) == 0) { fprintf(stderr, "sepscratch FAIL: %s LEAKED %s (keepscratch 0 " "must rm run scratch)\n", drvs[s], runscr); fail++; } } out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); if (system(cmd) == -1) { /* best-effort cleanup */ } if (fail) { fprintf(stderr, "sepscratch: %d check(s) failed\n", fail); return 1; } printf("sepscratch: ww+ww_ww — build -o scratch kept, run scratch " "removed (no /tmp .sepwork leak)\n"); return 0; }