code wiki / _hdl_build / nx_cron_reconcile.nx

nx_cron_reconcile.nx source

↩ module page · 227 lines · 10129 B

1// nx_cron_reconcile.nx -- SOVEREIGN crontab reconciler (operator directive 2026-07-16: "make the 2// capabilities native to the nishi ecosystem, don't make me do it"). The nishi-managed block of a crontab 3// becomes DATA: cron.reg declares exactly the nishi rows that must exist; this organ rewrites the crontab 4// so its nishi rows (any line containing /nishihost/) equal EXACTLY the declared set, leaving every 5// non-nishi row BYTE-EXACT. Dead rows (retired reconcilers) vanish by construction; new rows = a registry 6// line. Runs as root with ZERO operator action by piggybacking on the existing root cron row 7// (nx_edge443_reconcile.sh -- the same proven pattern as the vsz_watchdog on the rail). 8// 9// NEVER-BRICK BY CONSTRUCTION (this writes a root-owned BOOT FILE): 10// R1 only lines containing /nishihost/ are ever touched -- the filter IS the blast-radius bound 11// R2 refuse (exit 3, no write) if the registry is missing/empty -- an absent SSOT must never wipe rows 12// R3 refuse (exit 5, no write) if ANY declared row lacks /nishihost/ -- the organ cannot inject 13// non-nishi rows even if the registry is corrupted/hostile 14// R4 refuse (exit 4, no write) if the crontab is unreadable or EMPTY -- a boot file is never empty 15// R5 idempotent: byte-identical result -> NO-CHANGE, zero writes (safe at any frequency) 16// R6 atomic: write <crontab>.nxnew then renameat over -- no torn boot file ever exists on disk 17// After a real change, SIGHUP every `crond` (reload -- standard, non-destructive; if crond ignores it, 18// stale in-memory entries keep harmlessly firing removed paths until its next restart = no regression). 19// 20// usage: nx_cron_reconcile <crontab-path> <reg-path> (gate runs it on COPIES; prod = /etc/crontab) 21// exit: 0 ok (CHANGED or NO-CHANGE) | 2 usage | 3 reg-refuse | 4 crontab-refuse | 5 reg-taint | 6 write-fail 22// license_tier: ORIGINAL module: nishi-core.ops.cron_reconcile 23import "nx_syscalls.nx" 24const CR_MAGIC_65536: i64 = 65536 25 26const CR_CTCAP: i64 = 65536 27const CR_REGCAP: i64 = 16384 28const CR_MARK: *u8 = "/nishihost/" as *u8 29 30func cr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 31func cr_p(s: *u8) -> i64 { sys_write(1, s, cr_slen(s)); return 0 } 32func cr_decw(v: i64, out: *u8) -> i64 { 33 var o: i64 = 0 34 var m: i64 = v 35 if m < 0 { out[0] = 45 as u8; o = 1; m = 0 - m } 36 if m == 0 { out[o] = 48 as u8; return o + 1 } 37 var nd: i64 = 1 38 var mm: i64 = m 39 while mm >= 10 { nd = nd + 1; mm = mm / 10 } 40 var i: i64 = nd - 1 41 while m > 0 { out[o + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 42 return o + nd 43} 44func cr_pn(v: i64) -> i64 { let b: *u8 = sys_mmap(28); let n: i64 = cr_decw(v, b); sys_write(1, b, n); sys_munmap(b, 28); return 0 } 45func cr_contains(hay: *u8, hlen: i64, needle: *u8) -> i64 { 46 let nlen: i64 = cr_slen(needle) 47 if hlen < nlen { return 0 } 48 var s: i64 = 0 49 let last: i64 = hlen - nlen 50 while s <= last { 51 var m: i64 = 0 52 var eq: i64 = 1 53 while m < nlen { if hay[s + m] != needle[m] { eq = 0; m = nlen } m = m + 1 } 54 if eq == 1 { return 1 } 55 s = s + 1 56 } 57 return 0 58} 59func cr_line_end(buf: *u8, pos: i64, end: i64) -> i64 { 60 var p: i64 = pos 61 while p < end { if (buf[p] as i64) == 10 { return p } p = p + 1 } 62 return end 63} 64func cr_read(path: *u8, buf: *u8, cap: i64) -> i64 { 65 let fd: i64 = sys_openat_rd(path) 66 if fd < 0 { return 0 - 1 } 67 let n: i64 = sys_read(fd, buf, cap - 1) 68 sys_close(fd) 69 return n 70} 71 72func main(argc: i64, argv: *i64) -> i64 { 73 if argc < 3 { cr_p("usage: nx_cron_reconcile <crontab-path> <reg-path>\n" as *u8); return 2 } 74 let ctpath: *u8 = argv[1] as *u8 75 let regpath: *u8 = argv[2] as *u8 76 let ct: *u8 = sys_mmap(CR_CTCAP) 77 let reg: *u8 = sys_mmap(CR_REGCAP) 78 let out: *u8 = sys_mmap(CR_CTCAP + CR_REGCAP) 79 let np: *u8 = sys_mmap(512) 80 81 // R4: crontab must exist and be non-empty 82 let cn: i64 = cr_read(ctpath, ct, CR_CTCAP) 83 if cn <= 0 { cr_p("REFUSED: crontab unreadable/empty (a boot file is never empty) -- no write\n" as *u8); return 4 } 84 // R2: registry must exist with >=1 declared row; R3: every row must carry the nishi mark 85 let rn: i64 = cr_read(regpath, reg, CR_REGCAP) 86 if rn <= 0 { cr_p("REFUSED: cron.reg missing/empty -- an absent SSOT never wipes rows\n" as *u8); return 3 } 87 var decl: i64 = 0 88 var pos: i64 = 0 89 while pos < rn { 90 let le: i64 = cr_line_end(reg, pos, rn) 91 if le > pos { if (reg[pos] as i64) != 35 { if (reg[pos] as i64) != 13 { 92 if cr_contains((((reg as i64) + pos)) as *u8, le - pos, CR_MARK) == 0 { 93 cr_p("REFUSED: declared row lacks /nishihost/ (registry taint -- this organ cannot manage non-nishi rows)\n" as *u8) 94 return 5 95 } 96 decl = decl + 1 97 } } } 98 pos = le + 1 99 } 100 if decl == 0 { cr_p("REFUSED: cron.reg has no declared rows -- no write\n" as *u8); return 3 } 101 102 // build: non-nishi crontab lines byte-exact, then the declared block 103 var w: i64 = 0 104 var removed: i64 = 0 105 pos = 0 106 while pos < cn { 107 let le2: i64 = cr_line_end(ct, pos, cn) 108 if cr_contains((((ct as i64) + pos)) as *u8, le2 - pos, CR_MARK) == 1 { removed = removed + 1 } 109 else { 110 var k: i64 = pos 111 while k < le2 { out[w] = ct[k]; w = w + 1; k = k + 1 } 112 out[w] = 10 as u8 113 w = w + 1 114 } 115 pos = le2 + 1 116 } 117 pos = 0 118 while pos < rn { 119 let le3: i64 = cr_line_end(reg, pos, rn) 120 if le3 > pos { if (reg[pos] as i64) != 35 { if (reg[pos] as i64) != 13 { 121 var k2: i64 = pos 122 while k2 < le3 { out[w] = reg[k2]; w = w + 1; k2 = k2 + 1 } 123 out[w] = 10 as u8 124 w = w + 1 125 } } } 126 pos = le3 + 1 127 } 128 129 // R5: idempotence -- byte-identical means zero writes 130 var same: i64 = 0 131 if w == cn { 132 same = 1 133 var ci: i64 = 0 134 while ci < cn { if out[ci] != ct[ci] { same = 0; ci = cn } ci = ci + 1 } 135 } 136 if same == 1 { 137 cr_p("NO-CHANGE (crontab already matches cron.reg; declared=" as *u8) 138 cr_pn(decl) 139 cr_p(")\n" as *u8) 140 return 0 141 } 142 143 // R6: atomic write -- <path>.nxnew then rename over 144 var npw: i64 = 0 145 var pi: i64 = 0 146 while ctpath[pi] != (0 as u8) { np[npw] = ctpath[pi]; npw = npw + 1; pi = pi + 1 } 147 let sfx: *u8 = ".nxnew" as *u8 148 var si: i64 = 0 149 while sfx[si] != (0 as u8) { np[npw] = sfx[si]; npw = npw + 1; si = si + 1 } 150 np[npw] = 0 as u8 151 let wfd: i64 = sys_openat_wr(np, 420) 152 if wfd < 0 { cr_p("WRITE-FAIL: cannot create .nxnew (permission?) -- crontab untouched\n" as *u8); return 6 } 153 var off: i64 = 0 154 while off < w { 155 let wr: i64 = sys_write(wfd, (((out as i64) + off)) as *u8, w - off) 156 if wr <= 0 { sys_close(wfd); cr_p("WRITE-FAIL: short write on .nxnew -- crontab untouched\n" as *u8); return 6 } 157 off = off + wr 158 } 159 sys_close(wfd) 160 if sys_renameat(np, ctpath) != 0 { cr_p("WRITE-FAIL: rename over crontab failed -- crontab untouched\n" as *u8); return 6 } 161 cr_p("CHANGED: nishi-rows removed=" as *u8) 162 cr_pn(removed) 163 cr_p(" declared=" as *u8) 164 cr_pn(decl) 165 cr_p(" bytes " as *u8) 166 cr_pn(cn) 167 cr_p("->" as *u8) 168 cr_pn(w) 169 cr_p("\n" as *u8) 170 171 // SIGHUP crond so the in-memory schedule reloads (safe standard reload; no-op if none found) 172 let dfd: i64 = sys_openat_rd("/proc\x00" as *u8) 173 if dfd >= 0 { 174 let dbuf: *u8 = sys_mmap(CR_MAGIC_65536) 175 let cpath: *u8 = sys_mmap(64) 176 let cbuf: *u8 = sys_mmap(32) 177 var hup: i64 = 0 178 var reading: i64 = 1 179 while reading == 1 { 180 let dn: i64 = sys_getdents64(dfd, dbuf, CR_MAGIC_65536) 181 if dn <= 0 { reading = 0 } 182 else { 183 var doff: i64 = 0 184 while doff < dn { 185 let recp: *u8 = (((dbuf as i64) + doff)) as *u8 186 let reclen: i64 = dirent_reclen(recp) 187 let nm: *u8 = dirent_name(recp) 188 if nm[0] >= (48 as u8) { if nm[0] <= (57 as u8) { 189 var qi: i64 = 0 190 let pre: *u8 = "/proc/" as *u8 191 var ppi: i64 = 0 192 while pre[ppi] != (0 as u8) { cpath[ppi] = pre[ppi]; ppi = ppi + 1 } 193 while nm[qi] != (0 as u8) { cpath[ppi] = nm[qi]; ppi = ppi + 1; qi = qi + 1 } 194 let suf: *u8 = "/comm" as *u8 195 var ssi: i64 = 0 196 while suf[ssi] != (0 as u8) { cpath[ppi] = suf[ssi]; ppi = ppi + 1; ssi = ssi + 1 } 197 cpath[ppi] = 0 as u8 198 let cfd: i64 = sys_openat_rd(cpath) 199 if cfd >= 0 { 200 let cl: i64 = sys_read(cfd, cbuf, 31) 201 sys_close(cfd) 202 // comm == "crond\n" 203 if cl >= 5 { if cbuf[0]==(99 as u8) { if cbuf[1]==(114 as u8) { if cbuf[2]==(111 as u8) { if cbuf[3]==(110 as u8) { if cbuf[4]==(100 as u8) { 204 var tail_ok: i64 = 0 205 if cl == 5 { tail_ok = 1 } 206 if cl == 6 { if (cbuf[5] as i64) == 10 { tail_ok = 1 } } 207 if tail_ok == 1 { 208 var pid: i64 = 0 209 var di: i64 = 0 210 while nm[di] != (0 as u8) { pid = pid * 10 + ((nm[di] as i64) - 48); di = di + 1 } 211 nx_kill(pid, 1) 212 hup = hup + 1 213 } 214 } } } } } } 215 } 216 } } 217 doff = doff + reclen 218 } 219 } 220 } 221 sys_close(dfd) 222 cr_p("crond HUP'd: " as *u8) 223 cr_pn(hup) 224 cr_p("\n" as *u8) 225 } 226 return 0 227}