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}