ww: preserve original environment for directory tests

This commit is contained in:
2026-08-21 12:57:55 +09:00
parent bcc3aa9336
commit 8a5fc8977f
5 changed files with 424 additions and 67 deletions

View File

@@ -116,6 +116,11 @@ fn pkgallocstrs(cap: i32) ([]str | nomem) = {
return value;
};
fn pkgalloci32(cap: i32) ([]i32 | nomem) = {
let value: []i32 = alloc([], cap: u64)?;
return value;
};
fn pkgallocbytes(cap: i32) ([]u8 | nomem) = {
let value: []u8 = alloc([], cap: u64)?;
return value;
@@ -268,11 +273,81 @@ fn pkgfreestrs(values: []str) void = {
};
};
// Go's AppendPATH and AppendPWD append, then os/exec keeps the last duplicate
// values. WW's executor preserves duplicates, so discard earlier exact entries
// and materialize the selected driver's sibling directory first in PATH.
fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str,
pathowned: *str, pwdowned: *str) bool = {
fn pkgenvkeylen(entry: str) i32 = {
let i: i32 = 0;
for (i < entry.len) {
if (entry[i] == '=') { return i; };
i += 1;
};
return -1;
};
fn pkgenvkeyequal(a: str, an: i32, b: str, bn: i32) bool = {
if (an != bn) { return false; };
let i: i32 = 0;
for (i < an) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
fn pkgenvkeyis(entry: str, n: i32, name: str) bool = {
return pkgenvkeyequal(entry, n, name, name.len);
};
fn pkgenvhash(entry: str, n: i32) u64 = {
let h: u64 = 14695981039346656037u64;
let i: i32 = 0;
for (i < n) {
h = h ^ (entry[i]: u64);
h = h * 1099511628211u64;
i += 1;
};
return h;
};
fn pkgenvtablecap(entries: i32) i32 = {
if (entries == 0) { return 0; };
if (entries > PKG_COUNT_MAX / 2) { return -1; };
let need: i32 = entries * 2;
let cap: i32 = PKG_INITIAL_CAP;
for (cap < need) {
if (cap > PKG_COUNT_MAX / 2) { return -1; };
cap *= 2;
};
return cap;
};
// Go snapshots user-test variables through Unix os.Environ before appending
// PATH and PWD. WW's raw environment walker and executor do no such duplicate
// normalization, so the launch owner must materialize that snapshot itself.
fn pkgenvfirst(slots: []i32, inherited: []str, at: i32, n: i32) bool = {
let slot: i32 = (pkgenvhash(inherited[at], n)
% (slots.len: u64)): i32;
let probes: i32 = 0;
for (probes < slots.len) {
let prior: i32 = slots[slot];
if (prior < 0) {
slots[slot] = at;
return true;
};
let priorn: i32 = pkgenvkeylen(inherited[prior]);
if (pkgenvkeyequal(inherited[at], n,
inherited[prior], priorn)) {
return false;
};
slot += 1;
if (slot == slots.len) { slot = 0; };
probes += 1;
};
return false;
};
// Go's appended PATH and PWD survive os/exec's later-value selection. WW's
// executor preserves its concrete vector, so inherited copies are excluded.
fn runenv(pwd: str, builder: str, out: *[]str, pathowned: *str,
pwdowned: *str) bool = {
let toolbin: str;
let oom: bool = false;
if (!pkgcanonicaldir(pkgdirname(builder), &toolbin, &oom)) {
@@ -283,35 +358,71 @@ fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str,
return false;
};
let inherited: []str = os.getenvs();
if (inherited.len > PKG_COUNT_MAX - 4) {
if (inherited.len > PKG_COUNT_MAX - 2) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 4);
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 2);
let env: []str;
match (allocation) {
case let value: []str => env = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
};
let keyed: i32 = 0;
let i: i32 = 0;
for (i < inherited.len) {
if (!strings.hasprefix(inherited[i], "TMPDIR=")
&& !strings.hasprefix(inherited[i], "LC_ALL=")
&& !strings.hasprefix(inherited[i], "PATH=")
&& !strings.hasprefix(inherited[i], "PWD=")) {
let n: i32 = pkgenvkeylen(inherited[i]);
if (n >= 0 && !pkgenvkeyis(inherited[i], n, "PATH")
&& !pkgenvkeyis(inherited[i], n, "PWD")) {
keyed += 1;
};
i += 1;
};
let tablecap: i32 = pkgenvtablecap(keyed);
if (tablecap < 0) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
pkgfreestrs(env);
return false;
};
let slots: []i32;
slots.ptr = nil: *i32;
slots.len = 0;
slots.cap = 0;
if (tablecap != 0) {
let tableallocation: ([]i32 | nomem) = pkgalloci32(tablecap);
match (tableallocation) {
case let value: []i32 => slots = value;
case nomem => {
pkgputln(os.STDERR_FILENO,
"wwtest package: out of memory");
pkgfreestrs(env);
return false;
};
};
slots.len = tablecap;
i = 0;
for (i < slots.len) { slots[i] = -1; i += 1; };
};
i = 0;
for (i < inherited.len) {
let n: i32 = pkgenvkeylen(inherited[i]);
if (n < 0) {
if (inherited[i].len != 0) { append(env, inherited[i]); };
} else if (!pkgenvkeyis(inherited[i], n, "PATH")
&& !pkgenvkeyis(inherited[i], n, "PWD")
&& pkgenvfirst(slots, inherited, i, n)) {
append(env, inherited[i]);
};
i += 1;
};
append(env, "LC_ALL=C");
let tmpenv: str;
if (!pkgstring(&tmpenv, "TMPDIR=", tmpdir)) {
pkgfreestrs(env);
return false;
if (slots.ptr != nil) {
os.free(slots.ptr: *void,
(slots.cap: u64) * (size(i32): u64));
};
let pathenv: str;
let pathok: bool = false;
@@ -327,29 +438,24 @@ fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str,
case void => pathok = pkgstring(&pathenv, "PATH=", toolbin);
};
if (!pathok) {
pkgfreeownedstr(tmpenv);
pkgfreestrs(env);
return false;
};
let pwdenv: str;
if (!pkgstring(&pwdenv, "PWD=", pwd)) {
pkgfreeownedstr(tmpenv);
pkgfreeownedstr(pathenv);
pkgfreestrs(env);
return false;
};
append(env, tmpenv);
append(env, pathenv);
append(env, pwdenv);
*out = env;
*tmpowned = tmpenv;
*pathowned = pathenv;
*pwdowned = pwdenv;
return true;
};
fn freerunenv(env: []str, tmpowned: str, pathowned: str, pwdowned: str) void = {
pkgfreeownedstr(tmpowned);
fn freerunenv(env: []str, pathowned: str, pwdowned: str) void = {
pkgfreeownedstr(pathowned);
pkgfreeownedstr(pwdowned);
pkgfreestrs(env);
@@ -1624,11 +1730,9 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
let i: i32 = 0;
for (i < filters.len) { append(ra, filters[i]); i += 1; };
let env: []str;
let tmpowned: str;
let pathowned: str;
let pwdowned: str;
if (!runenv(g.root, g.dir, builder, &env, &tmpowned,
&pathowned, &pwdowned)) {
if (!runenv(g.dir, builder, &env, &pathowned, &pwdowned)) {
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return false;
@@ -1644,7 +1748,7 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
rcmd.deadline.nsec = 0i64;
rcmd.grace = 0i64: time.duration;
exec.start(h, &rcmd);
freerunenv(env, tmpowned, pathowned, pwdowned);
freerunenv(env, pathowned, pwdowned);
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return true;