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}