code wiki / (root) / nx_poll.nx

nx_poll.nx source

↩ module page · 134 lines · 4876 B

1// nx_poll.nx -- poll(2) and ppoll(2) wrappers. 2// 3// Used by: 4// - Network servers waiting for I/O on N sockets 5// - Build pipelines watching child stdout + stderr in parallel 6// - Async runtimes that need readiness notification 7// 8// Linux RV64 doesn't expose poll() directly -- we use ppoll 9// (syscall 73) with a NULL timespec for the equivalent of poll's 10// blocking behaviour, or a real timespec for timeout. 11// 12// struct pollfd: 13// int fd i32 14// short events i16 15// short revents i16 16// 17// Total = 8 bytes per entry. Caller allocates an array, fills 18// fd + events on each, and reads revents after the syscall. 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "syscalls.nx" 27const NX_MAGIC_1000000: i64 = 1000000 28 29// ppoll syscall number is per-ISA; gate via the same @ifdef pattern 30// nx_syscalls.nx uses for SYS_READ/WRITE/MMAP/etc. Without this, 31// --target x86_64 compiled the RV64 number (73) as a literal, which 32// is setrlimit on x86_64 -- nx_poll() returned EINVAL, daemons 33// silently dropped to keep=0 and exited immediately after their 34// listen+banner. Caught by nx_signaling stone S2 redeploy 2026-05-20. 35@ifdef TARGET_X86_64 36const NX_SYS_PPOLL: i64 = 271 37@endif 38@ifndef TARGET_X86_64 39const NX_SYS_PPOLL: i64 = 73 40@endif 41 42// poll event mask bits. 43const NX_POLLIN: i64 = 0x0001 44const NX_POLLPRI: i64 = 0x0002 45const NX_POLLOUT: i64 = 0x0004 46const NX_POLLERR: i64 = 0x0008 47const NX_POLLHUP: i64 = 0x0010 48const NX_POLLNVAL: i64 = 0x0020 49const NX_POLLRDNORM: i64 = 0x0040 50const NX_POLLRDBAND: i64 = 0x0080 51const NX_POLLWRNORM: i64 = 0x0100 52const NX_POLLWRBAND: i64 = 0x0200 53 54const NX_POLLFD_BYTES: i64 = 8 55 56// Set fd + events on entry `i` of an array of pollfds. 57func nx_pollfd_set(pfds: *u8, i: i64, fd: i64, events: i64) -> i64 { 58 let p: i64 = (pfds as i64) + i * NX_POLLFD_BYTES 59 let fdp: *i32 = p as *i32 60 fdp[0] = fd 61 let ev_p: *u8 = (p + 4) as *u8 62 ev_p[0] = events & 0xFF 63 ev_p[1] = (events >> 8) & 0xFF 64 let rev_p: *u8 = (p + 6) as *u8 65 rev_p[0] = 0 66 rev_p[1] = 0 67 return 0 68} 69 70// Read revents on entry `i`. 71func nx_pollfd_revents(pfds: *u8, i: i64) -> i64 { 72 let p: i64 = (pfds as i64) + i * NX_POLLFD_BYTES 73 let rev_p: *u8 = (p + 6) as *u8 74 return (rev_p[0] as i64) | ((rev_p[1] as i64) << 8) 75} 76 77// ppoll(pfds, n, timeout_ts, sigmask, sigmask_size). Pass timeout_ts=0 78// for indefinite blocking, or a pointer to {tv_sec: i64, tv_nsec: i64} 79// for a relative timeout. Pass sigmask=0 for no signal-mask change. 80func nx_ppoll(pfds: *u8, n: i64, timeout_ts: i64, sigmask: i64) -> i64 { 81 return __syscall(NX_SYS_PPOLL, pfds as i64, n, timeout_ts, sigmask, 8, 0) 82} 83 84// poll(pfds, n, timeout_ms): convenience that converts ms to ts. 85// timeout_ms < 0 = indefinite, 0 = poll-and-return. 86func nx_poll(pfds: *u8, n: i64, timeout_ms: i64) -> i64 { 87 if timeout_ms < 0 { 88 return nx_ppoll(pfds, n, 0, 0) 89 } 90 let ts_raw: *u8 = sys_mmap(16) 91 let ts: *i64 = ts_raw as *i64 92 ts[0] = timeout_ms / 1000 93 ts[1] = (timeout_ms - ts[0] * 1000) * NX_MAGIC_1000000 94 return nx_ppoll(pfds, n, ts_raw as i64, 0) 95} 96 97// ---- self-test --------------------------------------------------- 98 99func main() -> i64 { 100 // Constants. 101 if NX_POLLIN != 1 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 102 if NX_POLLOUT != 4 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 103 if NX_POLLERR != 8 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 104 if NX_POLLHUP != 0x10 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 105 106 // Round-trip set + read. 107 let arr: *u8 = sys_mmap(NX_POLLFD_BYTES * 4) 108 nx_pollfd_set(arr, 0, 5, NX_POLLIN | NX_POLLOUT) 109 nx_pollfd_set(arr, 1, 7, NX_POLLERR) 110 111 let fd0_p: *i32 = arr as *i32 112 if fd0_p[0] != 5 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 113 114 let ev0_p: *u8 = (((arr as i64) + 4) as *u8) 115 let ev0: i64 = (ev0_p[0] as i64) | ((ev0_p[1] as i64) << 8) 116 if ev0 != (NX_POLLIN | NX_POLLOUT) { return __syscall(93, 6, 0, 0, 0, 0, 0) } 117 118 let fd1_p: *i32 = (((arr as i64) + NX_POLLFD_BYTES) as *i32) 119 if fd1_p[0] != 7 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 120 121 // revents after set should be 0. 122 if nx_pollfd_revents(arr, 0) != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 123 if nx_pollfd_revents(arr, 1) != 0 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 124 125 // Real poll on stderr (always writable) with 0 timeout. 126 let real: *u8 = sys_mmap(NX_POLLFD_BYTES) 127 nx_pollfd_set(real, 0, 2, NX_POLLOUT) 128 let r: i64 = nx_poll(real, 1, 0) 129 if r < 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 130 // Don't check exact revents -- some sandboxes do weird things 131 // with poll on inherited fds. 132 133 return 0 134}