code wiki / (root) / nx_heal_lib.nx

nx_heal_lib.nx source

↩ module page · 419 lines · 21093 B

1// nx_heal_lib.nx -- CONSOLIDATED heal-plane tool, LIBRARY half (importable: no main; the CLI is 2// nx_heal.nx, the gate nx_heal_gate.nx asserts on these functions' RETURN VALUES directly). 3// Tool #11 of the 15-tool architecture: one verb-router, composing what exists, ONE MCP stub. 4// READ-ONLY first increment: surfaces the supervision + 5// self-heal + remediation state of the NAS plane so an operator or workstream can SEE/MONITOR over 6// MCP what tonight's prevention layer is doing (tools_selfheal / edge_selfheal / supervisor snap). 7// Verbs: 8// status -> one rollup: supervisor snapshot (UP/DOWN counts + every non-UP line), 9// selfheal_status.tsv, last self-heal log line + remediation count per plane 10// log tools|edge -> tail of that plane's self-heal log 11// remediations -> per-plane remediation event counts + most recent epochs 12// All verbs take an optional basedir (default "." = ~/nishihost on the NAS); the gate points it at a 13// fixture dir. Mutating verbs (reconcile/reap) are a LATER increment behind a separate cap -- this 14// increment is honestly read-only (schema ro=1). license_tier: ORIGINAL 15import "nx_syscalls.nx" 16import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 17import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 18import "nx_seg_store.nx" // ss_readall (canonical file slurp) -- no new primitives 19import "nx_vsz_watchdog_core.nx" // vw_read (bounded file read) / vw_num_at / vw_contains -- portable file+string helpers 20import "nx_os_proc.nx" // osp_* -- OS process-introspection SEAM (Linux procfs / NishiOS-native). NO raw /proc above this. 21 22const NH_BUF_CAP: i64 = 262144 // max bytes considered per surfaced file (logs stay small by design) 23const NH_TAIL_LINES: i64 = 20 // `log` verb: lines of tail shown 24const NH_RECENT_EVTS: i64 = 5 // `remediations`: most recent epochs echoed 25const NH_PATH_CAP: i64 = 512 26const NH_ASCII_0: i64 = 48 // '0' (decimal print) 27 28func nh_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 29// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 30// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 31// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 32// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 33func nh_putn(v: i64) -> i64 { nxi_out(v); return 0 } 34func nh_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 35func nh_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i } 36// basedir + "/" + name, NUL-terminated 37func nh_path(out: *u8, base: *u8, name: *u8) -> i64 { 38 var o: i64 = nh_cat(out, 0, base) 39 if o > 0 { if out[o-1] != (47 as u8) { out[o] = 47 as u8; o = o + 1 } } 40 o = nh_cat(out, o, name) 41 out[o] = 0 as u8 42 return o 43} 44// slurp basedir/name; returns len (0 if absent/empty), buffer in *bufp 45func nh_read(base: *u8, name: *u8, bufp: *i64) -> i64 { 46 let p: *u8 = sys_mmap(NH_PATH_CAP) 47 nh_path(p, base, name) 48 let szp: *i64 = sys_mmap(16) as *i64 49 let b: *u8 = ss_readall(p, szp) 50 bufp[0] = b as i64 51 var n: i64 = szp[0] 52 if n < 0 { n = 0 } 53 if n > NH_BUF_CAP { n = NH_BUF_CAP } 54 return n 55} 56// print the LAST `want` lines of buf[0..n) 57func nh_tail(buf: *u8, n: i64, want: i64) -> i64 { 58 var lines: i64 = 0 59 var i: i64 = 0 60 while i < n { if buf[i] == (10 as u8) { lines = lines + 1 } i = i + 1 } 61 if n > 0 { if buf[n-1] != (10 as u8) { lines = lines + 1 } } 62 var skip: i64 = lines - want 63 if skip < 0 { skip = 0 } 64 var at: i64 = 0 65 i = 0 66 while i < n { 67 if at >= skip { sys_write(1, buf + i, 1) } 68 if buf[i] == (10 as u8) { at = at + 1 } 69 i = i + 1 70 } 71 if n > 0 { if buf[n-1] != (10 as u8) { nh_puts("\n" as *u8) } } 72 return lines 73} 74// last line of buf (no trailing-\n dependence); prints "(none)" if empty 75func nh_lastline(buf: *u8, n: i64) -> i64 { 76 if n <= 0 { nh_puts("(none)\n" as *u8); return 0 } 77 var e: i64 = n 78 if buf[e-1] == (10 as u8) { e = e - 1 } 79 var s: i64 = e 80 var go: i64 = 1 81 while go == 1 { go = 0; if s > 0 { if buf[s-1] != (10 as u8) { s = s - 1; go = 1 } } } 82 sys_write(1, buf + s, e - s) 83 nh_puts("\n" as *u8) 84 return 1 85} 86// count newline-terminated lines 87func nh_lines(buf: *u8, n: i64) -> i64 { 88 var c: i64 = 0; var i: i64 = 0 89 while i < n { if buf[i] == (10 as u8) { c = c + 1 } i = i + 1 } 90 if n > 0 { if buf[n-1] != (10 as u8) { c = c + 1 } } 91 return c 92} 93// NUL-terminated `lit` matches buf at position i (bounded by end)? 94func nh_match(buf: *u8, i: i64, end: i64, lit: *u8) -> i64 { 95 var k: i64 = 0 96 while lit[k] != (0 as u8) { 97 if i + k >= end { return 0 } 98 if buf[i + k] != lit[k] { return 0 } 99 k = k + 1 100 } 101 return 1 102} 103// does line [ls,le) contain " UP " ? (supervisor snap: "SVC <name> <port> UP 1 0 0 0") 104func nh_line_has_up(buf: *u8, ls: i64, le: i64) -> i64 { 105 var i: i64 = ls 106 while i < le { 107 if nh_match(buf, i, le, " UP " as *u8) == 1 { return 1 } 108 i = i + 1 109 } 110 return 0 111} 112func nh_is_svc(buf: *u8, ls: i64, le: i64) -> i64 { 113 return nh_match(buf, ls, le, "SVC " as *u8) 114} 115 116// status: supervisor snap rollup + selfheal state per plane 117func nh_status(base: *u8) -> i64 { 118 nh_puts("=== NX-HEAL STATUS (read-only rollup) ===\n" as *u8) 119 let bp: *i64 = sys_mmap(16) as *i64 120 // supervisor snapshot: count SVC UP vs not; echo every non-UP line verbatim 121 let sn: i64 = nh_read(base, "mgmt_snap.json" as *u8, bp) 122 let sb: *u8 = bp[0] as *u8 123 var up: i64 = 0 124 var down: i64 = 0 125 if sn > 0 { 126 var ls: i64 = 0; var i: i64 = 0 127 while i <= sn { 128 var eol: i64 = 0 129 if i == sn { eol = 1 } else { if sb[i] == (10 as u8) { eol = 1 } } 130 if eol == 1 { 131 if nh_is_svc(sb, ls, i) == 1 { 132 if nh_line_has_up(sb, ls, i) == 1 { up = up + 1 } else { 133 down = down + 1 134 nh_puts(" !! " as *u8); sys_write(1, sb + ls, i - ls); nh_puts("\n" as *u8) 135 } 136 } 137 ls = i + 1 138 } 139 i = i + 1 140 } 141 } 142 nh_puts("supervisor: svc-up=" as *u8); nh_putn(up); nh_puts(" svc-DOWN=" as *u8); nh_putn(down) 143 if sn <= 0 { nh_puts(" (no snapshot!)" as *u8) } 144 nh_puts("\n" as *u8) 145 // selfheal_status.tsv verbatim -- RETIRED 2026-07-16 (writer nx_selfheal_monitor.sh retired; the 146 // live truth is the fleet organ below). Kept as a labeled legacy read so old fixtures still resolve. 147 let tn: i64 = nh_read(base, "selfheal_status.tsv" as *u8, bp) 148 if tn > 0 { nh_puts("--- selfheal_status.tsv (RETIRED 07-16, frozen) ---\n" as *u8); sys_write(1, bp[0] as *u8, tn) } 149 // FLEET ORGAN (2026-07-16): nx_daemon_supervisor owns per-daemon supervision now (daemons.reg; 150 // /status on 127.0.0.1:18095 for live state). Its log carries the remediation events (REVIVED / 151 // EXE-PURGE / WEDGE-KILL) that replaced the per-plane bash selfheal logs below. 152 nh_puts("--- fleet organ (daemon_supervisor.log; live state :18095) ---\nlast: " as *u8) 153 let fl: i64 = nh_read(base, "daemon_supervisor.log" as *u8, bp) 154 let fb: *u8 = bp[0] as *u8 155 nh_lastline(fb, fl) 156 var frev: i64 = 0 157 var fpur: i64 = 0 158 var fls: i64 = 0 159 var fi: i64 = 0 160 while fi <= fl { 161 var feol: i64 = 0 162 if fi == fl { feol = 1 } else { if fb[fi] == (10 as u8) { feol = 1 } } 163 if feol == 1 { 164 if fi > fls { 165 if vw_contains((fb as i64 + fls) as *u8, fi - fls, "REVIVED" as *u8, 7) == 1 { frev = frev + 1 } 166 if vw_contains((fb as i64 + fls) as *u8, fi - fls, "EXE-PURGE" as *u8, 9) == 1 { fpur = fpur + 1 } 167 if vw_contains((fb as i64 + fls) as *u8, fi - fls, "WEDGE-KILL" as *u8, 10) == 1 { fpur = fpur + 1 } 168 } 169 fls = fi + 1 170 } 171 fi = fi + 1 172 } 173 nh_puts("revives=" as *u8); nh_putn(frev); nh_puts(" purges+wedgekills=" as *u8); nh_putn(fpur); nh_puts("\n" as *u8) 174 // TENANCY ORGAN (2026-07-16): INV-1..9 sentinel; report on 127.0.0.1:18094; alerts appended on NEW. 175 nh_puts("--- tenancy organ (tenancy_alerts.log; report :18094) ---\nlast: " as *u8) 176 let ta: i64 = nh_read(base, "tenancy_alerts.log" as *u8, bp) 177 nh_lastline(bp[0] as *u8, ta) 178 // per-plane bash selfheal -- RETIRED 2026-07-16 (writers in retired/); frozen last-events kept, labeled 179 nh_puts("--- tools plane (bash selfheal RETIRED 07-16, frozen) ---\nlast: " as *u8) 180 let tl: i64 = nh_read(base, "tools_selfheal.log" as *u8, bp) 181 nh_lastline(bp[0] as *u8, tl) 182 let tr: i64 = nh_read(base, "tools_remediations.log" as *u8, bp) 183 nh_puts("remediations-total=" as *u8); nh_putn(nh_lines(bp[0] as *u8, tr)); nh_puts("\n" as *u8) 184 nh_puts("--- edge plane (bash selfheal RETIRED 07-16, frozen) ---\nlast: " as *u8) 185 let el: i64 = nh_read(base, "edge_selfheal.log" as *u8, bp) 186 nh_lastline(bp[0] as *u8, el) 187 let er: i64 = nh_read(base, "edge_remediations.log" as *u8, bp) 188 nh_puts("remediations-total=" as *u8); nh_putn(nh_lines(bp[0] as *u8, er)); nh_puts("\n" as *u8) 189 nh_puts("verdict=" as *u8) 190 if down == 0 { nh_puts("GREEN\n" as *u8) } else { nh_puts("ATTENTION (services down)\n" as *u8) } 191 return down // 0 = all supervised services UP (gate-assertable; CLI exit mirrors it) 192} 193 194func nh_log(base: *u8, plane: *u8) -> i64 { 195 let bp: *i64 = sys_mmap(16) as *i64 196 var n: i64 = 0 197 if nh_streq(plane, "fleet" as *u8) == 1 { n = nh_read(base, "daemon_supervisor.log" as *u8, bp) } else { 198 if nh_streq(plane, "tenancy" as *u8) == 1 { n = nh_read(base, "tenancy_alerts.log" as *u8, bp) } else { 199 if nh_streq(plane, "tools" as *u8) == 1 { n = nh_read(base, "tools_selfheal.log" as *u8, bp) } else { 200 if nh_streq(plane, "edge" as *u8) == 1 { n = nh_read(base, "edge_selfheal.log" as *u8, bp) } else { 201 nh_puts("nx_heal log: unknown plane (fleet|tenancy|tools|edge)\n" as *u8); return 0 - 1 202 } } } } 203 if n <= 0 { nh_puts("(no log yet for this plane)\n" as *u8); return 0 } 204 return nh_tail(bp[0] as *u8, n, NH_TAIL_LINES) // total lines in the log (gate-assertable) 205} 206 207// ===================== diagnose: the WHY behind a crash-loop / wedge ===================== 208// Answers "why is <daemon> restarting?" with EVIDENCE: live pids + their AGES (a long-lived parent 209// with young children = fork-model workers, NOT restarts -- the false-positive that read as a 210// crash-loop), heal-plane remediation recency, and supervisor-log correlation. The classifier is a 211// PURE function (gated on synthetic inputs); the /proc plumbing reuses the vsz-watchdog's proven walk. 212const NH_OLD_SECS: i64 = 600 // a pid older than this = stable long-lived parent 213const NH_RECENT_WIN: i64 = 3600 // remediation recency window (matches selfheal's per-hour accounting) 214const NH_MAX_PIDS: i64 = 64 // matched-pid table cap 215const NH_ALLPID_CAP: i64 = 4096 // max live pids enumerated per scan (via the nx_os_proc seam) 216const NH_CL_CAP: i64 = 4096 // argv0 read cap 217const NH_SUP_CAP: i64 = 262144 // supervisor-log read cap 218// verdicts (returned by the pure classifier) 219const NH_V_DOWN: i64 = 0 // no live process 220const NH_V_STABLE: i64 = 1 // long-lived parent; pid churn = worker children (monitor false-positive) 221const NH_V_LOOP_HEAL: i64 = 2 // young processes + recent heal-plane remediations = selfheal is bouncing it 222const NH_V_LOOP_UNTRACKED: i64 = 3 // young processes, NO heal remediations = something ELSE restarts it 223 224func nh_str_eq(a: *u8, b: *u8) -> i64 { return nh_streq(a, b) } 225// wallclock now = OS boot-epoch + monotonic-since-boot (both via the nx_os_proc seam; NO raw /proc here) 226func nh_wallclock() -> i64 { 227 let be: i64 = osp_boot_epoch() 228 if be <= 0 { return 0 } 229 return be + osp_uptime_s() 230} 231// enumerate processes whose argv0 basename == daemon; fill pids[]/ages[]/ppids[] up to NH_MAX_PIDS. 232// OS-AGNOSTIC by construction: osp_* (nx_os_proc) is the ONLY OS touch-point -- Linux procfs today, 233// NishiOS-native tomorrow, same source. Returns count. 234func nh_scan(daemon: *u8, pids: *i64, ages: *i64, ppids: *i64) -> i64 { 235 let self: i64 = osp_selfpid() 236 let up: i64 = osp_uptime_s() 237 let hz: i64 = osp_hz() 238 let all: *i64 = sys_mmap(8 * NH_ALLPID_CAP) as *i64 239 let np: i64 = osp_list_pids(all, NH_ALLPID_CAP) 240 let a0: *u8 = sys_mmap(NH_CL_CAP) 241 var cnt: i64 = 0 242 var i: i64 = 0 243 while i < np { 244 let pid: i64 = all[i] 245 if pid != self { if cnt < NH_MAX_PIDS { 246 if osp_cmd_argv0(pid, a0, NH_CL_CAP - 1) > 0 { 247 if nh_str_eq(a0, daemon) == 1 { 248 let st: i64 = osp_starttime_ticks(pid) 249 if st >= 0 { if hz > 0 { 250 pids[cnt] = pid 251 ppids[cnt] = osp_ppid(pid) 252 ages[cnt] = up - st / hz 253 cnt = cnt + 1 254 } } 255 } 256 } 257 } } 258 i = i + 1 259 } 260 return cnt 261} 262// count leading-integer epochs in buf within [now-win, now] 263func nh_recent_epochs(buf: *u8, n: i64, now: i64, win: i64) -> i64 { 264 let ep: *i64 = sys_mmap(16) as *i64 265 var c: i64 = 0 266 var ls: i64 = 0 267 var i: i64 = 0 268 while i <= n { 269 var eol: i64 = 0 270 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } } 271 if eol == 1 { 272 if i > ls { 273 let v: i64 = vw_num_at(buf, i, ls, ep) 274 if v > 0 { if v >= now - win { if v <= now { c = c + 1 } } } 275 } 276 ls = i + 1 277 } 278 i = i + 1 279 } 280 return c 281} 282// PURE classifier (gated): why-verdict from the evidence triple 283func nh_classify(npids: i64, oldest_age: i64, recent_remed: i64) -> i64 { 284 if npids <= 0 { return NH_V_DOWN } 285 if oldest_age >= NH_OLD_SECS { return NH_V_STABLE } 286 if recent_remed > 0 { return NH_V_LOOP_HEAL } 287 return NH_V_LOOP_UNTRACKED 288} 289 290// diagnose <daemon> -- gather evidence, classify, explain. plane (tools|edge|-) selects which 291// remediations log correlates; base = dir of the heal-plane files (default "."). Returns the verdict. 292func nh_diagnose(daemon: *u8, plane: *u8, base: *u8) -> i64 { 293 nh_puts("=== NX-HEAL DIAGNOSE " as *u8); nh_puts(daemon); nh_puts(" ===\n" as *u8) 294 let pids: *i64 = sys_mmap(8 * NH_MAX_PIDS) as *i64 295 let ages: *i64 = sys_mmap(8 * NH_MAX_PIDS) as *i64 296 let ppids: *i64 = sys_mmap(8 * NH_MAX_PIDS) as *i64 297 let cnt: i64 = nh_scan(daemon, pids, ages, ppids) 298 nh_puts("live processes: " as *u8); nh_putn(cnt); nh_puts("\n" as *u8) 299 var oldest: i64 = 0 - 1 300 var i: i64 = 0 301 while i < cnt { 302 nh_puts(" pid=" as *u8); nh_putn(pids[i]) 303 nh_puts(" ppid=" as *u8); nh_putn(ppids[i]) 304 nh_puts(" age_s=" as *u8); nh_putn(ages[i]); nh_puts("\n" as *u8) 305 if ages[i] > oldest { oldest = ages[i] } 306 i = i + 1 307 } 308 // heal-plane remediation recency for the selected plane 309 var recent: i64 = 0 310 let bp: *i64 = sys_mmap(16) as *i64 311 let now: i64 = nh_wallclock() 312 if nh_str_eq(plane, "tools" as *u8) == 1 { 313 let n: i64 = nh_read(base, "tools_remediations.log" as *u8, bp) 314 recent = nh_recent_epochs(bp[0] as *u8, n, now, NH_RECENT_WIN) 315 } 316 if nh_str_eq(plane, "edge" as *u8) == 1 { 317 let n2: i64 = nh_read(base, "edge_remediations.log" as *u8, bp) 318 recent = nh_recent_epochs(bp[0] as *u8, n2, now, NH_RECENT_WIN) 319 } 320 nh_puts("heal-plane remediations (last hour, plane=" as *u8); nh_puts(plane); nh_puts("): " as *u8); nh_putn(recent); nh_puts("\n" as *u8) 321 // supervisor-log correlation: how often does the supervisor mention this daemon? 322 let sb: *u8 = sys_mmap(NH_SUP_CAP) 323 let sn: i64 = vw_read(osp_supervisor_log(), sb, NH_SUP_CAP - 1) // path via the seam (deployment-specific) 324 if sn > 0 { 325 var hits: i64 = 0 326 var ls: i64 = 0 327 var j: i64 = 0 328 var lastls: i64 = 0 - 1 329 while j <= sn { 330 var eol: i64 = 0 331 if j == sn { eol = 1 } else { if sb[j] == (10 as u8) { eol = 1 } } 332 if eol == 1 { 333 if vw_contains((sb as i64 + ls) as *u8, j - ls, daemon, vw_slen(daemon)) == 1 { hits = hits + 1; lastls = ls } 334 ls = j + 1 335 } 336 j = j + 1 337 } 338 nh_puts("supervisor-log mentions: " as *u8); nh_putn(hits) 339 if lastls >= 0 { 340 // print the LAST mentioning line verbatim (bounded to its newline) 341 var e: i64 = lastls 342 var ge: i64 = 1 343 while ge == 1 { ge = 0; if e < sn { if sb[e] != (10 as u8) { e = e + 1; ge = 1 } } } 344 nh_puts(" last: " as *u8) 345 sys_write(1, (sb as i64 + lastls) as *u8, e - lastls) 346 } 347 nh_puts("\n" as *u8) 348 } else { nh_puts("supervisor-log: (unreadable -- correlation skipped)\n" as *u8) } 349 // verdict + explanation 350 let v: i64 = nh_classify(cnt, oldest, recent) 351 nh_puts("WHY-VERDICT: " as *u8) 352 if v == NH_V_DOWN { nh_puts("DOWN -- no live process; check its launcher/selfheal + last log lines\n" as *u8) } 353 if v == NH_V_STABLE { nh_puts("STABLE -- long-lived parent (age>=600s); any pid churn is per-connection WORKER children, NOT restarts. A monitor counting pid churn as restarts is a FALSE-POSITIVE crash-loop.\n" as *u8) } 354 if v == NH_V_LOOP_HEAL { nh_puts("RESTART-LOOP (heal-plane) -- young processes + recent selfheal remediations: the selfheal keeps bouncing it; root-cause the underlying failure (its log tail), the restart is a symptom.\n" as *u8) } 355 if v == NH_V_LOOP_UNTRACKED { nh_puts("RESTART-LOOP (UNTRACKED) -- young processes but NO heal-plane remediations: something OUTSIDE the heal plane restarts it (guard/reconcile/crash+respawn); check supervisor-log mentions above.\n" as *u8) } 356 return v 357} 358 359// ===================== live organ surfaces over MCP (2026-07-16) ===================== 360// The fleet organ (:18095) and tenancy organ (:18094) serve their LIVE state as loopback HTTP text. 361// This fetches one and prints the BODY -- so `nx_heal fleet|tenancy` puts the live per-row state on MCP 362// through the already-capped heal tool (no new cap, no toolreg row). Plain HTTP GET, loopback-only by 363// construction (hardcoded 127.0.0.1), bounded read, 3s socket timeout -- a hung organ can't hang the tool. 364const NH_FETCH_CAP: i64 = 32768 365func nh_organ_body(port: i64) -> i64 { 366 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 367 if fd < 0 { nh_puts("(organ unreachable: socket failed)\n" as *u8); return 0 - 1 } 368 sys_set_socket_timeout(fd, 3) 369 let sa: *u8 = sys_mmap(16) 370 var i: i64 = 0 371 while i < 16 { sa[i] = 0 as u8; i = i + 1 } 372 sa[0] = 2 as u8 373 sa[2] = ((port / 256) % 256) as u8 374 sa[3] = (port % 256) as u8 375 sa[4] = 127 as u8 376 sa[7] = 1 as u8 377 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) != 0 { 378 sys_close(fd) 379 nh_puts("(organ DOWN on 127.0.0.1:" as *u8); nh_putn(port); nh_puts(" -- the supervisor should revive it)\n" as *u8) 380 return 0 - 1 381 } 382 let req: *u8 = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" as *u8 383 var rl: i64 = 0 384 while req[rl] != (0 as u8) { rl = rl + 1 } 385 sys_write(fd, req, rl) 386 let buf: *u8 = sys_mmap(NH_FETCH_CAP) 387 var n: i64 = 0 388 var rd: i64 = 1 389 while rd > 0 { 390 rd = sys_read(fd, (buf as i64 + n) as *u8, NH_FETCH_CAP - 1 - n) 391 if rd > 0 { n = n + rd } 392 if n >= NH_FETCH_CAP - 1 { rd = 0 } 393 } 394 sys_close(fd) 395 // print past the header terminator (\r\n\r\n); whole buffer if none found 396 var body: i64 = 0 397 i = 0 398 while i + 4 <= n { 399 if buf[i]==(13 as u8) { if buf[i+1]==(10 as u8) { if buf[i+2]==(13 as u8) { if buf[i+3]==(10 as u8) { body = i + 4; i = n } } } } 400 i = i + 1 401 } 402 sys_write(1, (buf as i64 + body) as *u8, n - body) 403 sys_munmap(buf, NH_FETCH_CAP) 404 return 0 405} 406 407func nh_remediations(base: *u8) -> i64 { 408 nh_puts("=== remediation events (force-restarts) ===\n" as *u8) 409 let bp: *i64 = sys_mmap(16) as *i64 410 let tn: i64 = nh_read(base, "tools_remediations.log" as *u8, bp) 411 let tcnt: i64 = nh_lines(bp[0] as *u8, tn) 412 nh_puts("tools: total=" as *u8); nh_putn(tcnt); nh_puts(" recent:\n" as *u8) 413 if tn > 0 { nh_tail(bp[0] as *u8, tn, NH_RECENT_EVTS) } 414 let en: i64 = nh_read(base, "edge_remediations.log" as *u8, bp) 415 let ecnt: i64 = nh_lines(bp[0] as *u8, en) 416 nh_puts("edge: total=" as *u8); nh_putn(ecnt); nh_puts(" recent:\n" as *u8) 417 if en > 0 { nh_tail(bp[0] as *u8, en, NH_RECENT_EVTS) } 418 return tcnt + ecnt // combined event count (gate-assertable) 419}