/* * 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). * * Three deterministic, self-scoped checks per driver stage (ww + ww_ww), with * 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-owned root at `/tmp/ww_run_

` EXACTLY. The * invocation uses only its `main` and `main.sepwork` paths; after the * child exits, assert that the whole root is gone. Self-scoped to P, so * concurrent builds do not share ownership. * C. COLLISION: pre-create the exact build scratch with a sentinel, require * build refusal, and require that sentinel to survive. * * run/test share the build_one_sep scratch-cleanup choke-point and apply the * same exact output/root cleanup immediately around it. Revert the wrapper * rm or the owned-root cleanup and check B reddens. * * The trivial no-import root keeps all artifacts under a fresh mkdtemp-owned * directory or the driver's own exact /tmp temporary directory. Models * 989_sepbuild_run.c. */ #include #include #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 runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return 1; } static int write_file(const char *path, const char *body, int *acquired) { *acquired = 0; FILE *f = fopen(path, "wb"); if (!f) return -1; *acquired = 1; if (fputs(body, f) == EOF) { fclose(f); return -1; } return fclose(f); } /* 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); close(dn); } /* 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; pid_t got; do { got = waitpid(pid, &status, 0); } while (got < 0 && errno == EINTR); if (got != pid) return -1; 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[] = "/tmp/wwscratch_XXXXXX"; char cmd[4096], rootww[1024]; int fail = 0, cleanup_fail = 0; const char *drvs[] = { "ww", "ww_ww" }; int root_owned = 0; int build_started[2] = { 0, 0 }; int collision_started[2] = { 0, 0 }; int collscr_owned[2] = { 0, 0 }; int marker_owned[2] = { 0, 0 }; if (mkdtemp(td) == NULL) return 1; snprintf(rootww, sizeof rootww, "%s/root.ww", td); if (write_file(rootww, root_src, &root_owned)) { fail++; goto out; } 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); build_started[s] = 1; int brc = runwait(cmd); if (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 runroot[64]; snprintf(runroot, sizeof runroot, "/tmp/ww_run_%d", (int)p); if (access(runroot, F_OK) == 0) { fprintf(stderr, "sepscratch FAIL: %s LEAKED %s\n", drvs[s], runroot); fail++; } /* C. A pre-existing scratch belongs to someone else. */ char collision[1024], collscr[1100], marker[1200]; snprintf(collision, sizeof collision, "%s/collision_%s", td, drvs[s]); snprintf(collscr, sizeof collscr, "%s.sepwork", collision); if (mkdir(collscr, 0755) != 0) { fprintf(stderr, "sepscratch FAIL: cannot acquire %s\n", collscr); fail++; continue; } collscr_owned[s] = 1; snprintf(marker, sizeof marker, "%s/sentinel", collscr); if (write_file(marker, "owned\n", &marker_owned[s]) != 0) { fprintf(stderr, "sepscratch FAIL: cannot write %s\n", marker); fail++; continue; } snprintf(cmd, sizeof cmd, "timeout 240 %s/%s build -o %s %s >/dev/null 2>&1", bin, drvs[s], collision, rootww); collision_started[s] = 1; int crc = runwait(cmd); if (crc == 0) { fprintf(stderr, "sepscratch FAIL: %s adopted existing %s\n", drvs[s], collscr); fail++; } else if (crc != 1) { fprintf(stderr, "sepscratch FAIL: %s collision refusal exit=%d want=1\n", drvs[s], crc); fail++; } char markbuf[16] = {0}; FILE *markf = fopen(marker, "rb"); if (markf == NULL || fread(markbuf, 1, sizeof "owned\n" - 1, markf) != sizeof "owned\n" - 1 || memcmp(markbuf, "owned\n", sizeof "owned\n" - 1) != 0) { fprintf(stderr, "sepscratch FAIL: %s deleted %s\n", drvs[s], marker); fail++; } if (markf != NULL && fclose(markf) != 0) fail++; } out: for (int s = 0; s < 2; s++) { char path[1200]; if (build_started[s]) { snprintf(path, sizeof path, "%s/prog_%s.sepwork", td, drvs[s]); snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path); if (runwait(cmd) != 0) cleanup_fail = 1; snprintf(path, sizeof path, "%s/prog_%s", td, drvs[s]); if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1; } if (marker_owned[s]) { snprintf(path, sizeof path, "%s/collision_%s.sepwork/sentinel", td, drvs[s]); if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1; } if (collscr_owned[s]) { snprintf(path, sizeof path, "%s/collision_%s.sepwork", td, drvs[s]); if (rmdir(path) != 0) cleanup_fail = 1; } if (collision_started[s]) { snprintf(path, sizeof path, "%s/collision_%s", td, drvs[s]); if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1; } } if (root_owned && unlink(rootww) != 0 && errno != ENOENT) cleanup_fail = 1; if (rmdir(td) != 0) cleanup_fail = 1; if (cleanup_fail) { fprintf(stderr, "sepscratch FAIL: cleanup incomplete under %s\n", td); fail++; } if (fail) { fprintf(stderr, "sepscratch: %d check(s) failed\n", fail); return 1; } printf("sepscratch: ww+ww_ww — build -o scratch kept, run workspace " "removed, collisions refused without deletion\n"); return 0; }