Files
ww/lib/os/exec/exec.ww
Hojun-Cho f28843f2f5 ww test: preserve combined product output order
Go 1.26.5 maps each test binary's stdout and stderr to the same product writer. Teach the captured executor to share one open file description for byte-equal output paths, then make directory test products emit that one ordered capture on coordinator stdout. Build captures and inherited-stdio routes remain unchanged.\n\nKeep the executor mechanism, package policy, native Cstage/WWstage proof, and normative contract together so every commit preserves the observable test-output behavior.
2026-08-20 18:29:09 +09:00

733 lines
19 KiB
Plaintext

// exec runs subprocesses. It owns argv/environment conversion, launch error
// reporting, waiting, deadlines, and process-group cleanup; callers own build
// and test policy.
package exec;
import os;
import time;
@symbol("rt_syscall") fn syscall3(num: i64, a: i64, b: i64, c: i64) i64;
def SYS_FCNTL: i64 = 72i64;
def F_DUPFD_CLOEXEC: i32 = 1030;
export type termination = enum i32 {
EXIT = 0,
SIGNAL = 1,
TIMEOUT = 2,
ERROR = 3,
};
export type result = struct {
termination: termination,
code: i32,
errno: i32,
cleanuperrno: i32,
};
// command is deliberately concrete. argv includes argv[0], env is the full
// environment, and an empty dir inherits the caller's working directory.
// An empty stdinpath selects the null device; stdoutpath and stderrpath are
// created exclusively with mode 0600. Equal output paths share one open
// capture. A zero deadline means no timeout.
export type command = struct {
path: str,
argv: []str,
env: []str,
dir: str,
stdinpath: str,
stdoutpath: str,
stderrpath: str,
deadline: time.instant,
grace: time.duration,
};
type state = enum i32 {
EMPTY = 0,
RUNNING = 1,
TERM = 2,
KILL = 3,
};
export type process = struct {
pid: i32,
state: i32,
markerfd: i32,
marker: [4]u8,
markern: i32,
markerclosed: bool,
deadline: i64,
termdeadline: i64,
grace: time.duration,
hasdeadline: bool,
done: bool,
reaped: bool,
leaderdone: bool,
reaptermination: termination,
reapcode: i32,
result: result,
};
// interrupt is the small signalfd watch paired with child launch. start
// restores the pre-watch mask in children before exec, so SIGINT/SIGTERM do
// not remain blocked in executed programs.
export type interrupt = struct {
fd: i32,
oldmask: u64,
active: bool,
errno: i32,
};
let launchmaskactive: bool = false;
let launcholdmask: u64 = 0u64;
fn instantns(i: *time.instant) i64 = {
return i.sec * (time.second: i64) + i.nsec;
};
fn nowns() i64 = {
let now: time.instant = time.now(time.clock.monotonic);
return instantns(&now);
};
fn reset(p: *process) void = {
p.pid = -1;
p.state = state.EMPTY as i32;
p.markerfd = -1;
p.markern = 0;
p.markerclosed = false;
p.deadline = 0i64;
p.termdeadline = 0i64;
p.grace = 0i64: time.duration;
p.hasdeadline = false;
p.done = false;
p.reaped = false;
p.leaderdone = false;
p.reaptermination = termination.ERROR;
p.reapcode = 0;
p.result.termination = termination.ERROR;
p.result.code = 0;
p.result.errno = 0;
p.result.cleanuperrno = 0;
};
fn hasnul(s: str) bool = {
let i: i32 = 0;
for (i < s.len) {
if (s[i] == 0u8) { return true; };
i += 1;
};
return false;
};
fn samepath(a: str, b: str) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
for (i < a.len) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
fn valid(c: *command) bool = {
if (c.path.len == 0 || c.argv.len == 0 || c.argv[0].len == 0) {
return false;
};
if ((c.grace: i64) < 0i64) { return false; };
if (hasnul(c.path) || hasnul(c.dir) || hasnul(c.stdinpath)
|| hasnul(c.stdoutpath)
|| hasnul(c.stderrpath)) {
return false;
};
let i: i32 = 0;
for (i < c.argv.len) {
if (hasnul(c.argv[i])) { return false; };
i += 1;
};
i = 0;
for (i < c.env.len) {
if (hasnul(c.env[i])) { return false; };
i += 1;
};
return true;
};
fn cstr(s: str) *u8 = {
let b: []u8 = alloc([], (s.len + 1): u64)!;
b.len = s.len + 1;
let i: i32 = 0;
for (i < s.len) { b[i] = s[i]; i += 1; };
b[s.len] = 0u8;
return b.ptr;
};
fn ctable(v: []str) []*u8 = {
let r: []*u8 = alloc([], (v.len + 1): u64)!;
let i: i32 = 0;
for (i < v.len) { append(r, cstr(v[i])); i += 1; };
append(r, nil: *u8);
return r;
};
fn setcleanup(r: *result, rc: i32) void = {
if (rc >= 0 || rc == -3 || r.cleanuperrno != 0) { return; };
r.cleanuperrno = -rc;
};
fn closefd(r: *result, fd: i32) void = {
if (fd >= 0) { setcleanup(r, os.close(fd)); };
};
// Capture and marker descriptors must not occupy stdin/stdout/stderr. The
// caller may legitimately have closed any of those descriptors before run.
fn safefd(r: *result, fd: i32) i32 = {
if (fd > os.STDERR_FILENO) { return fd; };
let moved: i32 = syscall3(SYS_FCNTL, fd: i64,
F_DUPFD_CLOEXEC: i64, (os.STDERR_FILENO + 1): i64): i32;
closefd(r, fd);
return moved;
};
fn fail(p: *process, rc: i32) void = {
p.result.termination = termination.ERROR;
p.result.errno = rc;
if (p.result.errno < 0) { p.result.errno = -p.result.errno; };
if (p.result.errno == 0) { p.result.errno = 5; };
p.done = true;
};
fn childmark(fd: i32, rc: i32) void = {
let ei: i32 = rc;
if (ei < 0) { ei = -ei; };
if (ei == 0) { ei = 5; };
let e: u32 = ei: u32;
let b: [4]u8;
b[0] = e: u8;
b[1] = (e >> 8u32): u8;
b[2] = (e >> 16u32): u8;
b[3] = (e >> 24u32): u8;
for (true) {
let n: i64 = os.write(fd, &b[0], size([4]u8));
if (n == size([4]u8): i64) { break; };
if (n != -4i64) { os.exit(126); };
};
os.close(fd);
os.exit(127);
};
fn childclose(fd: i32, markerfd: i32) void = {
let rc: i32 = os.close(fd);
if (rc < 0) { childmark(markerfd, rc); };
};
fn child(c: *command, av: []*u8, ep: []*u8, infd: i32,
outfd: i32, errfd: i32, markerread: i32, markerwrite: i32) void = {
let rc: i32 = os.setpgid(0, 0);
if (rc < 0) { childmark(markerwrite, rc); };
if (launchmaskactive) {
rc = os.sigprocmask(os.SIG_SETMASK, &launcholdmask, nil: *u64);
if (rc < 0) { childmark(markerwrite, rc); };
};
childclose(markerread, markerwrite);
rc = os.dup2(infd, os.STDIN_FILENO);
if (rc < 0) { childmark(markerwrite, rc); };
childclose(infd, markerwrite);
rc = os.dup2(outfd, os.STDOUT_FILENO);
if (rc < 0) { childmark(markerwrite, rc); };
rc = os.dup2(errfd, os.STDERR_FILENO);
if (rc < 0) { childmark(markerwrite, rc); };
childclose(outfd, markerwrite);
childclose(errfd, markerwrite);
if (c.dir.len != 0) {
rc = os.chdir(c.dir);
if (rc < 0) { childmark(markerwrite, rc); };
};
rc = os.execve(c.path, av.ptr, ep.ptr);
childmark(markerwrite, rc);
};
export fn start(p: *process, c: *command) void = {
reset(p);
p.deadline = instantns(&c.deadline);
p.hasdeadline = c.deadline.sec != 0i64 || c.deadline.nsec != 0i64;
p.grace = c.grace;
if (!valid(c)) { fail(p, 22); return; };
let stdinpath: str = c.stdinpath;
if (stdinpath.len == 0) { stdinpath = "/dev/null"; };
let infd: i32 = os.open(stdinpath, os.flag.RDONLY, 0i32);
if (infd < 0) { fail(p, infd); return; };
infd = safefd(&p.result, infd);
if (infd < 0) { fail(p, infd); return; };
let outfd: i32 = os.open(c.stdoutpath,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
if (outfd < 0) {
fail(p, outfd);
closefd(&p.result, infd);
return;
};
outfd = safefd(&p.result, outfd);
if (outfd < 0) {
fail(p, outfd);
closefd(&p.result, infd);
return;
};
let errfd: i32;
if (samepath(c.stdoutpath, c.stderrpath)) {
errfd = syscall3(SYS_FCNTL, outfd: i64,
F_DUPFD_CLOEXEC: i64, (os.STDERR_FILENO + 1): i64): i32;
} else {
errfd = os.open(c.stderrpath,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
};
if (errfd < 0) {
fail(p, errfd);
closefd(&p.result, infd);
closefd(&p.result, outfd);
return;
};
errfd = safefd(&p.result, errfd);
if (errfd < 0) {
fail(p, errfd);
closefd(&p.result, infd);
closefd(&p.result, outfd);
return;
};
let marker: [2]i32;
let rc: i32 = os.pipe2(&marker, os.O_CLOEXEC | os.O_NONBLOCK);
if (rc < 0) {
fail(p, rc);
closefd(&p.result, infd);
closefd(&p.result, outfd);
closefd(&p.result, errfd);
return;
};
marker[0] = safefd(&p.result, marker[0]);
if (marker[0] < 0) {
fail(p, marker[0]);
closefd(&p.result, infd);
closefd(&p.result, outfd);
closefd(&p.result, errfd);
closefd(&p.result, marker[1]);
return;
};
marker[1] = safefd(&p.result, marker[1]);
if (marker[1] < 0) {
fail(p, marker[1]);
closefd(&p.result, infd);
closefd(&p.result, outfd);
closefd(&p.result, errfd);
closefd(&p.result, marker[0]);
return;
};
let av: []*u8 = ctable(c.argv);
let ep: []*u8 = ctable(c.env);
let pid: i32 = os.fork();
if (pid < 0) {
fail(p, pid);
closefd(&p.result, infd);
closefd(&p.result, outfd);
closefd(&p.result, errfd);
closefd(&p.result, marker[0]);
closefd(&p.result, marker[1]);
return;
};
if (pid == 0) {
child(c, av, ep, infd, outfd, errfd, marker[0], marker[1]);
};
p.pid = pid;
p.state = state.RUNNING as i32;
p.markerfd = marker[0];
closefd(&p.result, infd);
closefd(&p.result, outfd);
closefd(&p.result, errfd);
closefd(&p.result, marker[1]);
// The child creates its group before user code. The parent repeats the
// operation to close the cancel-before-child-runs race.
let pgrc: i32 = os.setpgid(pid, pid);
if (pgrc < 0 && pgrc != -13 && pgrc != -3) {
p.result.termination = termination.ERROR;
p.result.errno = -pgrc;
setcleanup(&p.result, os.kill(-pid, os.SIGTERM));
p.termdeadline = nowns() + (p.grace: i64);
p.state = state.TERM as i32;
};
};
fn decodemarker(p: *process) void = {
if (p.markern != 4) {
if (p.result.termination != termination.TIMEOUT) {
p.result.termination = termination.ERROR;
};
if (p.result.errno == 0) { p.result.errno = 71; };
return;
};
let e: u32 = p.marker[0]: u32
| ((p.marker[1]: u32) << 8u32)
| ((p.marker[2]: u32) << 16u32)
| ((p.marker[3]: u32) << 24u32);
if (p.result.termination != termination.TIMEOUT) {
p.result.termination = termination.ERROR;
};
if (p.result.errno == 0) { p.result.errno = e: i32; };
if (p.result.errno == 0) { p.result.errno = 5; };
};
fn closemarker(p: *process) void = {
if (p.markerfd < 0) { return; };
closefd(&p.result, p.markerfd);
p.markerfd = -1;
};
fn readmarker(p: *process) void = {
if (p.markerfd < 0 || p.markerclosed || p.markern == 4) { return; };
for (true) {
let n: i64 = os.read(p.markerfd, &p.marker[p.markern],
(4 - p.markern): u64);
if (n > 0) {
p.markern += n: i32;
if (p.markern == 4) { decodemarker(p); return; };
continue;
};
if (n == 0) { p.markerclosed = true; return; };
if (n == -4i64) { continue; };
if (n == -11i64) { return; };
p.markerclosed = true;
if (p.result.termination != termination.TIMEOUT) {
p.result.termination = termination.ERROR;
};
if (p.result.errno == 0) { p.result.errno = (-n): i32; };
return;
};
};
fn groupsignal(p: *process, sig: i32) i32 = {
let rc: i32 = os.kill(-p.pid, sig);
setcleanup(&p.result, rc);
return rc;
};
fn leadersignal(p: *process, sig: i32) i32 = {
let rc: i32 = os.kill(p.pid, sig);
setcleanup(&p.result, rc);
return rc;
};
fn leaderterminal(p: *process) bool = {
return p.reaped || p.leaderdone;
};
// 0 means no group member remains, 1 means the group is live, and -1 is an
// explicit cleanup failure.
fn groupstate(p: *process) i32 = {
let rc: i32 = os.kill(-p.pid, 0);
if (rc == -3) { return 0; };
if (rc == 0) { return 1; };
setcleanup(&p.result, rc);
return -1;
};
fn finish(p: *process) void = {
closemarker(p);
p.state = state.EMPTY as i32;
p.done = true;
};
fn startterm(p: *process, now: i64) void = {
groupsignal(p, os.SIGTERM);
p.termdeadline = now + (p.grace: i64);
p.state = state.TERM as i32;
};
fn startkill(p: *process) void = {
groupsignal(p, os.SIGKILL);
if (!leaderterminal(p)) { leadersignal(p, os.SIGKILL); };
p.state = state.KILL as i32;
};
fn advancecleanup(p: *process, now: i64) bool = {
if (p.state == (state.RUNNING as i32)) { return false; };
let gs: i32 = groupstate(p);
if (leaderterminal(p) && gs == 0) { finish(p); return true; };
if (p.state == (state.TERM as i32) && now >= p.termdeadline) {
startkill(p);
gs = groupstate(p);
};
if (p.state == (state.KILL as i32)) {
groupsignal(p, os.SIGKILL);
if (!leaderterminal(p)) { leadersignal(p, os.SIGKILL); };
gs = groupstate(p);
if (leaderterminal(p) && (gs == 0 || gs < 0)) {
finish(p);
return true;
};
};
return false;
};
fn reap(p: *process, status: i32) void = {
p.reaped = true;
p.leaderdone = true;
if (os.wifexited(status)) {
p.reaptermination = termination.EXIT;
p.reapcode = os.wexitstatus(status);
} else if (os.wifsignaled(status)) {
p.reaptermination = termination.SIGNAL;
p.reapcode = os.wtermsig(status);
} else {
if (p.result.errno == 0) { p.result.errno = 71; };
};
readmarker(p);
if (p.markern == 0 && p.markerclosed) {
if (p.result.termination != termination.TIMEOUT
&& p.result.errno == 0) {
p.result.termination = p.reaptermination;
p.result.code = p.reapcode;
};
} else if (p.markern != 4 && p.result.errno == 0) {
decodemarker(p);
};
if (p.state == (state.RUNNING as i32)) {
startterm(p, nowns());
};
};
fn waitleader(p: *process) bool = {
if (p.reaped) { return true; };
let status: i32 = 0;
for (true) {
let pid: i32 = os.wait4(p.pid, &status, os.WNOHANG, nil: *void);
if (pid == p.pid) { reap(p, status); return true; };
if (pid == 0) { return false; };
if (pid == -4) { continue; };
if (p.result.errno == 0) { p.result.errno = -pid; };
if (pid == -10) { p.leaderdone = true; };
if (p.result.termination != termination.TIMEOUT) {
p.result.termination = termination.ERROR;
};
if (p.state == (state.RUNNING as i32)) { startterm(p, nowns()); };
return false;
};
return false;
};
export fn poll(p: *process) bool = {
if (p.done) { return true; };
if (p.pid <= 0 || p.state == (state.EMPTY as i32)) { return true; };
readmarker(p);
waitleader(p);
let now: i64 = nowns();
if (p.state == (state.RUNNING as i32) && p.hasdeadline
&& p.markerclosed && p.markern == 0 && p.result.errno == 0
&& now >= p.deadline) {
p.result.termination = termination.TIMEOUT;
p.result.code = 0;
startterm(p, now);
};
advancecleanup(p, now);
return p.done;
};
export fn cancel(p: *process) void = {
if (p.done || p.pid <= 0 || p.state == (state.EMPTY as i32)) { return; };
if (p.state == (state.RUNNING as i32)) { startterm(p, nowns()); };
};
export fn run(c: *command, out: *result) void = {
let watch: interrupt;
if (!interruptopen(&watch)) {
out.termination = termination.ERROR;
out.code = 0;
out.errno = watch.errno;
out.cleanuperrno = 0;
return;
};
let p: process;
let interrupted: i32 = 0;
start(&p, c);
for (!p.done) {
let signo: i32 = interruptpoll(&watch);
if (signo != 0) {
if (signo > 0 && interrupted == 0) { interrupted = signo; };
cancel(&p);
};
if (poll(&p)) { break; };
time.sleep(time.millisecond, time.clock.monotonic);
};
interruptclose(&watch);
if (watch.errno != 0 && p.result.cleanuperrno == 0) {
p.result.cleanuperrno = watch.errno;
};
*out = p.result;
if (interrupted != 0) { os.kill(os.getpid(), interrupted); };
};
// runstdio executes one child with the caller's stdin/stdout/stderr and waits
// only for that child. It deliberately does not create or clean a process
// group: commands such as a compiler driver or a user program own any
// descendants they create. env is the complete environment; an empty slice
// requests an empty environment. The close-on-exec marker keeps an execve
// setup failure distinct from a real program exit 127.
export fn runstdio(path: str, argv: []str, env: []str, out: *result) void = {
out.termination = termination.ERROR;
out.code = 0;
out.errno = 0;
out.cleanuperrno = 0;
if (path.len == 0 || argv.len == 0 || argv[0].len == 0
|| hasnul(path)) {
out.errno = 22;
return;
};
let i: i32 = 0;
for (i < argv.len) {
if (hasnul(argv[i])) { out.errno = 22; return; };
i += 1;
};
i = 0;
for (i < env.len) {
if (hasnul(env[i])) { out.errno = 22; return; };
i += 1;
};
let marker: [2]i32;
let rc: i32 = os.pipe2(&marker, os.O_CLOEXEC);
if (rc < 0) { out.errno = -rc; return; };
marker[0] = safefd(out, marker[0]);
if (marker[0] < 0) {
out.errno = -marker[0];
closefd(out, marker[1]);
return;
};
marker[1] = safefd(out, marker[1]);
if (marker[1] < 0) {
out.errno = -marker[1];
closefd(out, marker[0]);
return;
};
let av: []*u8 = ctable(argv);
let ep: []*u8 = ctable(env);
let pid: i32 = os.fork();
if (pid < 0) {
out.errno = -pid;
closefd(out, marker[0]);
closefd(out, marker[1]);
return;
};
if (pid == 0) {
if (launchmaskactive) {
rc = os.sigprocmask(os.SIG_SETMASK, &launcholdmask,
nil: *u64);
if (rc < 0) { childmark(marker[1], rc); };
};
childclose(marker[0], marker[1]);
rc = os.execve(path, av.ptr, ep.ptr);
childmark(marker[1], rc);
};
closefd(out, marker[1]);
let status: i32 = 0;
let waited: i32 = 0;
for (true) {
waited = os.wait4(pid, &status, 0, nil: *void);
if (waited == pid) { break; };
if (waited == -4) { continue; };
out.errno = -waited;
if (out.errno <= 0) { out.errno = 5; };
break;
};
let mark: [4]u8;
let markn: i32 = 0;
for (waited == pid && markn < 4) {
let n: i64 = os.read(marker[0], &mark[markn], (4 - markn): u64);
if (n > 0) { markn += n: i32; continue; };
if (n == 0) { break; };
if (n == -4i64) { continue; };
out.errno = (-n): i32;
break;
};
closefd(out, marker[0]);
if (waited != pid || out.errno != 0) { return; };
if (markn != 0) {
if (markn != 4) { out.errno = 71; return; };
let e: u32 = mark[0]: u32
| ((mark[1]: u32) << 8u32)
| ((mark[2]: u32) << 16u32)
| ((mark[3]: u32) << 24u32);
out.errno = e: i32;
if (out.errno == 0) { out.errno = 5; };
out.code = 127;
return;
};
if (os.wifexited(status)) {
out.termination = termination.EXIT;
out.code = os.wexitstatus(status);
return;
};
if (os.wifsignaled(status)) {
out.termination = termination.SIGNAL;
out.code = os.wtermsig(status);
return;
};
out.errno = 71;
};
export fn interruptopen(w: *interrupt) bool = {
w.fd = -1;
w.oldmask = 0u64;
w.active = false;
w.errno = 0;
if (launchmaskactive) { w.errno = 16; return false; };
let mask: u64 = (1u64 << ((os.SIGINT - 1): u64))
| (1u64 << ((os.SIGTERM - 1): u64));
let rc: i32 = os.sigprocmask(os.SIG_BLOCK, &mask, &w.oldmask);
if (rc < 0) { w.errno = -rc; return false; };
w.fd = os.signalfd(-1, &mask, os.SFD_CLOEXEC | os.SFD_NONBLOCK);
if (w.fd < 0) {
w.errno = -w.fd;
os.sigprocmask(os.SIG_SETMASK, &w.oldmask, nil: *u64);
w.fd = -1;
return false;
};
launcholdmask = w.oldmask;
launchmaskactive = true;
w.active = true;
return true;
};
export fn interruptpoll(w: *interrupt) i32 = {
if (!w.active || w.fd < 0) { w.errno = 22; return -22; };
let info: [128]u8;
for (true) {
let n: i64 = os.read(w.fd, &info[0], size([128]u8));
if (n == -4i64) { continue; };
if (n == -11i64) { return 0; };
if (n < 0) { w.errno = (-n): i32; return n: i32; };
if (n != size([128]u8): i64) { w.errno = 71; return -71; };
let signo: u32 = info[0]: u32
| ((info[1]: u32) << 8u32)
| ((info[2]: u32) << 16u32)
| ((info[3]: u32) << 24u32);
return signo: i32;
};
return 0;
};
export fn interruptclose(w: *interrupt) bool = {
if (!w.active) { return w.errno == 0; };
let ok: bool = true;
let rc: i32 = os.close(w.fd);
if (rc < 0) { w.errno = -rc; ok = false; };
rc = os.sigprocmask(os.SIG_SETMASK, &w.oldmask, nil: *u64);
if (rc < 0) {
if (w.errno == 0) { w.errno = -rc; };
ok = false;
};
launchmaskactive = false;
launcholdmask = 0u64;
w.fd = -1;
w.active = false;
return ok;
};