code wiki / (root) / nx_os_introspect.nx

nx_os_introspect.nx source

↩ module page · 203 lines · 9957 B

1// nx_os_introspect.nx -- OS "OBSERVE THE RUNNING SYSTEM" library (CAP-OS-INTROSPECT / CAP-PORT-OWNER). 2// The sovereign /proc perception rung (axis=os, owner=CONDUCTOR, parent VAR-OS-NISHI): read the process + socket 3// tables via SYSCALLS ONLY (no ps/ss/lsof/netstat) to answer "what process really holds this port?". EXTRACTED 4// (un-trapped) from nx_portcheck.nx's GREEN 5/5 chain so it is IMPORTABLE -- the shared substrate BOTH the live 5// mgmt plane (nx_hostctl snapshot -> /api/services -> /health) AND the hardware-up digital twin (a twin must 6// introspect the running system it models) compose, instead of each re-implementing the /proc walk (the sprawl 7// this retires: nx_socket_list stubbed the very inode->PID step this finishes). NO main() -> pure library. 8// Chain: /proc/net/tcp{,6} LISTEN(0x0A) -> inode ; /proc/<pid>/fd socket:[inode] -> PID ; /proc/<pid>/{comm,cmdline}. 9// license_tier: ORIGINAL 10import "nx_syscalls.nx" 11const PON_MAGIC_32768: i64 = 32768 12 13const PON_TCP_LISTEN: i64 = 0x0A 14const PON_AT_FDCWD: i64 = 0 - 100 15const PON_SYS_READLINKAT: i64 = 267 // x86_64 readlinkat(dirfd, path, buf, bufsiz) 16 17func pon_is_space(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } return 0 } 18func pon_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 19 20// parse a hex run [s,e) -> value (ports in /proc/net/tcp are stored big-endian hex text, compared directly). 21func pon_hex(buf: *u8, s: i64, e: i64) -> i64 { 22 var v: i64 = 0; var i: i64 = s 23 while i < e { 24 let c: i64 = buf[i] & 0xff; var d: i64 = 0 - 1 25 if c >= 48 { if c <= 57 { d = c - 48 } } 26 if c >= 65 { if c <= 70 { d = c - 55 } } 27 if c >= 97 { if c <= 102 { d = c - 87 } } 28 if d < 0 { return v } 29 v = (v << 4) | d; i = i + 1 30 } 31 return v 32} 33// parse a decimal run [s,e) -> value 34func pon_dec(buf: *u8, s: i64, e: i64) -> i64 { 35 var v: i64 = 0; var i: i64 = s 36 while i < e { let c: i64 = buf[i] & 0xff; if c < 48 { return v } if c > 57 { return v } v = v*10 + (c-48); i = i + 1 } 37 return v 38} 39// whitespace-delimited field idx in [lstart,lend) -> fs/fe = its [start,end). returns 1/0. 40func pon_field(buf: *u8, lstart: i64, lend: i64, idx: i64, fs: *i64, fe: *i64) -> i64 { 41 var o: i64 = lstart; var f: i64 = 0 42 while o < lend { 43 while o < lend { if pon_is_space(buf[o] as i64) == 1 { o = o + 1 } else { break } } 44 if o >= lend { return 0 } 45 let st: i64 = o 46 while o < lend { if pon_is_space(buf[o] as i64) == 1 { break } else { o = o + 1 } } 47 if f == idx { fs[0] = st; fe[0] = o; return 1 } 48 f = f + 1 49 } 50 return 0 51} 52// scan a /proc/net/tcp* file for a LISTEN socket on `port`; return its inode (0 = none). 53func pon_scan_file(path: *u8, port: i64) -> i64 { 54 let lb: *i64 = sys_mmap(8) as *i64 55 let data: *u8 = sys_read_file(path, lb) 56 if (data as i64) == 0 { return 0 } 57 let n: i64 = lb[0] 58 let fs: *i64 = sys_mmap(8) as *i64; let fe: *i64 = sys_mmap(8) as *i64 59 var ls: i64 = 0; var first: i64 = 1 60 while ls < n { 61 var le: i64 = ls 62 while le < n { if data[le] == (10 as u8) { break } else { le = le + 1 } } 63 if first == 1 { first = 0 } 64 else { if le > ls { 65 if pon_field(data, ls, le, 3, fs, fe) == 1 { // st 66 if pon_hex(data, fs[0], fe[0]) == PON_TCP_LISTEN { 67 if pon_field(data, ls, le, 1, fs, fe) == 1 { // local_address "IP:PORT" 68 var cp: i64 = fs[0] 69 while cp < fe[0] { if data[cp] == (58 as u8) { break } else { cp = cp + 1 } } // ':' 70 if cp < fe[0] { 71 if pon_hex(data, cp + 1, fe[0]) == port { 72 if pon_field(data, ls, le, 9, fs, fe) == 1 { return pon_dec(data, fs[0], fe[0]) } // inode 73 } 74 } 75 } 76 } 77 } 78 } } 79 ls = le + 1 80 } 81 return 0 82} 83// inode of a LISTEN socket on `port` across IPv4 + IPv6 (0 = nothing listening on it). 84func pon_listener_inode(port: i64) -> i64 { 85 let i4: i64 = pon_scan_file("/proc/net/tcp" as *u8, port) 86 if i4 > 0 { return i4 } 87 return pon_scan_file("/proc/net/tcp6" as *u8, port) 88} 89 90func pon_readlink(path: *u8, out: *u8, cap: i64) -> i64 { 91 let r: i64 = __syscall(PON_SYS_READLINKAT, PON_AT_FDCWD, path as i64, out as i64, cap, 0, 0) 92 if r < 0 { return 0 } 93 if r < cap { out[r] = 0 as u8 } 94 return r 95} 96func pon_str_all_digits(s: *u8) -> i64 { var i: i64 = 0; if s[0] == (0 as u8) { return 0 } while s[i] != (0 as u8) { let c: i64 = s[i] & 0xff; if c < 48 { return 0 } if c > 57 { return 0 } i = i + 1 } return 1 } 97func pon_join3(a: *u8, b: *u8, c: *u8, out: *u8) -> i64 { 98 var o: i64 = 0 99 var i: i64 = 0; while a[i] != (0 as u8) { out[o]=a[i]; o=o+1; i=i+1 } 100 i=0; while b[i] != (0 as u8) { out[o]=b[i]; o=o+1; i=i+1 } 101 i=0; while c[i] != (0 as u8) { out[o]=c[i]; o=o+1; i=i+1 } 102 out[o]=0 as u8; return o 103} 104func pon_join(a: *u8, b: *u8, c: *u8, d: *u8, out: *u8) -> i64 { 105 var o: i64 = 0 106 var i: i64 = 0; while a[i] != (0 as u8) { out[o]=a[i]; o=o+1; i=i+1 } 107 i=0; while b[i] != (0 as u8) { out[o]=b[i]; o=o+1; i=i+1 } 108 i=0; while c[i] != (0 as u8) { out[o]=c[i]; o=o+1; i=i+1 } 109 i=0; while d[i] != (0 as u8) { out[o]=d[i]; o=o+1; i=i+1 } 110 out[o]=0 as u8; return o 111} 112// does /proc/<piddir>/fd contain a symlink to socket:[inode]? returns 1/0. 113func pon_pid_has_inode(piddir: *u8, inode: i64) -> i64 { 114 let fdpath: *u8 = sys_mmap(256); pon_join3("/proc/" as *u8, piddir, "/fd" as *u8, fdpath) 115 let fd: i64 = sys_openat_rd(fdpath) 116 if fd < 0 { return 0 } 117 let buf: *u8 = sys_mmap(PON_MAGIC_32768); let lnk: *u8 = sys_mmap(256); let full: *u8 = sys_mmap(320) 118 var found: i64 = 0 119 var rn: i64 = sys_getdents64(fd, buf, PON_MAGIC_32768) 120 while rn > 0 { 121 var off: i64 = 0 122 while off < rn { 123 let rec: *u8 = (buf as i64 + off) as *u8 124 let nm: *u8 = dirent_name(rec) 125 if pon_str_all_digits(nm) == 1 { 126 pon_join("/proc/" as *u8, piddir, "/fd/" as *u8, nm, full) 127 let ln: i64 = pon_readlink(full, lnk, 256) 128 if ln > 8 { 129 if lnk[0]==(115 as u8) { if lnk[7]==(91 as u8) { // 's' ... '[' = socket:[ 130 var k: i64 = 8; while k < ln { if lnk[k] == (93 as u8) { break } else { k = k+1 } } 131 if pon_dec(lnk, 8, k) == inode { found = 1 } 132 } } 133 } 134 } 135 let rl: i64 = dirent_reclen(rec); if rl <= 0 { off = rn } else { off = off + rl } 136 } 137 if found == 1 { rn = 0 } else { rn = sys_getdents64(fd, buf, PON_MAGIC_32768) } 138 } 139 sys_close(fd) 140 return found 141} 142// scan /proc/<pid> for the process owning `inode`; return the pid (0 = none). 143func pon_inode_to_pid(inode: i64) -> i64 { 144 let fd: i64 = sys_openat_rd("/proc" as *u8) 145 if fd < 0 { return 0 } 146 let buf: *u8 = sys_mmap(PON_MAGIC_32768) 147 var pid: i64 = 0 148 var rn: i64 = sys_getdents64(fd, buf, PON_MAGIC_32768) 149 while rn > 0 { 150 var off: i64 = 0 151 while off < rn { 152 let rec: *u8 = (buf as i64 + off) as *u8 153 let nm: *u8 = dirent_name(rec) 154 if pon_str_all_digits(nm) == 1 { 155 if pon_pid_has_inode(nm, inode) == 1 { pid = pon_dec(nm, 0, pon_strlen(nm)) } 156 } 157 let rl: i64 = dirent_reclen(rec); if rl <= 0 { off = rn } else { off = off + rl } 158 } 159 if pid > 0 { rn = 0 } else { rn = sys_getdents64(fd, buf, PON_MAGIC_32768) } 160 } 161 sys_close(fd) 162 return pid 163} 164// build "/proc/<pid>/<leaf>" into path 165func pon_pid_path(pid: i64, leaf: *u8, path: *u8) -> i64 { 166 let ps: *u8 = sys_mmap(24); var t: i64 = pid; var k: i64 = 0 167 let tmp: *u8 = sys_mmap(24) 168 if t == 0 { ps[0]=48 as u8; k=1 } else { while t > 0 { tmp[k] = (48 + (t%10)) as u8; t=t/10; k=k+1 } var j: i64 = 0; while j < k { ps[j] = tmp[k-1-j]; j=j+1 } } 169 ps[k] = 0 as u8 170 return pon_join3("/proc/" as *u8, ps, leaf, path) 171} 172// /proc/<pid>/comm -> out (the process name; kernel-truncated to 15 chars). returns length. 173func pon_pid_comm(pid: i64, out: *u8, cap: i64) -> i64 { 174 let path: *u8 = sys_mmap(64); pon_pid_path(pid, "/comm" as *u8, path) 175 let lb: *i64 = sys_mmap(8) as *i64 176 let d: *u8 = sys_read_file(path, lb) 177 if (d as i64) == 0 { out[0]=0 as u8; return 0 } 178 var n: i64 = lb[0]; if n > 0 { if d[n-1]==(10 as u8) { n = n-1 } } 179 var i: i64 = 0; while i < n { if i < cap-1 { out[i]=d[i] } i=i+1 } out[n]=0 as u8 180 return n 181} 182// /proc/<pid>/cmdline -> out (FULL command; NUL arg-separators rendered as spaces). The honest, un-truncated 183// identity (comm caps at 15 chars). returns length (0 = empty, e.g. a kernel thread -> caller falls back to comm). 184func pon_pid_cmdline(pid: i64, out: *u8, cap: i64) -> i64 { 185 let path: *u8 = sys_mmap(64); pon_pid_path(pid, "/cmdline" as *u8, path) 186 let lb: *i64 = sys_mmap(8) as *i64 187 let d: *u8 = sys_read_file(path, lb) 188 if (d as i64) == 0 { out[0]=0 as u8; return 0 } 189 var n: i64 = lb[0] 190 var i: i64 = 0 191 while i < n { if i < cap-1 { let c: i64 = d[i] & 0xff; if c == 0 { out[i] = 32 as u8 } else { out[i] = c as u8 } } i = i + 1 } 192 var w: i64 = n; if w > cap-1 { w = cap-1 } 193 while w > 0 { if out[w-1] == (32 as u8) { w = w - 1 } else { break } } // trim trailing space (the final NUL) 194 out[w] = 0 as u8 195 return w 196} 197// HIGH-LEVEL: which PID holds a LISTEN on `port`? 0 = nothing / free. Composes the chain in one call -- THE api 198// the supervisor snapshot + the mgmt plane + the twin call ("who really owns :8456?"). 199func pon_port_owner_pid(port: i64) -> i64 { 200 let inode: i64 = pon_listener_inode(port) 201 if inode <= 0 { return 0 } 202 return pon_inode_to_pid(inode) 203}