code wiki / (root) / nx_torrent_keep_gate.nx

nx_torrent_keep_gate.nx source

↩ module page · 104 lines · 5597 B

1// nx_torrent_keep_gate.nx -- SOVEREIGN proof that the local keeper (nx_torrent_keep) supervises the media 2// daemon: with the keeper running, KILLING the daemon results in it being RESPAWNED with a NEW live pid. 3// This is the measured, re-runnable S-class regression guard for the workstation-local death-respawn 4// supervision that brings the local daemon to NAS-parity (the NAS already has nx_hostctl). The whole 5// scenario runs in ONE process so it is not affected by cross-call WSL VM recycling. 6// 7// Run: build the keeper elf first (nx_sov_build_run.elf nx_torrent_keep --build-only), then run this gate. 8// license_tier: ORIGINAL layer: gate over the L7 keeper 9// module: nishi-core.torrent.keep_gate 10// depends: nishi-core.syscalls 11import "nx_syscalls.nx" 12import "nx_gate_verdict.nx" 13 14func g_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 15func g_putn(v: i64) -> i64 { 16 let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } 17 let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 18 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 19 var i: i64 = 0; while i < k { bb[i] = t[k-1-i]; i = i + 1 } 20 sys_write(1, bb, k); return 0 21} 22func g_report(label: *u8, ok: i64) -> i64 { 23 if ok == 1 { g_puts(" [PASS] " as *u8) } else { g_puts(" [FAIL] " as *u8) } 24 g_puts(label); g_puts("\n" as *u8); return 0 25} 26func g_read_pid(path: *u8) -> i64 { 27 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 - 1 } 28 let b: *u8 = sys_mmap(32); let n: i64 = sys_read(fd, b, 31); sys_close(fd) 29 if n <= 0 { return 0 - 1 } 30 var v: i64 = 0; var i: i64 = 0 31 while i < n { let c: i64 = b[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); i = i + 1 } else { i = n } } else { i = n } } 32 return v 33} 34func g_alive(pid: i64) -> i64 { if pid <= 0 { return 0 } if nx_kill(pid, 0) == 0 { return 1 } return 0 } 35// fork+exec the WOMB to (re)ensure the daemon up (idempotent); build/launch noise -> /dev/null; wait for it. 36func g_ensure_up(envp: *i64) -> i64 { 37 let womb: *u8 = "_offc/nx_sov_build_run.elf" as *u8 38 let pid: i64 = sys_fork() 39 if pid == 0 { 40 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 41 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 42 let av: *i64 = sys_mmap(8*4) as *i64; av[0] = womb as i64; av[1] = "nx_torrent_up" as *u8 as i64; av[2] = 0 43 sys_execve(womb, av, envp); sys_exit(127) 44 } 45 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 46 return (st[0] >> 8) & 0xff 47} 48 49func main() -> i64 { 50 let pidfile: *u8 = "/tmp/nx_torrentd.pid" as *u8 51 let keepelf: *u8 = "/tmp/nx_torrent_keep.sov.elf" as *u8 52 let envp: *i64 = sys_mmap(8*4) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 53 var pass: i64 = 0; var tot: i64 = 0 54 55 // 1. ensure the daemon is up; capture its pid 56 g_ensure_up(envp) 57 let pid0: i64 = g_read_pid(pidfile) 58 var t1: i64 = 0; if pid0 > 0 { if g_alive(pid0) == 1 { t1 = 1 } } 59 tot = tot + 1; if t1 == 1 { pass = pass + 1 } g_report("daemon initially UP (pre-keeper)" as *u8, t1) 60 g_puts(" pid0=" as *u8); g_putn(pid0); g_puts("\n" as *u8) 61 62 // 2. launch the keeper as a child of this gate (so we can stop it deterministically afterward) 63 let kpid: i64 = sys_fork() 64 if kpid == 0 { 65 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 66 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 67 let av: *i64 = sys_mmap(8*4) as *i64; av[0] = keepelf as i64; av[1] = 0 68 sys_execve(keepelf, av, envp); sys_exit(127) 69 } 70 sys_sleep_ms(3000) // let the keeper adopt the running daemon 71 var t2: i64 = 0; if g_alive(kpid) == 1 { t2 = 1 } 72 tot = tot + 1; if t2 == 1 { pass = pass + 1 } g_report("keeper launched + running" as *u8, t2) 73 74 // 3. KILL the daemon (simulate a crash). The keeper polls every 5s, so at +1.5s it is still down. 75 if pid0 > 0 { nx_kill(pid0, 9) } 76 sys_sleep_ms(1500) 77 var t3: i64 = 0; if g_alive(pid0) == 0 { t3 = 1 } 78 tot = tot + 1; if t3 == 1 { pass = pass + 1 } g_report("daemon killed (pid0 gone)" as *u8, t3) 79 80 // 4. poll up to 26s for the keeper's respawn: a NEW live pid in the pidfile 81 var newpid: i64 = 0 - 1; var waited: i64 = 0 82 while waited < 26000 { 83 sys_sleep_ms(2000); waited = waited + 2000 84 let p: i64 = g_read_pid(pidfile) 85 if p > 0 { if p != pid0 { if g_alive(p) == 1 { newpid = p; waited = 26000 } } } 86 } 87 var t4: i64 = 0; if newpid > 0 { t4 = 1 } 88 tot = tot + 1; if t4 == 1 { pass = pass + 1 } g_report("keeper RESPAWNED the daemon (new live pid != pid0)" as *u8, t4) 89 g_puts(" newpid=" as *u8); g_putn(newpid); g_puts("\n" as *u8) 90 91 // 5. cleanup: stop the gate's keeper child (the respawned daemon stays up -- it is the live one) 92 if kpid > 0 { nx_kill(kpid, 9) } 93 94 g_puts("TORRENT-KEEP-GATE authored=organ pass=" as *u8); g_putn(pass); g_puts("/" as *u8); g_putn(tot) 95 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 96 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 97 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 98 let ctr__dry: *i64 = gv_ctr() 99 ctr__dry[0] = pass 100 ctr__dry[1] = tot 101 let rc__dry: i64 = gv_verdict("TORRENT-KEEP-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 102 sys_exit(rc__dry) 103 return rc__dry 104}