nx_sweep_core.nx source
↩ module page · 136 lines · 6727 B
1// nx_sweep_core.nx -- STANDING-SWEEP core (library). The SOTA property a naive sweep lacks: it NEVER
2// HANGS. Each check runs in a forked child whose stdout/stderr are captured, under a SINGLE wall-clock
3// DEADLINE; if the child overruns it is SIGKILLed and the check verdict is TIMEOUT -- one slow/wedged
4// check can never stall the whole sweep. Pure + gate-testable: sw_run_deadline is the primitive, sw_verdict
5// maps (rc, expect) -> verdict. Data-driven: checks come from a registry the daemon parses, never hardcoded.
6// license_tier: ORIGINAL
7import "nx_syscalls.nx"
8
9const SW_TIMEOUT: i64 = 0 - 100 // child killed on the deadline
10const SW_SPAWN_ERR: i64 = 0 - 101 // pipe/fork failed
11const SW_POLLIN: i64 = 1 // poll events: data-to-read
12const SW_SIGKILL: i64 = 9
13const SW_MS_PER_S: i64 = 1000
14const SW_NS_PER_MS: i64 = 1000000
15const SW_POLL_TICK: i64 = 200 // max ms per poll wait (re-checks the deadline this often)
16const SW_REAP_TICK: i64 = 5 // ms between WNOHANG reap polls after EOF
17const SW_FD_MASK: i64 = 0xFFFFFFFF
18const SW_B0: i64 = 0xFF // byte masks for the 32-bit pollfd fd field
19const SW_SH8: i64 = 8
20const SW_SH16: i64 = 16
21const SW_SH24: i64 = 24
22const SW_POLLFD_BYTES: i64 = 8 // struct pollfd = {int fd; short events; short revents}
23const SW_PF_EV: i64 = 4 // events offset
24// verdict codes (data-driven; no magic in callers)
25const SW_V_PASS: i64 = 0
26const SW_V_FAIL: i64 = 1
27const SW_V_TIMEOUT: i64 = 2
28const SW_V_ERROR: i64 = 3
29const SW_PTRPAIR: i64 = 16 // scratch for a two-i64 struct (timespec / fds / wait-status)
30const SW_EXEC_FAIL: i64 = 127 // child exit code when execve fails (bad path / not ELF)
31const SW_FD_STDOUT: i64 = 1
32const SW_FD_STDERR: i64 = 2
33const SW_NFDS_1: i64 = 1 // poll: exactly one fd
34const SW_SCR_PF: i64 = 16 // scratch offset: pollfd (after fds[2])
35const SW_SCR_STP: i64 = 24 // scratch offset: wait-status (after pollfd)
36const SW_SCR_MIN: i64 = 40 // minimum caller scratch bytes
37
38// monotonic milliseconds (elapsed measurement -- immune to wall-clock steps). LEAK-FREE: munmaps its
39// scratch (this is called MANY times per check in the deadline poll loop; a bare sys_mmap here leaked a
40// 4KB page per call -- invisible until fast-cycled, the exact never-surfaced-until-measured leak class).
41func sw_mono_ms() -> i64 {
42 let ts: *i64 = sys_mmap(SW_PTRPAIR) as *i64
43 sys_clock_gettime_mono(ts)
44 let r: i64 = ts[0] * SW_MS_PER_S + ts[1] / SW_NS_PER_MS
45 sys_munmap(ts as *u8, SW_PTRPAIR)
46 return r
47}
48// map a run result + expected exit code -> verdict
49func sw_verdict(rc: i64, expect: i64) -> i64 {
50 if rc == SW_TIMEOUT { return SW_V_TIMEOUT }
51 if rc == SW_SPAWN_ERR { return SW_V_ERROR }
52 if rc == expect { return SW_V_PASS }
53 return SW_V_FAIL
54}
55// write a struct pollfd {int fd; short events=POLLIN; short revents=0} into pf[0..8). SW_PF_EV (=4) doubles
56// as the fd byte-width; the two shorts after it are zeroed. Byte-loop -> no literal offsets (rule 11).
57func sw_pollfd(pf: *u8, fd: i64) -> i64 {
58 var b: i64 = 0
59 while b < SW_PF_EV { pf[b] = ((fd >> (b * SW_SH8)) & SW_B0) as u8; b = b + 1 } // 32-bit fd, little-endian
60 pf[SW_PF_EV] = SW_POLLIN as u8
61 var e: i64 = SW_POLLIN // =1: start after the low events byte
62 while e < SW_PF_EV { pf[SW_PF_EV + e] = 0 as u8; e = e + 1 } // clear events-hi + revents
63 return 0
64}
65// Run `path` with argv (NULL-terminated *i64 vector, argv[0]=path) in directory `cwd` (0 or ""-> inherit),
66// capturing up to cap bytes of stdout+stderr into out, KILLING the child if it overruns deadline_ms.
67// Returns the child's exit code, or SW_TIMEOUT (killed), or SW_SPAWN_ERR. outlen (if non-null) gets bytes.
68// cwd is applied IN THE CHILD (isolated -- the daemon's own cwd is never mutated).
69// `scr` is a caller-provided scratch block (>= SW_SCR_MIN bytes) reused across calls, so a hot loop calling
70// this every cycle does ZERO sys_mmap here (the forever-daemon never-leak requirement). Layout: fds@0(16),
71// pf@SW_SCR_PF(8), stp@SW_SCR_STP(16).
72func sw_run_deadline(path: *u8, argv: *i64, cwd: *u8, out: *u8, cap: i64, outlen: *i64, deadline_ms: i64, scr: *u8) -> i64 {
73 if (outlen as i64) != 0 { outlen[0] = 0 }
74 let fds: *i64 = scr as *i64
75 if sys_pipe2(fds, 0) != 0 { return SW_SPAWN_ERR }
76 let packed: i64 = fds[0]
77 let rfd: i64 = packed & SW_FD_MASK
78 let wfd: i64 = (packed >> SW_SH24 >> SW_SH8) & SW_FD_MASK // >>32, staged to avoid a 32-shift literal
79 let pid: i64 = sys_fork()
80 if pid < 0 { sys_close(rfd); sys_close(wfd); return SW_SPAWN_ERR }
81 if pid == 0 {
82 sys_dup3(wfd, SW_FD_STDOUT, 0)
83 sys_dup3(wfd, SW_FD_STDERR, 0)
84 sys_close(rfd)
85 sys_close(wfd)
86 if (cwd as i64) != 0 { if cwd[0] != (0 as u8) { sys_chdir(cwd) } }
87 let envp: *i64 = sys_mmap(SW_PTRPAIR) as *i64
88 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
89 envp[1] = 0
90 sys_execve(path, argv, envp)
91 sys_exit(SW_EXEC_FAIL)
92 return 0
93 }
94 sys_close(wfd)
95 let pf: *u8 = ((scr as i64) + SW_SCR_PF) as *u8
96 let stp: *i64 = ((scr as i64) + SW_SCR_STP) as *i64
97 let start: i64 = sw_mono_ms()
98 var total: i64 = 0
99 var eof: i64 = 0
100 // ---- drain phase: read until EOF or deadline ----
101 while eof == 0 {
102 let remaining: i64 = deadline_ms - (sw_mono_ms() - start)
103 if remaining <= 0 {
104 nx_kill(pid, SW_SIGKILL)
105 sys_wait4(pid, stp, 0)
106 sys_close(rfd)
107 if (outlen as i64) != 0 { outlen[0] = total }
108 return SW_TIMEOUT
109 }
110 var wait: i64 = remaining
111 if wait > SW_POLL_TICK { wait = SW_POLL_TICK }
112 sw_pollfd(pf, rfd)
113 let pr: i64 = sys_poll(pf, SW_NFDS_1, wait)
114 if pr > 0 {
115 if total < cap {
116 let r: i64 = sys_read(rfd, ((out as i64) + total) as *u8, cap - total)
117 if r <= 0 { eof = 1 } else { total = total + r }
118 } else { eof = 1 } // buffer full -> stop reading, still reap under the deadline
119 }
120 }
121 sys_close(rfd)
122 if (outlen as i64) != 0 { outlen[0] = total }
123 // ---- reap phase: bounded by the SAME deadline ----
124 var run: i64 = 1
125 while run == 1 {
126 let w: i64 = sys_wait4(pid, stp, WNOHANG)
127 if w == pid { return wait_exit_code(stp[0]) }
128 if deadline_ms - (sw_mono_ms() - start) <= 0 {
129 nx_kill(pid, SW_SIGKILL)
130 sys_wait4(pid, stp, 0)
131 return SW_TIMEOUT
132 }
133 sys_sleep_ms(SW_REAP_TICK)
134 }
135 return SW_SPAWN_ERR
136}