code wiki / _hdl_build / nx_snapfeed.nx

nx_snapfeed.nx source

↩ module page · 103 lines · 4623 B

1// nx_snapfeed.nx -- the /proc + supervisor.log PARSE logic that produces the FEED consumed by nx_mgmt_snapshot's 2// ss_synth (which in turn emits the SUP/SVC snapshot /api/health reads). This is the DATA-gathering parse tier of 3// the R1b producer: pid/ppid/sid extraction + service identification + crash-loop restart counting. PURE + 4// gateable (sf_*, imports only nx_syscalls); the LIVE /proc directory enumeration reuses nx_hostctl's existing 5// proc-scan (anti-reinvention) -- this organ is the parse logic that scan will call per pid. 6// 7// TWO hard-won correctness lessons are encoded here (both from real incidents): 8// 1) IDENTIFY A SERVICE BY ITS FULL /proc/<pid>/cmdline, NEVER by `comm`. The kernel truncates comm to 15 chars 9// (TASK_COMM_LEN-1), so `nx_gallery_gateway.elf` (22 ch) shows as `nx_gallery_gate` -- a comm match SILENTLY 10// MISSES it (this exact bug = the 2026-06-29 gallery-login disaster: a kill matched nothing while reporting 11// success). sf_contains over the full cmdline blob is the fix. 12// 2) PARSE /proc/<pid>/stat ROBUSTLY: field 2 (comm) is wrapped in parens and MAY CONTAIN SPACES AND PARENS, so 13// a naive space-split corrupts ppid/sid. We anchor on the LAST ')' then read fields [state ppid pgrp sid]. 14// license_tier: ORIGINAL 15import "nx_syscalls.nx" 16 17func sf_digit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 } 18 19func sf_atoi(b: *u8, off: i64, len: i64) -> i64 { 20 var v: i64 = 0 21 var i: i64 = 0 22 while i < len { let c: i64 = b[off + i] as i64; if sf_digit(c) == 1 { v = v * 10 + (c - 48) } i = i + 1 } 23 return v 24} 25 26// substring search within b[lo..hi) for the null-terminated needle. 27func sf_contains_seg(b: *u8, lo: i64, hi: i64, needle: *u8) -> i64 { 28 var sl: i64 = 0 29 while needle[sl] != (0 as u8) { sl = sl + 1 } 30 if sl == 0 { return 1 } 31 var i: i64 = lo 32 while i + sl <= hi { 33 var j: i64 = 0 34 var ok: i64 = 1 35 while j < sl { if (b[i + j] as i64) != (needle[j] as i64) { ok = 0 } j = j + 1 } 36 if ok == 1 { return 1 } 37 i = i + 1 38 } 39 return 0 40} 41 42// does the FULL cmdline blob (n bytes, NUL-or-space separated args) contain the service key? (lesson #1) 43func sf_contains(b: *u8, n: i64, needle: *u8) -> i64 { return sf_contains_seg(b, 0, n, needle) } 44 45// robust /proc/<pid>/stat parse -> out3[0]=pid, out3[1]=ppid, out3[2]=sid. returns 1 ok / 0 malformed. (lesson #2) 46func sf_parse_stat(buf: *u8, n: i64, out3: *i64) -> i64 { 47 // pid = leading integer (field 1, before the comm '('). 48 var pid: i64 = 0 49 var i: i64 = 0 50 var go: i64 = 1 51 while go == 1 { 52 if i >= n { go = 0 } else { 53 let c: i64 = buf[i] as i64 54 if sf_digit(c) == 1 { pid = pid * 10 + (c - 48); i = i + 1 } else { go = 0 } 55 } 56 } 57 // anchor on the LAST ')' so a comm with embedded spaces/parens can't shift the fields. 58 var lp: i64 = 0 - 1 59 var k: i64 = 0 60 while k < n { if (buf[k] as i64) == 41 { lp = k } k = k + 1 } 61 if lp < 0 { return 0 } 62 // after ')': token 0 = state, 1 = ppid, 2 = pgrp, 3 = sid. 63 var ppid: i64 = 0 64 var sid: i64 = 0 65 var pos: i64 = lp + 1 66 var tok: i64 = 0 67 var done: i64 = 0 68 while done == 0 { 69 var sk: i64 = 1 70 while sk == 1 { if pos >= n { sk = 0 } else { if (buf[pos] as i64) == 32 { pos = pos + 1 } else { sk = 0 } } } 71 if pos >= n { done = 1 } else { 72 let st: i64 = pos 73 var sc: i64 = 1 74 while sc == 1 { if pos >= n { sc = 0 } else { if (buf[pos] as i64) == 32 { sc = 0 } else { pos = pos + 1 } } } 75 if tok == 1 { ppid = sf_atoi(buf, st, pos - st) } 76 if tok == 3 { sid = sf_atoi(buf, st, pos - st); done = 1 } 77 tok = tok + 1 78 } 79 } 80 out3[0] = pid 81 out3[1] = ppid 82 out3[2] = sid 83 return 1 84} 85 86// is this proc a session LEADER (an independent lineage)? pid == sid. (keeper/conn-children share sid, pid!=sid) 87func sf_is_leader(pid: i64, sid: i64) -> i64 { if pid == sid { return 1 } return 0 } 88 89// count guard-restarts of <key> in the supervisor.log window: lines containing BOTH "restarted" AND the key. 90func sf_count_restarts(log: *u8, n: i64, key: *u8) -> i64 { 91 var cnt: i64 = 0 92 var cur: i64 = 0 93 while cur < n { 94 var le: i64 = cur 95 var f: i64 = 0 96 while f == 0 { if le >= n { f = 1 } else { if (log[le] as i64) == 10 { f = 1 } else { le = le + 1 } } } 97 if sf_contains_seg(log, cur, le, "restarted" as *u8) == 1 { 98 if sf_contains_seg(log, cur, le, key) == 1 { cnt = cnt + 1 } 99 } 100 cur = le + 1 101 } 102 return cnt 103}