os/exec: subprocess supervision
Captured-async start/poll/cancel plus inherited-stdio run/runstdio with process-group creation, close-on-exec errno marker for exec-setup failure, TERM-grace-KILL escalation, and a signalfd interrupt watch. test/wwfixture/process/main.ww is its self-exec harness, built by both driver stages.
This commit is contained in:
687
lib/os/exec/exec.ww
Normal file
687
lib/os/exec/exec.ww
Normal file
@@ -0,0 +1,687 @@
|
|||||||
|
// 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.
|
||||||
|
// stdoutpath and stderrpath are created exclusively with mode 0600. A zero
|
||||||
|
// deadline means no timeout.
|
||||||
|
export type command = struct {
|
||||||
|
path: str,
|
||||||
|
argv: []str,
|
||||||
|
env: []str,
|
||||||
|
dir: 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 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.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, 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(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 outfd: i32 = os.open(c.stdoutpath,
|
||||||
|
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
|
||||||
|
if (outfd < 0) { fail(p, outfd); return; };
|
||||||
|
outfd = safefd(&p.result, outfd);
|
||||||
|
if (outfd < 0) { fail(p, outfd); return; };
|
||||||
|
let errfd: i32 = os.open(c.stderrpath,
|
||||||
|
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
|
||||||
|
if (errfd < 0) {
|
||||||
|
fail(p, errfd);
|
||||||
|
closefd(&p.result, outfd);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
errfd = safefd(&p.result, errfd);
|
||||||
|
if (errfd < 0) {
|
||||||
|
fail(p, errfd);
|
||||||
|
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, outfd);
|
||||||
|
closefd(&p.result, errfd);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
marker[0] = safefd(&p.result, marker[0]);
|
||||||
|
if (marker[0] < 0) {
|
||||||
|
fail(p, marker[0]);
|
||||||
|
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, 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, outfd);
|
||||||
|
closefd(&p.result, errfd);
|
||||||
|
closefd(&p.result, marker[0]);
|
||||||
|
closefd(&p.result, marker[1]);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (pid == 0) { child(c, av, ep, outfd, errfd, marker[0], marker[1]); };
|
||||||
|
p.pid = pid;
|
||||||
|
p.state = state.RUNNING as i32;
|
||||||
|
p.markerfd = marker[0];
|
||||||
|
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;
|
||||||
|
};
|
||||||
398
test/wwfixture/process/main.ww
Normal file
398
test/wwfixture/process/main.ww
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
package main;
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import temp;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
fn eq(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 writeexact(fd: i32, s: str) bool = {
|
||||||
|
match (os.writeall(fd, s.ptr, s.len: u64)) {
|
||||||
|
case let n: i64 => return n == s.len: i64;
|
||||||
|
case let e: os.oserror => return false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn readfile(path: str) str = {
|
||||||
|
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||||
|
assert(!(fd < 0));
|
||||||
|
let sr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
|
let n: i64 = -1i64;
|
||||||
|
match (sr) {
|
||||||
|
case let v: i64 => n = v;
|
||||||
|
case let e: os.oserror => abort("filesize failed");
|
||||||
|
};
|
||||||
|
assert(!(n < 0i64));
|
||||||
|
let b: []u8 = alloc([], (n + 1i64): u64)!;
|
||||||
|
b.len = (n + 1i64): i32;
|
||||||
|
let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64);
|
||||||
|
assert(!(os.close(fd) != 0));
|
||||||
|
let got: i64 = -1i64;
|
||||||
|
match (rr) {
|
||||||
|
case let v: i64 => got = v;
|
||||||
|
case let e: os.oserror => abort("read failed");
|
||||||
|
};
|
||||||
|
assert(got == n);
|
||||||
|
let ni: i32 = n: i32;
|
||||||
|
b[ni] = 0u8;
|
||||||
|
let out: str;
|
||||||
|
out.ptr = b.ptr;
|
||||||
|
out.len = n: i32;
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn waitchild(pid: i32) i32 = {
|
||||||
|
let status: i32 = 0;
|
||||||
|
for (true) {
|
||||||
|
let got: i32 = os.wait4(pid, &status, 0i32, nil: *void);
|
||||||
|
if (got == pid) { return status; };
|
||||||
|
if (got != -4) { return -1; };
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn childmode(args: []str) void = {
|
||||||
|
let mode: str = args[2];
|
||||||
|
if (eq(mode, "exit0")) { os.exit(0); };
|
||||||
|
if (eq(mode, "exit7")) { os.exit(7); };
|
||||||
|
if (eq(mode, "exit127")) { os.exit(127); };
|
||||||
|
if (eq(mode, "route")) {
|
||||||
|
if (args.len != 5 || !eq(args[3], "token")) { os.exit(129); };
|
||||||
|
match (os.getenv("ROUTE")) {
|
||||||
|
case let value: str => if (!eq(value, "value")) { os.exit(130); };
|
||||||
|
case void => os.exit(131);
|
||||||
|
};
|
||||||
|
let cwdbytes: [4096]u8;
|
||||||
|
let n: i64 = os.getcwd(&cwdbytes[0], size([4096]u8));
|
||||||
|
if (n <= 1i64 || n > size([4096]u8): i64) { os.exit(132); };
|
||||||
|
let cwd: str;
|
||||||
|
cwd.ptr = &cwdbytes[0];
|
||||||
|
cwd.len = (n - 1i64): i32;
|
||||||
|
if (!eq(cwd, args[4])) { os.exit(133); };
|
||||||
|
if (!writeexact(os.STDOUT_FILENO, "route stdout\n")) { os.exit(134); };
|
||||||
|
if (!writeexact(os.STDERR_FILENO, "route stderr\n")) { os.exit(135); };
|
||||||
|
os.exit(0);
|
||||||
|
};
|
||||||
|
if (eq(mode, "signal")) {
|
||||||
|
os.kill(os.getpid(), os.SIGKILL);
|
||||||
|
os.exit(120);
|
||||||
|
};
|
||||||
|
if (eq(mode, "hold")) {
|
||||||
|
for (true) { time.sleep(time.second, time.clock.monotonic); };
|
||||||
|
};
|
||||||
|
if (eq(mode, "resistdesc")) {
|
||||||
|
let mask: u64 = 1u64 << ((os.SIGTERM - 1): u64);
|
||||||
|
if (os.sigprocmask(os.SIG_BLOCK, &mask, nil: *u64) != 0) {
|
||||||
|
os.exit(121);
|
||||||
|
};
|
||||||
|
let pid: i32 = os.fork();
|
||||||
|
if (pid < 0) { os.exit(122); };
|
||||||
|
if (pid > 0 && args.len == 4) {
|
||||||
|
let readyfd: i32 = os.open(args[3],
|
||||||
|
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
|
||||||
|
if (readyfd < 0) { os.exit(142); };
|
||||||
|
let me: u32 = os.getpid(): u32;
|
||||||
|
let ready: [4]u8;
|
||||||
|
ready[0] = me: u8;
|
||||||
|
ready[1] = (me >> 8u32): u8;
|
||||||
|
ready[2] = (me >> 16u32): u8;
|
||||||
|
ready[3] = (me >> 24u32): u8;
|
||||||
|
if (os.write(readyfd, &ready[0], 4u64) != 4i64) {
|
||||||
|
os.exit(143);
|
||||||
|
};
|
||||||
|
if (os.close(readyfd) != 0) { os.exit(144); };
|
||||||
|
};
|
||||||
|
for (true) { time.sleep(time.second, time.clock.monotonic); };
|
||||||
|
};
|
||||||
|
if (eq(mode, "exitdesc")) {
|
||||||
|
let mask: u64 = 1u64 << ((os.SIGTERM - 1): u64);
|
||||||
|
if (os.sigprocmask(os.SIG_BLOCK, &mask, nil: *u64) != 0) {
|
||||||
|
os.exit(124);
|
||||||
|
};
|
||||||
|
let ready: [2]i32;
|
||||||
|
if (os.pipe(&ready) != 0) { os.exit(125); };
|
||||||
|
let pid: i32 = os.fork();
|
||||||
|
if (pid < 0) { os.exit(126); };
|
||||||
|
let b: [1]u8 = [1u8];
|
||||||
|
if (pid == 0) {
|
||||||
|
os.close(ready[0]);
|
||||||
|
if (os.write(ready[1], &b[0], 1u64) != 1i64) { os.exit(127); };
|
||||||
|
os.close(ready[1]);
|
||||||
|
for (true) { time.sleep(time.second, time.clock.monotonic); };
|
||||||
|
};
|
||||||
|
os.close(ready[1]);
|
||||||
|
if (os.read(ready[0], &b[0], 1u64) != 1i64) { os.exit(128); };
|
||||||
|
os.close(ready[0]);
|
||||||
|
os.exit(0);
|
||||||
|
};
|
||||||
|
os.exit(123);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn fillcommand(c: *exec.command, self: str, mode: str, root: str,
|
||||||
|
name: str, lifetime: time.duration, grace: time.duration) void = {
|
||||||
|
let av: []str = alloc([], 3u64)!;
|
||||||
|
append(av, self);
|
||||||
|
append(av, "child");
|
||||||
|
append(av, mode);
|
||||||
|
let env: []str = alloc([], 2u64)!;
|
||||||
|
append(env, "PATH=/usr/bin:/bin");
|
||||||
|
append(env, "LC_ALL=C");
|
||||||
|
c.path = self;
|
||||||
|
c.argv = av;
|
||||||
|
c.env = env;
|
||||||
|
c.dir = root;
|
||||||
|
c.stdoutpath = strings.concat(root, "/", name, ".out");
|
||||||
|
c.stderrpath = strings.concat(root, "/", name, ".err");
|
||||||
|
c.deadline = time.add(time.now(time.clock.monotonic), lifetime);
|
||||||
|
c.grace = grace;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn waitdone(p: *exec.process) void = {
|
||||||
|
for (!exec.poll(p)) {
|
||||||
|
time.sleep(time.millisecond, time.clock.monotonic);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cancelall(ps: []exec.process) bool = {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < ps.len) { exec.cancel(&ps[i]); i += 1; };
|
||||||
|
let pending: bool = true;
|
||||||
|
for (pending) {
|
||||||
|
pending = false;
|
||||||
|
i = 0;
|
||||||
|
for (i < ps.len) {
|
||||||
|
if (!exec.poll(&ps[i])) { pending = true; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
if (pending) { time.sleep(time.millisecond, time.clock.monotonic); };
|
||||||
|
};
|
||||||
|
let ok: bool = true;
|
||||||
|
i = 0;
|
||||||
|
for (i < ps.len) {
|
||||||
|
if (ps[i].pid > 0 && (ps[i].result.errno != 0
|
||||||
|
|| ps[i].result.cleanuperrno != 0)) { ok = false; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() void = {
|
||||||
|
let args: []str = os.args();
|
||||||
|
if (args.len >= 3 && eq(args[1], "child")) {
|
||||||
|
childmode(args);
|
||||||
|
};
|
||||||
|
assert(!(args.len == 0));
|
||||||
|
let self: str = strings.dup(args[0]);
|
||||||
|
if (self.len == 0 || self[0] != '/': u8) {
|
||||||
|
let cwdbuf: [4096]u8;
|
||||||
|
let cwdn: i64 = os.getcwd(&cwdbuf[0], size([4096]u8));
|
||||||
|
assert(!(cwdn <= 1i64 || cwdn > size([4096]u8): i64));
|
||||||
|
let cwd: str;
|
||||||
|
cwd.ptr = &cwdbuf[0];
|
||||||
|
cwd.len = (cwdn - 1i64): i32;
|
||||||
|
self = strings.concat(cwd, "/", self);
|
||||||
|
};
|
||||||
|
let root: str = strings.dup(temp.dir());
|
||||||
|
let p: exec.process;
|
||||||
|
let c: exec.command;
|
||||||
|
let r: exec.result;
|
||||||
|
let grace: time.duration =
|
||||||
|
(20i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
|
||||||
|
fillcommand(&c, self, "exit0", root, "exit0",
|
||||||
|
time.second, grace);
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.EXIT && r.code == 0);
|
||||||
|
assert(r.errno == 0 && r.cleanuperrno == 0);
|
||||||
|
|
||||||
|
fillcommand(&c, self, "exitdesc", root, "exitdesc",
|
||||||
|
time.second, grace);
|
||||||
|
let descbefore: time.instant = time.now(time.clock.monotonic);
|
||||||
|
exec.start(&p, &c);
|
||||||
|
waitdone(&p);
|
||||||
|
let descafter: time.instant = time.now(time.clock.monotonic);
|
||||||
|
assert(p.result.termination == exec.termination.EXIT
|
||||||
|
&& p.result.code == 0);
|
||||||
|
assert(!((time.diff(descbefore, descafter): i64) < grace: i64));
|
||||||
|
assert(!(os.kill(-p.pid, 0) != -3));
|
||||||
|
|
||||||
|
fillcommand(&c, self, "exit7", root, "exit7",
|
||||||
|
time.second, grace);
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.EXIT && r.code == 7);
|
||||||
|
|
||||||
|
fillcommand(&c, self, "exit127", root, "exit127",
|
||||||
|
time.second, grace);
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.EXIT && r.code == 127);
|
||||||
|
assert(r.errno == 0);
|
||||||
|
|
||||||
|
fillcommand(&c, self, "route", root, "route",
|
||||||
|
time.second, grace);
|
||||||
|
let routeargv: []str = c.argv;
|
||||||
|
append(routeargv, "token");
|
||||||
|
append(routeargv, root);
|
||||||
|
c.argv = routeargv;
|
||||||
|
let routeenv: []str = c.env;
|
||||||
|
append(routeenv, "ROUTE=value");
|
||||||
|
c.env = routeenv;
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.EXIT && r.code == 0);
|
||||||
|
assert(eq(readfile(c.stdoutpath), "route stdout\n"));
|
||||||
|
assert(eq(readfile(c.stderrpath), "route stderr\n"));
|
||||||
|
|
||||||
|
fillcommand(&c, self, "signal", root, "signal",
|
||||||
|
time.second, grace);
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.SIGNAL);
|
||||||
|
assert(!(r.code != os.SIGKILL));
|
||||||
|
|
||||||
|
fillcommand(&c, "/no/such/exec-program", "exit0", root, "execfail",
|
||||||
|
time.second, grace);
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.ERROR && r.errno == 2);
|
||||||
|
|
||||||
|
fillcommand(&c, "/no/such/exec-program", "exit0", root, "pastfail",
|
||||||
|
time.second, grace);
|
||||||
|
c.deadline = time.add(time.now(time.clock.monotonic),
|
||||||
|
(-1i64 * (time.second: i64)): time.duration);
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.ERROR && r.errno == 2);
|
||||||
|
|
||||||
|
fillcommand(&c, self, "exit0", root, "chdirfail",
|
||||||
|
time.second, grace);
|
||||||
|
c.dir = strings.concat(root, "/missing-directory");
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.ERROR && r.errno == 2);
|
||||||
|
|
||||||
|
fillcommand(&c, self, "resistdesc", root, "timeout",
|
||||||
|
(300i64 * (time.millisecond: i64)): time.duration, grace);
|
||||||
|
let until: i64 = time.diff(time.now(time.clock.monotonic),
|
||||||
|
c.deadline): i64;
|
||||||
|
assert(!(until < 250i64 * (time.millisecond: i64)));
|
||||||
|
let timeoutbefore: time.instant = time.now(time.clock.monotonic);
|
||||||
|
exec.start(&p, &c);
|
||||||
|
waitdone(&p);
|
||||||
|
let timeoutafter: time.instant = time.now(time.clock.monotonic);
|
||||||
|
assert(p.result.termination == exec.termination.TIMEOUT);
|
||||||
|
assert(!((time.diff(timeoutbefore, timeoutafter): i64)
|
||||||
|
< 250i64 * (time.millisecond: i64)));
|
||||||
|
assert(!(os.kill(-p.pid, 0) != -3));
|
||||||
|
|
||||||
|
let exists: str = strings.concat(root, "/exists.out");
|
||||||
|
let fd: i32 = os.open(exists,
|
||||||
|
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
|
||||||
|
assert(!(fd < 0));
|
||||||
|
assert(!(os.close(fd) != 0));
|
||||||
|
fillcommand(&c, self, "exit0", root, "unused",
|
||||||
|
time.second, grace);
|
||||||
|
c.stdoutpath = exists;
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.ERROR && r.errno == 17);
|
||||||
|
|
||||||
|
let readyfile: str = strings.concat(root, "/runinterrupt.ready");
|
||||||
|
let coordinator: i32 = os.fork();
|
||||||
|
assert(!(coordinator < 0));
|
||||||
|
if (coordinator == 0) {
|
||||||
|
let ic: exec.command;
|
||||||
|
let ir: exec.result;
|
||||||
|
fillcommand(&ic, self, "resistdesc", root, "runinterrupt",
|
||||||
|
(10i64 * (time.second: i64)): time.duration, grace);
|
||||||
|
let iav: []str = ic.argv;
|
||||||
|
append(iav, readyfile);
|
||||||
|
ic.argv = iav;
|
||||||
|
exec.run(&ic, &ir);
|
||||||
|
os.exit(145);
|
||||||
|
};
|
||||||
|
let readywait: i32 = 0;
|
||||||
|
for (os.access(readyfile, 0i32) != 0 && readywait < 2000) {
|
||||||
|
time.sleep(time.millisecond, time.clock.monotonic);
|
||||||
|
readywait += 1;
|
||||||
|
};
|
||||||
|
assert(!(os.access(readyfile, 0i32) != 0));
|
||||||
|
let readybytes: str = readfile(readyfile);
|
||||||
|
assert(readybytes.len == 4);
|
||||||
|
let runpid: i32 = (readybytes[0]: u32
|
||||||
|
| ((readybytes[1]: u32) << 8u32)
|
||||||
|
| ((readybytes[2]: u32) << 16u32)
|
||||||
|
| ((readybytes[3]: u32) << 24u32)): i32;
|
||||||
|
assert(!(os.kill(coordinator, os.SIGINT) != 0));
|
||||||
|
let coordinatorstatus: i32 = waitchild(coordinator);
|
||||||
|
assert(coordinatorstatus >= 0 && os.wifsignaled(coordinatorstatus)
|
||||||
|
&& os.wtermsig(coordinatorstatus) == os.SIGINT);
|
||||||
|
assert(!(os.kill(-runpid, 0) != -3));
|
||||||
|
|
||||||
|
let closedprobe: i32 = os.fork();
|
||||||
|
assert(!(closedprobe < 0));
|
||||||
|
if (closedprobe == 0) {
|
||||||
|
if (os.close(os.STDOUT_FILENO) != 0) { os.exit(137); };
|
||||||
|
if (os.close(os.STDERR_FILENO) != 0) { os.exit(138); };
|
||||||
|
let cc: exec.command;
|
||||||
|
let cr: exec.result;
|
||||||
|
fillcommand(&cc, self, "route", root, "closed",
|
||||||
|
time.second, grace);
|
||||||
|
let cav: []str = cc.argv;
|
||||||
|
append(cav, "token");
|
||||||
|
append(cav, root);
|
||||||
|
cc.argv = cav;
|
||||||
|
let cev: []str = cc.env;
|
||||||
|
append(cev, "ROUTE=value");
|
||||||
|
cc.env = cev;
|
||||||
|
exec.run(&cc, &cr);
|
||||||
|
if (cr.termination != exec.termination.EXIT || cr.code != 0
|
||||||
|
|| cr.errno != 0 || cr.cleanuperrno != 0) { os.exit(139); };
|
||||||
|
if (!eq(readfile(cc.stdoutpath), "route stdout\n")) { os.exit(140); };
|
||||||
|
if (!eq(readfile(cc.stderrpath), "route stderr\n")) { os.exit(141); };
|
||||||
|
os.exit(0);
|
||||||
|
};
|
||||||
|
let closedstatus: i32 = waitchild(closedprobe);
|
||||||
|
assert(closedstatus >= 0 && os.wifexited(closedstatus)
|
||||||
|
&& os.wexitstatus(closedstatus) == 0);
|
||||||
|
|
||||||
|
let watch: exec.interrupt;
|
||||||
|
assert(exec.interruptopen(&watch));
|
||||||
|
let handles: []exec.process = alloc([], 2u64)!;
|
||||||
|
let zero: exec.process;
|
||||||
|
append(handles, zero);
|
||||||
|
append(handles, zero);
|
||||||
|
fillcommand(&c, self, "hold", root, "interrupt",
|
||||||
|
(10i64 * (time.second: i64)): time.duration, grace);
|
||||||
|
exec.start(&handles[0], &c);
|
||||||
|
assert(!(os.kill(os.getpid(), os.SIGINT) != 0));
|
||||||
|
let signo: i32 = 0;
|
||||||
|
for (signo == 0) {
|
||||||
|
signo = exec.interruptpoll(&watch);
|
||||||
|
};
|
||||||
|
assert(!(signo != os.SIGINT));
|
||||||
|
assert(cancelall(handles));
|
||||||
|
assert(handles[0].result.termination == exec.termination.SIGNAL);
|
||||||
|
assert(exec.poll(&handles[1]));
|
||||||
|
assert(exec.interruptclose(&watch));
|
||||||
|
|
||||||
|
let captures: []str = [
|
||||||
|
"exit0.out", "exit0.err", "exitdesc.out", "exitdesc.err",
|
||||||
|
"exit7.out", "exit7.err", "exit127.out", "exit127.err",
|
||||||
|
"route.out", "route.err", "signal.out", "signal.err",
|
||||||
|
"execfail.out", "execfail.err", "pastfail.out", "pastfail.err",
|
||||||
|
"chdirfail.out", "chdirfail.err", "timeout.out", "timeout.err",
|
||||||
|
"exists.out", "runinterrupt.out", "runinterrupt.err",
|
||||||
|
"runinterrupt.ready",
|
||||||
|
"closed.out", "closed.err", "interrupt.out", "interrupt.err",
|
||||||
|
];
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < captures.len) {
|
||||||
|
assert(!(os.remove(strings.concat(root, "/", captures[i])) != 0));
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
assert(!(os.rmdir(root) != 0));
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user