code wiki / (root) / nx_zombie_audit.nx

nx_zombie_audit.nx source

↩ module page · 113 lines · 6336 B

1// nx_zombie_audit.nx -- SOVEREIGN DEFUNCT_ZOMBIE_DETECTION (the capability nx_daemon_health_audit lists 2// as MISSING). Scans /proc (getdents, no ps/shell), parses /proc/<pid>/stat for state 'Z' (defunct/zombie), 3// maps each to its PARENT pid+comm, and reports per-parent zombie counts -- so the doctor (and the operator) 4// can SEE a fork-per-connection daemon leaking unreaped children (the nx_mp_serve 516-leak class). A parent 5// over ZA_LEAK_THRESHOLD zombies is flagged LEAK. Run: nx_zombie_audit. license_tier: ORIGINAL 6// 7// module: nishi-core.perception.zombie_audit 8// capability: PERCEPTION (daemon health) 9import "nx_syscalls.nx" 10const ZA_MAGIC_4096: i64 = 4096 11const ZA_MAGIC_1024: i64 = 1024 12 13const ZA_BUF: i64 = 65536 14const ZA_LEAK_THRESHOLD: i64 = 5 // > this many unreaped children under one parent = a reap-leak, not normal churn 15 16func za_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 17func za_n(v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m} var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} let b: *u8=sys_mmap(28); var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 } 18func za_pidnum(name: *u8) -> i64 { 19 var v: i64=0; var i: i64=0 20 if name[0]==(0 as u8) { return 0-1 } 21 while name[i]!=(0 as u8) { let c: i64=name[i] as i64; if c<48 {return 0-1} if c>57 {return 0-1} v=v*10+(c-48); i=i+1 } 22 return v 23} 24// write decimal `v` into dst at off; returns new off. 25func za_n_into(dst: *u8, off: i64, v: i64) -> i64 { 26 let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m} var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 27 var i: i64=0; while i<k { dst[off+i]=t[k-1-i]; i=i+1 } return off+k 28} 29// read /proc/<pid>/<leaf> into buf (NUL-terminated). returns bytes, or -1. 30func za_readfile(pid: i64, leaf: *u8, buf: *u8, cap: i64) -> i64 { 31 let path: *u8 = sys_mmap(128) 32 var po: i64=0; let pf: *u8="/proc/" as *u8; while pf[po]!=(0 as u8){path[po]=pf[po];po=po+1} 33 po = za_n_into(path, po, pid) 34 var li: i64=0; while leaf[li]!=(0 as u8){path[po]=leaf[li];po=po+1;li=li+1} path[po]=0 as u8 35 let fd: i64 = sys_openat_rd(path); if fd<0 { return 0-1 } 36 let r: i64 = sys_read(fd, buf, cap-1); sys_close(fd) 37 if r>0 { buf[r]=0 as u8 } else { buf[0]=0 as u8 } 38 return r 39} 40// parse /proc/<pid>/stat -> state char (return) + ppid (out_ppid). stat = "pid (comm) S ppid ..."; 41// comm can contain ')' and spaces, so scan to the LAST ')' then read state + ppid. 42func za_stat(pid: i64, out_ppid: *i64) -> i64 { 43 out_ppid[0] = 0 44 let buf: *u8 = sys_mmap(ZA_MAGIC_4096); let r: i64 = za_readfile(pid, "/stat" as *u8, buf, ZA_MAGIC_4096) 45 if r <= 0 { return 0 } 46 var last: i64 = 0 - 1; var i: i64 = 0 47 while i < r { if buf[i] == (41 as u8) { last = i } i = i + 1 } // last ')' (comm may contain ')') 48 if last < 0 { return 0 } 49 var p: i64 = last + 1 50 var d1: i64 = 0; while d1 == 0 { if p >= r { d1 = 1 } else { if buf[p] == (32 as u8) { p = p + 1 } else { d1 = 1 } } } // skip spaces 51 if p >= r { return 0 } 52 let state: i64 = buf[p] as i64 // field 3 = state char 53 p = p + 1 54 var d2: i64 = 0; while d2 == 0 { if p >= r { d2 = 1 } else { if buf[p] == (32 as u8) { p = p + 1 } else { d2 = 1 } } } // skip spaces 55 var pp: i64 = 0; var dg: i64 = 1 56 while dg == 1 { if p >= r { dg = 0 } else { let c: i64 = buf[p] as i64; if c >= 48 { if c <= 57 { pp = pp*10 + (c-48); p = p + 1 } else { dg = 0 } } else { dg = 0 } } } // field 4 = ppid 57 out_ppid[0] = pp 58 return state 59} 60 61// scan /proc; count total zombies (state 'Z'=90), and fill per-parent (ppid,count). returns #leaking parents. 62func za_scan(out_ppid: *i64, out_cnt: *i64, cap: i64, total_out: *i64) -> i64 { 63 let fd: i64 = sys_openat_rd("/proc" as *u8); if fd<0 { total_out[0]=0; return 0 } 64 let gbuf: *u8 = sys_mmap(ZA_BUF) 65 var nparents: i64 = 0; var total: i64 = 0; var go: i64 = 1 66 while go == 1 { 67 let n: i64 = sys_getdents64(fd, gbuf, ZA_BUF) 68 if n <= 0 { go = 0 } else { 69 var off: i64 = 0 70 while off < n { 71 let rec: *u8 = ((gbuf as i64) + off) as *u8 72 let reclen: i64 = dirent_reclen(rec) 73 let name: *u8 = dirent_name(rec) 74 let pid: i64 = za_pidnum(name) 75 if pid > 0 { 76 let ppb: *i64 = sys_mmap(16) as *i64 77 let st: i64 = za_stat(pid, ppb) 78 if st == 90 { // 'Z' 79 total = total + 1 80 let ppid: i64 = ppb[0] 81 var f: i64 = 0-1; var k: i64 = 0 82 while k < nparents { if out_ppid[k]==ppid { f=k; k=nparents } else { k=k+1 } } 83 if f >= 0 { out_cnt[f] = out_cnt[f] + 1 } else { if nparents < cap { out_ppid[nparents]=ppid; out_cnt[nparents]=1; nparents=nparents+1 } } 84 } 85 } 86 if reclen <= 0 { off = n } else { off = off + reclen } 87 } 88 } 89 } 90 sys_close(fd) 91 total_out[0] = total 92 return nparents 93} 94 95func main() -> i64 { 96 let pp: *i64 = sys_mmap(8*ZA_MAGIC_1024) as *i64; let cc: *i64 = sys_mmap(8*ZA_MAGIC_1024) as *i64; let tot: *i64 = sys_mmap(16) as *i64 97 let nparents: i64 = za_scan(pp, cc, ZA_MAGIC_1024, tot) 98 za_p("=== NISHI ZOMBIE AUDIT (defunct-process / unreaped-child detection) ===\n" as *u8) 99 za_p("total_zombies=" as *u8); za_n(tot[0]); za_p(" leaking_parents=" as *u8) 100 var leaks: i64 = 0; var i: i64 = 0 101 while i < nparents { if cc[i] > ZA_LEAK_THRESHOLD { leaks = leaks + 1 } i = i + 1 } 102 za_n(leaks); za_p("\n" as *u8) 103 i = 0 104 while i < nparents { 105 let comm: *u8 = sys_mmap(64); za_readfile(pp[i], "/comm" as *u8, comm, 64) // parent comm 106 var cl: i64=0; while comm[cl]!=(0 as u8) { if comm[cl]==(10 as u8) { comm[cl]=0 as u8 } cl=cl+1 } 107 za_p(" parent pid=" as *u8); za_n(pp[i]); za_p(" comm=" as *u8); za_p(comm); za_p(" zombies=" as *u8); za_n(cc[i]) 108 if cc[i] > ZA_LEAK_THRESHOLD { za_p(" <<< LEAK (fork-per-conn daemon not reaping -- add WNOHANG wait4)" as *u8) } 109 za_p("\n" as *u8) 110 i = i + 1 111 } 112 sys_exit(0); return 0 113}