ww: clean per-pid sep-build scratch dir on run/test (#59)

ww run / ww test created /tmp/ww_{run,test}_<pid>.sepwork/ but removed only
the built binary, leaking the scratch DIR every invocation — the tmpfs filler
(98,811 entries blocked the gate twice). Add a keepscratch param to
build_one_sep: a thin wrapper rm -rfs the impl scratch at the single
choke-point when keepscratch==0 AND the path ends ".sepwork" (covers every
return, success+error; fires post-link, pre-run). do_build keeps it (the
byte-id gates read <stem>.sepwork from ww build -o); do_run/do_test clean;
do_test no-o redirects scratch into /tmp. Both stages symmetric; reuses the
existing shell rm -rf idiom (lib/os.removeall = #109). Test 989_sepscratch_run
is self-scoped by child pid (non-flaky) with a KEEP control + revert-verified
non-vacuity. Full gate: 448 pass, zero new run/test leaks. (ww_d_* relic +
historical bulk = one-time sweep + agent-probe discipline, not code.)
This commit is contained in:
2026-06-19 00:58:57 +09:00
parent 5adacd2eee
commit 3bb381ef48
4 changed files with 262 additions and 14 deletions

View File

@@ -1361,8 +1361,8 @@ fn cachestore(cacheroot: *u8, g: *sepgraph, pi: i32, manifest: *u8,
// each `.o` wrapped in its own deterministic per-package `.a`), then a
// reverse-topo `w6l` of the `.a` set + libwwrt.a. Side files land in a
// cold `<stem>.sepwork` scratch dir. Twin of cstage build_one_sep.
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, istest: i32) i32 = {
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, istest: i32, scratchout: **u8) i32 = {
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
@@ -1442,6 +1442,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
// rm -rf scratch (cold); recreate. Reuse os.removeall if present;
// here we mkdir and rely on TRUNC opens to overwrite stale files.
os.mkdir(pathstr(scratch), 493i32); // 0o755 (idempotent; stale files TRUNC'd)
// #59: hand the scratch path back so the buildonesep wrapper can rm it
// on run/test (keepscratch 0). Set AFTER mkdir so we only remove a dir
// we created.
if (scratchout != nil) { *scratchout = scratch; };
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
@@ -1640,6 +1644,39 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
return 0;
};
// buildonesep — wrapper over buildonesepimpl that rm -rf's the per-build
// `<stem>.sepwork` scratch when keepscratch==0 (ww run / ww test — the
// binary is the only wanted artifact). dobuild passes keepscratch 1: the
// byte-id gates read `<stem>.sepwork/*.s` from the `ww build -o` path, so
// build scratch must persist. One cleanup site covers every impl return.
// Guard: only rm a path the impl wrote that ends ".sepwork". Shells the
// existing /bin/sh idiom (md5appendhex precedent; no os.removeall yet —
// #109). Twin of cstage build_one_sep wrapper.
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, istest: i32, keepscratch: i32) i32 = {
let scratch: *u8 = nil;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
incs, lf, istest, &scratch);
if (keepscratch == 0 && scratch != nil) {
if (cstrendswithlit(scratch, ".sepwork")) {
let cmd: []u8 = alloc([], 8192u64)!;
cmd.len = 8192;
let co: u64 = strinto(cmd.ptr, 0u64, "rm -rf '");
co = cstrinto(cmd.ptr, co, scratch);
co = strinto(cmd.ptr, co, "'");
cstrseal(cmd.ptr, co);
let argv: []*u8 = alloc([], 4u64)!;
argv.len = 4;
argv[0] = "sh\0".ptr;
argv[1] = "-c\0".ptr;
argv[2] = cmd.ptr;
argv[3] = nil;
procrun("/bin/sh\0".ptr, argv.ptr);
};
};
return r;
};
// ---- Module-by-name resolution ----------------------------------------
//
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
@@ -1913,7 +1950,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibdirs = nlibdirs;
lf.libs = libs.ptr;
lf.nlibs = nlibs;
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32,
1i32 /* keepscratch: gates read build -o .sepwork */);
};
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
@@ -2070,7 +2108,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibs = nlibs;
// objstem = tmp → intermediates under /tmp/ww_run_<pid>.sepwork/,
// never next to the source (T3).
if (buildonesep(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
if (buildonesep(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32,
0i32 /* keepscratch: throwaway run scratch */) != 0) {
os.remove(pathstr(tmp.ptr));
return 1;
};
@@ -2113,6 +2152,9 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
} else {
makeruntmp(tmp.ptr);
outp = tmp.ptr;
// #59: scratch under /tmp (cleaned by keepscratch 0), not next to
// the source.
objstem = tmp.ptr;
};
// E3-C1: separate compilation is the sole build path (task #87).
let lf: lflags;
@@ -2120,7 +2162,10 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
lf.nlibdirs = 0;
lf.libs = nil;
lf.nlibs = 0;
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
// #59: -o → keep artifacts (mirror dobuild); no-o → throwaway, clean.
let keep: i32 = 0;
if (outstem != nil) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32, keep);
if (bres != 0) {
if (outstem == nil) { os.remove(pathstr(outp)); };
return 1;
@@ -2188,7 +2233,8 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makeruntmp(tmp.ptr);
let bres: i32 = buildonesep(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
// #59: objstem=tmp → scratch in /tmp (cleaned), keepscratch 0.
let bres: i32 = buildonesep(selfdir, path.ptr, 0, tmp.ptr, tmp.ptr, tincs.ptr, nil, 1i32, 0i32);
if (bres != 0) {
fail += 1;
cerr("FAIL ");