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, "merge")) { if (!writeexact(os.STDOUT_FILENO, "out 1\n")) { os.exit(146); }; if (!writeexact(os.STDERR_FILENO, "err 1\n")) { os.exit(147); }; if (!writeexact(os.STDOUT_FILENO, "out 2\n")) { os.exit(148); }; if (!writeexact(os.STDERR_FILENO, "err 2\n")) { os.exit(149); }; 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, "merge", root, "merge", time.second, grace); let mergeerr: str = c.stderrpath; c.stderrpath = c.stdoutpath; exec.run(&c, &r); assert(r.termination == exec.termination.EXIT && r.code == 0); assert(r.errno == 0 && r.cleanuperrno == 0); assert(eq(readfile(c.stdoutpath), "out 1\nerr 1\nout 2\nerr 2\n")); assert(!os.exists(mergeerr)); 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); }; fillcommand(&cc, self, "merge", root, "closedmerge", time.second, grace); let closedmergeerr: str = cc.stderrpath; cc.stderrpath = cc.stdoutpath; exec.run(&cc, &cr); if (cr.termination != exec.termination.EXIT || cr.code != 0 || cr.errno != 0 || cr.cleanuperrno != 0) { os.exit(150); }; if (!eq(readfile(cc.stdoutpath), "out 1\nerr 1\nout 2\nerr 2\n")) { os.exit(151); }; if (os.exists(closedmergeerr)) { os.exit(152); }; 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", "merge.out", "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", "closedmerge.out", "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)); };