nx_syscalls.nx source
↩ module page · 869 lines · 41870 B
1// syscalls.nx -- thin __syscall wrappers used across modules.
2//
3// Sovereign path: no libc. Every memory allocation, file op, and
4// clock read in the rest of the runtime routes through one of these
5// helpers. Numbers match Linux RV64; NishiOS uses the same set.
6//
7// Extracted from runtime.nx and ir.nx's copy-pasted helpers so the
8// module-import build doesn't produce duplicate symbols.
9
10// Tier aliases (nx_size / nx_idx / nx_fd / ...) ride along with the
11// syscall shelf: 141 runtime files use `as nx_size` etc. and only
12// compiled historically because the old parser silently void-cast
13// unknown type names (T#nx-int-alias-size-0 closed that hole LOUDLY,
14// which exposed the missing import). nx_tier.nx is pure type
15// aliases (0 funcs); prepass_register_aliases skips duplicates, so
16// modules that also import it directly stay fine.
17import "nx_tier.nx"
18const SYS_MAGIC_1024: i64 = 1024
19const SYS_MAGIC_1000000: i64 = 1000000
20const SYS_MAGIC_4294967296: i64 = 4294967296
21const SYS_MAGIC_100000: i64 = 100000
22
23// ---- syscall numbers (per-target) ----
24//
25// Cross-target via the macro processor (cardinal landed 2026-05-20:
26// feedback-hardware-agnostic-is-robustness -- the substrate must
27// compile + run on every silicon we point it at). Default path
28// (TARGET_X86_64 not defined) carries Linux RV64 numbers used by
29// qemu-RV64 + NishiOS. When nxc2 is invoked with --target x86_64
30// main.c pre-defines @macro TARGET_X86_64 1 so this file resolves
31// to x86_64 Linux ABI numbers.
32//
33// nx_syscalls_x86_64.nx remains the dedicated x86_64-only mirror
34// for files that want explicit single-target imports (e.g., bench
35// smokes built only for x86_64). This block makes nx_syscalls.nx
36// itself dual-target so substrate primitives compile portably.
37
38@ifdef TARGET_X86_64
39const SYS_READ: i64 = 0
40const SYS_WRITE: i64 = 1
41const SYS_CLOSE: i64 = 3
42const SYS_LSEEK: i64 = 8
43const SYS_OPENAT: i64 = 257
44const SYS_EXIT: i64 = 60
45const SYS_MMAP: i64 = 9
46const SYS_CLOCK_GETTIME: i64 = 228
47const SYS_IOCTL: i64 = 16
48const SYS_CLOCK_NANOSLEEP: i64 = 230
49// Namespace/container family, x86 branch (debt 1785528831). Moved here from
50// nx_syscalls_x86_64.nx so ONE module owns the wrapper set -- a TU reaching both
51// modules used to hold every wrapper TWICE, resolved silently by definition ORDER.
52const SYS_CHROOT: i64 = 161
53const SYS_MOUNT: i64 = 165
54const SYS_UNSHARE: i64 = 272
55const SYS_GETUID: i64 = 102
56const SYS_GETGID: i64 = 104
57const SYS_POLL: i64 = 7
58@endif
59
60@ifndef TARGET_X86_64
61const SYS_READ: i64 = 63
62const SYS_WRITE: i64 = 64
63const SYS_CLOSE: i64 = 57
64const SYS_LSEEK: i64 = 62
65const SYS_OPENAT: i64 = 56
66const SYS_EXIT: i64 = 93
67const SYS_MMAP: i64 = 222
68const SYS_CLOCK_GETTIME: i64 = 113
69const SYS_IOCTL: i64 = 29
70const SYS_CLOCK_NANOSLEEP: i64 = 115
71// Namespace/container family, RV64 branch (debt 1785528831). This is the branch actually
72// KEPT (TARGET_X86_64 is hard-pinned undefined), so these are the numbers the x86 backend
73// translates at emit: 51->161 chroot, 40->165 mount, 97->272 unshare, 174->102 getuid,
74// 176->104 getgid. The 40 and 51 rows were added to x86ctx_rv64_to_x86_64_syscall and
75// shipped FIRST -- without them both would pass through to the WRONG x86 syscall
76// (sendfile / getsockname), silently, because that translator's default is `return num`.
77const SYS_CHROOT: i64 = 51
78const SYS_MOUNT: i64 = 40
79const SYS_UNSHARE: i64 = 97
80const SYS_GETUID: i64 = 174
81const SYS_GETGID: i64 = 176
82const SYS_POLL: i64 = 73
83@endif
84
85func sys_ioctl(fd: i64, request: i64, arg: i64) -> i64 {
86 return __syscall(SYS_IOCTL, fd, request, arg, 0, 0, 0)
87}
88
89// poll(2): wait for events on fds. fds points to an array of `nfds`
90// struct pollfd { i32 fd; i16 events; i16 revents } (8 bytes each).
91// timeout_ms < 0 = block forever, 0 = return immediately. Returns the
92// count of ready fds (>0), 0 on timeout, or -errno. Used by the
93// substrate's own network diagnostics (bounded non-blocking connect)
94// instead of reaching for external tools. (rv64 const = ppoll; this
95// wrapper only runs on the x86_64 target.)
96func sys_poll(fds: *u8, nfds: i64, timeout_ms: i64) -> i64 {
97 return __syscall(SYS_POLL, fds, nfds, timeout_ms, 0, 0, 0)
98}
99
100// ---- core wrappers ----
101
102func sys_write(fd: i64, buf: *u8, count: i64) -> i64 {
103 return __syscall(SYS_WRITE, fd, buf, count, 0, 0, 0)
104}
105
106func sys_read(fd: i64, buf: *u8, count: i64) -> i64 {
107 return __syscall(SYS_READ, fd, buf, count, 0, 0, 0)
108}
109
110func sys_close(fd: i64) -> i64 {
111 return __syscall(SYS_CLOSE, fd, 0, 0, 0, 0, 0)
112}
113
114// chdir. The compiler only rv64->x86 translates CONSTANT syscall numbers (x86ctx_emit_syscall:
115// VK_CONST_INT); chdir is absent from that table, so a constant 49 falls through to x86_64 bind and a
116// constant 80 is mapped to fstat -- BOTH gave EBADF (PROBE-PROVEN by test_chdir). The documented escape
117// (nx_x86_64_ctx.nx:1004 "Runtime-computed syscall number -- load as-is") is to make op0 RUNTIME: a memory
118// load can't be folded to VK_CONST_INT, so the raw x86_64 number 80 passes through untranslated = real
119// chdir. Used by the supervisor to set a spawned daemon's CWD before execve. 0 on success, -errno on fail.
120func sys_chdir(path: *u8) -> i64 {
121 let nbox: *i64 = sys_mmap(16) as *i64
122 nbox[0] = 80 // x86_64 chdir, forced runtime so the rv64->x86 xlate is skipped
123 return __syscall(nbox[0], path as i64, 0, 0, 0, 0, 0)
124}
125
126// ⚠AT_FDCWD MOVED UP 2026-07-20 -- IT WAS A LIVE MISCOMPILE. This const was declared ~60 lines BELOW
127// (in the openat block) while sys_unlinkat and sys_fchmodat immediately below REFERENCE it. A module
128// const referenced ABOVE its declaration does not resolve, and nx_cc silently substituted CONSTANT 0
129// -- so both wrappers passed dirfd=0 (stdin) instead of -100. Absolute paths survive that (openat
130// ignores dirfd when the path is absolute), RELATIVE paths do not, which is exactly why unlinkat was
131// long recorded as flaky and "passing only by luck". Surfaced by the new unknown-identifier
132// diagnostic, which turned a silent 0 into a compile error. LAW (already banked, now enforced):
133// module-wide consts/statics go ABOVE every possible reader.
134const AT_FDCWD: i64 = -100
135
136// unlinkat(AT_FDCWD, path, 0) -- delete a file. x86_64 263 is a PROVEN pass-through (not an rv64 key),
137// but this is THE canonical home: 5+ organs hand-rolled `__syscall(263,...)` before this landed (DRY,
138// 2026-07-20). 0 on success, -errno on fail.
139func sys_unlinkat(path: *u8) -> i64 {
140 return __syscall(263, AT_FDCWD, path as i64, 0, 0, 0, 0)
141}
142
143// fchmodat(AT_FDCWD, path, mode) -- chmod by path. ⚠a CONSTANT 268 gets rv64->x86 TRANSLATED to the
144// wrong syscall (silent no-op chmod -- cost a vacuous-permission-test debug cycle, 2026-07-20), so the
145// number is forced RUNTIME via the sys_chdir nbox pattern. 0 on success, -errno on fail.
146func sys_fchmodat(path: *u8, mode: i64) -> i64 {
147 let nbox: *i64 = sys_mmap(16) as *i64
148 nbox[0] = 268 // x86_64 fchmodat, forced runtime so the xlate is skipped
149 return __syscall(nbox[0], AT_FDCWD, path as i64, mode, 0, 0, 0)
150}
151
152// exit_group(2) -- terminate ALL tasks in the thread group. Raw x86_64 231
153// (231 is NOT an rv64 key in the compiler's swap table, so it passes through
154// untranslated -- the munmap-11 precedent). THE explicit program-exit call
155// once a process holds live nx_thread_pool workers: CLONE_VM tasks are
156// separate PIDs, so plain sys_exit (93 -> x86 60, single task) leaves them
157// running, holding stdout open and wedging any pipeline that waits for EOF
158// (found 2026-07-07: the shared-pool matmul dispatcher hung the build lane
159// this way). Return-from-main already exit_groups via the _start trampoline;
160// use THIS for explicit early program exit. Per-THREAD exit stays sys_exit
161// (see nx_thread_exit).
162func sys_exit_group(code: i64) -> i64 {
163 return __syscall(231, code, 0, 0, 0, 0, 0)
164}
165
166// setpriority(PRIO_PROCESS=0, who=0 -> SELF, prio) -- x86_64 syscall 141.
167// Lower priority = larger nice value; 19 is the maximum yield.
168// WHY A WRAPPER AND NOT AN OPERATOR STEP (measured 2026-07-30): a bulk media
169// migration walk saturated the NAS; every forked organ queued behind its I/O so
170// EVERY agent MCP call 503'd for minutes -- the control plane went blind while a
171// background job did exactly what it was told. `renice 19` on the running pid
172// restored interactive service at once.
173// LAW: a long-running BULK job must yield to the interactive control plane BY
174// CONSTRUCTION at its own launch, not when an operator notices. Bind it to the
175// one act every bulk job performs (its startup) and nothing has to remember it.
176// WARN: `ionice` does NOT exist on the Synology busybox, so the I/O-class lever
177// is unavailable; CPU nice sufficed because the walk is SHA-256-bound over
178// cached reads (state R, not D, once niced).
179func sys_setpriority(prio: i64) -> i64 {
180 return __syscall(141, 0, 0, prio, 0, 0, 0)
181}
182
183// ADDITIVE TWIN 2026-08-04 (nx_resgov): re-nice ANOTHER process by pid. The incumbent above pins
184// who=0 = "me", so it cannot deprioritise a runaway -- and a governor that can only slow ITSELF has
185// no graceful rung between "observe" and "kill". PRIO_PROCESS=0, who=pid. Existing callers untouched
186// (rule 19: add the new entry point, never re-shape the one in service).
187func sys_setpriority_of(pid: i64, prio: i64) -> i64 {
188 return __syscall(141, 0, pid, prio, 0, 0, 0)
189}
190
191// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's
192// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat
193// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each;
194// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT
195// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive.
196func sys_munmap(addr: *u8, len: i64) -> i64 {
197 return __syscall(11, addr as i64, len, 0, 0, 0, 0)
198}
199
200// Seek within a file. whence: 0=SEEK_SET, 1=SEEK_CUR, 2=SEEK_END.
201// Returns new file offset on success, -errno on failure.
202func sys_lseek(fd: i64, offset: i64, whence: i64) -> i64 {
203 return __syscall(SYS_LSEEK, fd, offset, whence, 0, 0, 0)
204}
205
206func sys_exit(code: i64) -> i64 {
207 return __syscall(SYS_EXIT, code, 0, 0, 0, 0, 0)
208}
209
210// mmap anonymous R/W memory; returns raw bytes. Fixed flags:
211// PROT_READ|PROT_WRITE = 3, MAP_PRIVATE|MAP_ANONYMOUS = 0x22, fd=-1.
212func sys_mmap(size: i64) -> *u8 {
213 let r: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
214 return r as *u8
215}
216
217// mmap anonymous SHARED R/W memory -- ONE region that survives fork() so all
218// children see each other's writes (MAP_SHARED|MAP_ANONYMOUS = 0x21). Allocate
219// in the PARENT before fork. Foundation for the fork-per-connection video relay
220// (peers in separate children share the per-room frame table).
221func sys_mmap_shared(size: i64) -> *u8 {
222 let r: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x21, -1, 0)
223 return r as *u8
224}
225
226// openat flavors used by the compiler driver. AT_FDCWD = -100 (declared ABOVE, next to its first
227// reader -- see the miscompile note there; do NOT move it back down).
228// O_RDONLY = 0; O_CREAT|O_WRONLY|O_TRUNC = 0x241 on Linux RV64.
229const O_RDONLY: i64 = 0
230const O_WRONLY_CT: i64 = 0x241 // O_CREAT | O_WRONLY | O_TRUNC
231const O_WRONLY_CA: i64 = 0x441 // O_CREAT | O_WRONLY | O_APPEND
232
233func sys_openat_rd(path: *u8) -> i64 {
234 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY, 0, 0, 0)
235}
236
237// O_RDWR|O_CREAT (NO truncate) -- for offset-addressed persistent files like the metrics ring TSDB
238// (create if missing, then lseek+read/write records in place, never truncating existing history).
239const O_RDWR_CREATE: i64 = 0x42
240func sys_openat_rdwr(path: *u8, mode: i64) -> i64 {
241 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDWR_CREATE, mode, 0, 0)
242}
243
244func sys_openat_wr(path: *u8, mode: i64) -> i64 {
245 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CT, mode, 0, 0)
246}
247
248// Open path for append (create if missing). Used by append-only
249// journals such as .race_telemetry.tsv. RV64 syscall numbers; the
250// x86_64 mirror lives in nx_syscalls_x86_64.nx.
251func sys_openat_append(path: *u8, mode: i64) -> i64 {
252 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA, mode, 0, 0)
253}
254
255// symlinkat(target, AT_FDCWD, linkpath) -- raw x86_64 266 forced RUNTIME (the chdir escape, same as
256// readlinkat below). THE atomic-repoint primitive for release management: create releases/current.new ->
257// sys_renameat over releases/current = an atomic symlink swap (golive/rollback are instant + crash-safe).
258// 0 on success, -errno (notably -EEXIST=-17 if linkpath exists -- create the .new name, then rename).
259func sys_symlinkat(target: *u8, linkpath: *u8) -> i64 {
260 let nbox: *i64 = sys_mmap(16) as *i64
261 nbox[0] = 266
262 let r: i64 = __syscall(nbox[0], target as i64, AT_FDCWD, linkpath as i64, 0, 0, 0)
263 sys_munmap(nbox as *u8, 16)
264 return r
265}
266
267// readlinkat(AT_FDCWD, path, buf, cap) -- raw x86_64 267 forced RUNTIME (the chdir escape: keep the
268// number out of the rv64->x86 constant-translate path). Returns link length (NO NUL appended), -errno
269// on fail. nbox is munmap'd before return: the daemon supervisor calls this hundreds of times PER CYCLE
270// (exe-identity sweeps), and a leaked page per call is exactly the VSZ-balloon class that broke fork.
271func sys_readlinkat(path: *u8, buf: *u8, cap: i64) -> i64 {
272 let nbox: *i64 = sys_mmap(16) as *i64
273 nbox[0] = 267
274 let r: i64 = __syscall(nbox[0], AT_FDCWD, path as i64, buf as i64, cap, 0, 0)
275 sys_munmap(nbox as *u8, 16)
276 return r
277}
278
279// Atomically replace newpath with oldpath (rename(2) on one filesystem: a concurrent reader sees the
280// whole old file or the whole new file, never a torn read). The S-class content-publish primitive:
281// write the new page to a temp file, then sys_renameat(tmp, live) -> hot-swap, NO rm+ln race.
282// renameat2: rv64=276, x86_64=316, flags=0. The known-good compiler translates most rv64 syscall
283// numbers to the x86_64 target but its table MISSES 276 -- verified 2026-06-14 via nx_rename_probe:
284// raw 276 -> -EINVAL (lands on x86_64 `tee`), raw 316 -> renames OK. That silently broke every
285// cst_write_atomic publish (page.html.new written, never swapped in). Try the x86_64 number first
286// (works on every x86_64 build incl. known-good); fall back to the rv64 number for native-rv64 or
287// translating compilers that do map it. flags=0 so renameat2 == renameat semantics.
288func sys_renameat(oldpath: *u8, newpath: *u8) -> i64 {
289 let r: i64 = __syscall(316, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0)
290 if r == 0 { return 0 }
291 return __syscall(276, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0)
292}
293
294// fsync(2): flush file (or directory) data+metadata to stable storage.
295// PROBE-PROVEN 2026-06-10 (_fsync_probe): rv64 82 is NOT in the compiler's
296// translation table (lands on x86 rename -> -EFAULT both ways); direct
297// x86_64 74 passes through raw (the unlinkat-263 precedent) and behaves as
298// fsync (0 on a valid fd, -9 EBADF on a bad one). Storage commit points
299// fsync the data files AND their directory around rename(2) so a committed
300// segment survives power loss, not just process death.
301func sys_fsync(fd: i64) -> i64 {
302 return __syscall(74, fd, 0, 0, 0, 0, 0)
303}
304
305// flock(2): BSD-style whole-file ADVISORY lock. rv64 32 -> x86_64 73 via the compiler's
306// x86ctx_rv64_to_x86_64_syscall table (nx_x86_64_ctx.nx:961, PROVEN LIVE in flock_deploy.log).
307// op: SYS_LOCK_SH=1 / SYS_LOCK_EX=2 / SYS_LOCK_NB=4 (OR) / SYS_LOCK_UN=8. Returns 0 on success,
308// -errno on failure. Used by the framed-append durability floor to serialize the write-until-
309// complete loop so a partial/short write under contention can NEVER misalign a concurrent appender
310// (O_APPEND single-write atomicity is necessary but not sufficient on every fs -- the lock makes
311// the whole framed record write atomic against other lockers). Additive: no existing caller in
312// this file changes. NOTE: nx_flock.nx is a separate organ importing the LEGACY "syscalls.nx"
313// name; this wrapper lives HERE so organs already on nx_syscalls.nx (e.g. nx_framed_append) get
314// flock without a second import (double-import rc=6 trap).
315const SYS_LOCK_SH: i64 = 1
316const SYS_LOCK_EX: i64 = 2
317const SYS_LOCK_NB: i64 = 4
318const SYS_LOCK_UN: i64 = 8
319func sys_flock(fd: i64, op: i64) -> i64 {
320 return __syscall(32, fd, op, 0, 0, 0, 0)
321}
322
323// newfstatat(2): stat `path` into a 144-byte x86-64 struct stat at `statbuf`. x86_64 nr 262 is passed
324// DIRECTLY (the unlinkat-263 / fsync-74 precedent: stat-family rv64 numbers aren't in the compiler's
325// translation table, so a raw x86_64 number passes through untranslated). Returns 0 on success, <0
326// (e.g. -2 ENOENT) on error. st_mtim.tv_sec @ offset 88, st_mtim.tv_nsec @ 96 (the freshness channel).
327func sys_fstatat(path: *u8, statbuf: *u8) -> i64 {
328 return __syscall(262, AT_FDCWD, path, statbuf, 0, 0, 0)
329}
330
331// utimensat(2): set `path` atime+mtime from `times` (a struct timespec[2] = [atime.sec,atime.nsec,
332// mtime.sec,mtime.nsec]). x86_64 nr 280 passed DIRECTLY. A sovereign `touch`; also makes freshness
333// tests deterministic. Returns 0 on success, <0 on error.
334func sys_utimensat(path: *u8, times: *i64) -> i64 {
335 return __syscall(280, AT_FDCWD, path, times as i64, 0, 0, 0)
336}
337
338// ---- sovereign host control-plane syscalls (x86_64; single unconditional consts,
339// per the known-good-compiler @ifdef finding). The Nishi supervisor uses these to
340// manage the daemon lifecycle WITHOUT any shell (no pkill / mkdir / chmod glue). ----
341
342// COMPILER NOTE: the known-good compiler BAKES whole function bodies by NAME for some syscalls
343// (proven via emitted .s: a function literally named sys_kill emits number 8, sys_chmod emits 155
344// -- both wrong, regardless of the const referenced). So these wrappers use NON-baked names
345// (nx_kill / nx_chmod). sys_mkdir / sys_renameat are not baked, so those keep the sys_ name.
346
347// DESIGN: __syscall takes the RV64/generic number; the compiler's x86ctx_rv64_to_x86_64_syscall table
348// (nx_x86_64_ctx.nx) translates it to the build target. So pass the RV64 number. These four were added
349// to that sovereign table 2026-06-06 (kill 129->62, mkdirat 34->258, fchmodat 53->268, renameat2
350// 276->316); x86 kill(62) had collided with rv64 lseek(62), x86 fchmodat(268) with rv64 pivot_root(268).
351
352// kill(pid, sig) -- rv64 129 -> x86_64 62. SIGTERM=15 / SIGKILL=9. Host control plane.
353func nx_kill(pid: i64, sig: i64) -> i64 { return __syscall(129, pid, sig, 0, 0, 0, 0) }
354
355// prlimit64(pid, resource, new_limit, old_limit) -- the Linux RESOURCE-LIMIT primitive =
356// the Job-Object ActiveProcessLimit / memory-limit analog for the sovereign supervisor (M5).
357// x86_64 prlimit64 = 302 (PASSED DIRECTLY, the unlinkat-263 / fsync-74 / fstatat-262
358// precedent: a raw x86_64 number not in the compiler's rv64->x86 swap table passes through
359// untranslated). NOTE: rv64 prlimit64 IS 261 but x86_64 261 = futimesat -- so the naive
360// "261 is the same on both" is WRONG (PROBE-PROVEN: 261 returned EFAULT/EINVAL because it
361// hit futimesat); the build target here is x86_64, so we emit 302 directly. pid=0 => the
362// calling process (a forked child caps ITSELF before running its payload). new_limit /
363// old_limit each point at a struct rlimit64 { rlim_cur: i64, rlim_max: i64 } (16 bytes);
364// pass 0 for old_limit to skip read-back. Returns 0 on success, -errno (e.g. -1 EPERM if
365// raising a hard limit unprivileged) on failure. NON-baked name (the compiler bakes some
366// sys_* bodies by name; the nx_ prefix avoids that trap).
367func nx_prlimit(pid: i64, resource: i64, new_limit: *u8, old_limit: *u8) -> i64 {
368 return __syscall(302, pid, resource, new_limit as i64, old_limit as i64, 0, 0)
369}
370
371// RLIMIT resource ids (Linux generic; identical rv64/x86_64). RLIMIT_AS = address-space
372// (virtual memory) cap -- the cleanest userspace-settable "memory budget" for a supervised
373// job. RLIMIT_CPU = CPU-seconds cap. WNOHANG=1 = wait4 non-blocking liveness poll option.
374const RLIMIT_CPU: i64 = 0
375const RLIMIT_AS: i64 = 9
376const WNOHANG: i64 = 1
377
378// mkdirat -- rv64 34 -> x86_64 258. Create a doc-root directory. mode e.g. 0x1ed (0755).
379func sys_mkdir(path: *u8, mode: i64) -> i64 { return __syscall(34, AT_FDCWD, path, mode, 0, 0, 0) }
380
381// fchmodat -- rv64 53 -> x86_64 268. +x a freshly-deployed daemon binary (mode 0x1ed). flags=0.
382func nx_chmod(path: *u8, mode: i64) -> i64 { return __syscall(53, AT_FDCWD, path, mode, 0, 0, 0) }
383
384// setsid -- x86_64 = 112 (not in the rv64->x86 table, so the literal passes through). Detach a forked
385// process into a NEW session so it survives the SSH/parent close -- sovereign daemonization (no shell setsid).
386func nx_setsid() -> i64 { return __syscall(112, 0, 0, 0, 0, 0, 0) }
387
388// CLOCK_MONOTONIC = 1. ts is 16 bytes {sec: i64, nsec: i64}.
389// Returns 0 / -errno.
390func sys_clock_gettime_mono(ts: *i64) -> i64 {
391 return __syscall(SYS_CLOCK_GETTIME, 1, ts, 0, 0, 0, 0)
392}
393
394// CLOCK_REALTIME = 0 -- wall-clock seconds since the Unix epoch. Use
395// this (NOT monotonic) for anything that must match calendar time:
396// X.509 notBefore/notAfter, logs, TLS timestamps. Monotonic returns
397// time-since-boot, which encodes as ~1970 when (mis)used as an epoch.
398func sys_clock_gettime_real(ts: *i64) -> i64 {
399 return __syscall(SYS_CLOCK_GETTIME, 0, ts, 0, 0, 0, 0)
400}
401
402// Wall-clock seconds since the Unix epoch.
403func sys_now_realtime_sec() -> i64 {
404 let ts: *i64 = sys_mmap(16) as *i64
405 sys_clock_gettime_real(ts)
406 return ts[0]
407}
408
409// Wall-clock milliseconds since the Unix epoch.
410func sys_now_realtime_ms() -> i64 {
411 let ts: *i64 = sys_mmap(16) as *i64
412 sys_clock_gettime_real(ts)
413 return ts[0] * 1000 + ts[1] / SYS_MAGIC_1000000
414}
415
416// Convenience: monotonic time in milliseconds. Caller does not own
417// the timespec buffer -- it is mmap'd once per call (cheap; the
418// underlying syscall already costs more than the page fault).
419func sys_now_ms() -> i64 {
420 let ts: *i64 = sys_mmap(16) as *i64
421 sys_clock_gettime_mono(ts)
422 let sec_part: i64 = ts[0] * 1000
423 let nsec_part: i64 = ts[1] / SYS_MAGIC_1000000
424 return sec_part + nsec_part
425}
426
427// Convenience: monotonic time in microseconds. Used by per-request
428// elapsed-time tracking in search engines + benches where ms is too
429// coarse. Same caller-ownership rules as sys_now_ms.
430func sys_now_us() -> i64 {
431 let ts: *i64 = sys_mmap(16) as *i64
432 sys_clock_gettime_mono(ts)
433 let sec_part: i64 = ts[0] * SYS_MAGIC_1000000
434 let nsec_part: i64 = ts[1] / 1000
435 return sec_part + nsec_part
436}
437
438// Alias used by nx_search_onsite_engine etc. Matches `_us` naming
439// convention. Substrate-canonical name is sys_now_us; this alias
440// preserves existing call sites without churn.
441func sys_clock_now_us() -> i64 {
442 return sys_now_us()
443}
444
445// Read the entire file at `path` into a fresh mmap'd buffer. Returns
446// a null-terminated *u8 plus writes the byte count to *out_len. On
447// error (open failure, oversize) returns null and leaves out_len = 0.
448// Uses a fixed 1 MiB buffer for the first pass; larger sources need a
449// growth loop.
450// ---- process control (Linux RV64) ----------------------------
451//
452// Lets NishiLang programs spawn other processes -- prerequisite
453// for replacing shell scripts (f6_gate.sh) with .nx equivalents.
454// NishiOS will expose a different process model (capability-based);
455// these wrappers are the Linux-host compatibility layer.
456
457@ifdef TARGET_X86_64
458const SYS_CLONE: i64 = 56
459const SYS_EXECVE: i64 = 59
460const SYS_WAIT4: i64 = 61
461const SYS_PIPE2: i64 = 293
462const SYS_DUP3: i64 = 292
463@endif
464
465@ifndef TARGET_X86_64
466const SYS_CLONE: i64 = 220
467const SYS_EXECVE: i64 = 221
468const SYS_WAIT4: i64 = 260
469const SYS_PIPE2: i64 = 59
470const SYS_DUP3: i64 = 24
471@endif
472
473// Clone flags (subset). CLONE_VFORK blocks parent until child
474// exec's or exits, matching fork() semantics closely enough for
475// our spawn-then-wait patterns.
476const CLONE_VM: i64 = 0x00000100
477const CLONE_VFORK: i64 = 0x00004000
478const SIGCHLD: i64 = 17
479
480// Create a child process via Linux clone(). Returns:
481// > 0 in the parent: child PID
482// == 0 in the child: child should exec or exit
483// < 0 on error: -errno
484// Uses SIGCHLD as the signal that parent receives on child exit
485// (the libc fork() default); no shared memory or thread flags.
486// ---- namespace / container family (debt 1785528831) ----------------
487// Moved here from nx_syscalls_x86_64.nx so ONE module owns the wrapper set. Their
488// absence here is why nx_container.nx had to import that module as a SECOND syscall
489// layer, which put every wrapper in the TU twice and let definition ORDER pick the
490// winner, silently, until the duplicate-definition guard made it fail closed.
491func sys_unshare(flags: i64) -> i64 {
492 return __syscall(SYS_UNSHARE, flags, 0, 0, 0, 0, 0)
493}
494func sys_mount(source: *u8, target: *u8, fs_type: *u8, mountflags: i64, data: *u8) -> i64 {
495 return __syscall(SYS_MOUNT, source, target, fs_type, mountflags, data, 0)
496}
497func sys_chroot(path: *u8) -> i64 {
498 return __syscall(SYS_CHROOT, path, 0, 0, 0, 0, 0)
499}
500func sys_getuid() -> i64 {
501 return __syscall(SYS_GETUID, 0, 0, 0, 0, 0, 0)
502}
503func sys_getgid() -> i64 {
504 return __syscall(SYS_GETGID, 0, 0, 0, 0, 0, 0)
505}
506
507func sys_fork() -> i64 {
508 return __syscall(SYS_CLONE, SIGCHLD, 0, 0, 0, 0, 0)
509}
510
511// Replace the current process image. `path` is the executable
512// (absolute or in $PATH if the child first does a fresh clone).
513// `argv` is a null-terminated array of *u8 (already-marshalled).
514// `envp` same shape, or null for "inherit parent's env".
515// Only returns on failure (-errno).
516// EXEC WITH A CLEAN FD TABLE (seq1785451144). A child inherits every fd its parent held, INCLUDING
517// listen sockets, across fork AND execve. That is how nx_opaque_login came to hold mgmt s :18098
518// alongside mgmt itself -- two listeners on one port, connections split between them, a VALID route
519// answering 404 on some requests. There is no error anywhere in that state, which is why it was
520// filed as a transport flake for months.
521// ADDITIVE ON PURPOSE: sys_execve is left byte-identical (910 call sites across 719 files -- a
522// global change there is unverifiable in one session). Spawners opt in by calling THIS instead.
523// AUDIT THAT MAKES IT SAFE: zero call sites in the tree dup3 to a target fd above 2, so no exec d
524// child is deliberately handed a high fd; 0/1/2 are preserved untouched.
525func sys_execve_clean(path: *u8, argv: *i64, envp: *i64) -> i64 {
526 var fd: i64 = 3
527 while fd < SYS_MAGIC_1024 { sys_close(fd); fd = fd + 1 }
528 return sys_execve(path, argv, envp)
529}
530
531func sys_execve(path: *u8, argv: *i64, envp: *i64) -> i64 {
532 return __syscall(SYS_EXECVE, path, argv, envp, 0, 0, 0)
533}
534
535// Wait for a child to exit. `pid` = -1 waits for ANY child,
536// otherwise waits for that specific PID. `status` is a caller-
537// mmapped i64 slot: on exit the low 16 bits carry Linux's w* status
538// flags (WIFEXITED / WEXITSTATUS). Returns the reaped child's PID
539// or -errno.
540func sys_wait4(pid: i64, status: *i64, options: i64) -> i64 {
541 return __syscall(SYS_WAIT4, pid, status, options, 0, 0, 0)
542}
543
544// Extract exit code from a wait4 status word. Matches the glibc
545// WEXITSTATUS macro: bits 8-15 of the low 16.
546func wait_exit_code(status: i64) -> i64 {
547 return (status >> 8) & 0xFF
548}
549
550// Terminating signal from a wait4 status (0 when the child exited normally). Sibling of
551// wait_exit_code; RESTORED 2026-07-30 after a stale whole-tree push erased both it and
552// sys_ignore_sigpipe below, while three files still CALLED them (nx_http_server, nx_sigpipe_gate,
553// nx_tools_api_serve) -- so the tree could not build until they came back.
554func wait_term_signal(status: i64) -> i64 {
555 return status & 0x7f
556}
557
558// Ignore SIGPIPE process-wide, so writing to a socket the peer already closed returns -EPIPE
559// instead of KILLING the process. SIGPIPE default action is TERMINATE, which for a daemon means
560// every client that walks away mid-response is an outage -- this one call at the listen primitive
561// is inherited by all 52 consumers of nx_http_server_listen.
562// rt_sigaction(SIGPIPE, {handler=SIG_IGN}, NULL, 8): syscall 13 on x86-64, which happens to equal
563// the signal number. SA_RESTORER is deliberately NOT set -- the kernel consults it only when it
564// DELIVERS a handler frame, and SIG_IGN never delivers one.
565// PROVEN, not asserted: nx_sigpipe_gate forks a child that writes to a closed pipe and demands
566// death-by-signal-13 WITHOUT this call and a clean -EPIPE WITH it.
567// Restore a signal to its DEFAULT disposition. THE INVERSE OF sys_ignore_sigpipe, and it exists
568// because SIG_IGN is inherited across BOTH fork and execve: a daemon that ignores SIGPIPE hands
569// that ignore to every child it spawns, FOREVER. That silently corrupted verification -- the
570// sigpipe gate reported 4/5 RED under /api/gate_run and 5/5 GREEN under a shell, same binary,
571// same minute, because its DISEASE control (writing to a closed peer must KILL) could not be
572// observed inside an environment where the kill was already disabled (seq1463). A harness must
573// not change the state it is verifying; where it must, it has to hand back a clean slate.
574// ⚠the same inheritance can also produce a FALSE GREEN, which is the far more dangerous half.
575func sys_default_signal(sig: i64) -> i64 {
576 let act: *i64 = sys_mmap(64) as *i64
577 act[0] = 0
578 act[1] = 0
579 act[2] = 0
580 act[3] = 0
581 return __syscall(13, sig, act as i64, 0, 8, 0, 0)
582}
583
584func sys_ignore_sigpipe() -> i64 {
585 let act: *i64 = sys_mmap(64) as *i64
586 act[0] = 1
587 act[1] = 0
588 act[2] = 0
589 act[3] = 0
590 return __syscall(13, 13, act as i64, 0, 8, 0, 0)
591}
592
593// Create a pipe. `fds` must point at 8+ writable bytes; the kernel
594// packs BOTH int32 fds into fds[0]: read end = low 32 bits, write end
595// = HIGH 32 bits (fds[1] is never written -- the old comment claiming
596// fds[1]=write-end caused a false-pass KAT + a hung gate, 2026-07-16).
597// Extract: rfd = fds[0] & 0xffffffff; wfd = (fds[0] / 4294967296) &
598// 0xffffffff. Returns 0 on success, -errno on failure.
599func sys_pipe2(fds: *i64, flags: i64) -> i64 {
600 return __syscall(SYS_PIPE2, fds, flags, 0, 0, 0, 0)
601}
602
603// Duplicate `oldfd` onto `newfd`, closing `newfd` first if open.
604// Used to wire child stdout to a pipe: dup3(pipe_write_end, 1).
605func sys_dup3(oldfd: i64, newfd: i64, flags: i64) -> i64 {
606 return __syscall(SYS_DUP3, oldfd, newfd, flags, 0, 0, 0)
607}
608
609// ---- directory listing (Linux RV64 getdents64) ---------------
610//
611// Foundation for ls / glob / dir-walk helpers. Linux returns
612// linux_dirent64 records:
613// u64 d_ino (inode, ignored here)
614// s64 d_off (next-record offset)
615// u16 d_reclen (this record's byte length)
616// u8 d_type (file type; DT_DIR=4, DT_REG=8, DT_LNK=10)
617// char d_name[] (null-terminated name, padded so d_reclen
618// carries us to the next record boundary)
619// Total struct header: 19 bytes, then name up to d_reclen - 19.
620
621@ifdef TARGET_X86_64
622const SYS_GETDENTS64: i64 = 217
623@endif
624@ifndef TARGET_X86_64
625const SYS_GETDENTS64: i64 = 61
626@endif
627
628const DT_UNKNOWN: i64 = 0
629const DT_FIFO: i64 = 1
630const DT_CHR: i64 = 2
631const DT_DIR: i64 = 4
632const DT_BLK: i64 = 6
633const DT_REG: i64 = 8
634const DT_LNK: i64 = 10
635const DT_SOCK: i64 = 12
636
637// Raw syscall. Returns bytes written on success (0 = end-of-dir),
638// or -errno on failure.
639func sys_getdents64(fd: i64, buf: *u8, buf_len: i64) -> i64 {
640 return __syscall(SYS_GETDENTS64, fd, buf, buf_len, 0, 0, 0)
641}
642
643// Extract fields from a linux_dirent64 record. `rec` points at
644// the start of the record; fields are at fixed offsets.
645func dirent_reclen(rec: *u8) -> i64 {
646 // d_reclen is u16 at offset 16. Read as two bytes little-endian.
647 let lo: i64 = rec[16]
648 let hi: i64 = rec[17]
649 return lo | (hi << 8)
650}
651
652func dirent_type(rec: *u8) -> i64 {
653 return rec[18]
654}
655
656// Pointer to the null-terminated name inside the record.
657func dirent_name(rec: *u8) -> *u8 {
658 let base: i64 = rec as i64
659 return (base + 19) as *u8
660}
661
662// ---- content-addressed file reader ---------------------------
663
664func sys_read_file(path: *u8, out_len: *i64) -> *u8 {
665 let fd: i64 = sys_openat_rd(path)
666 if fd < 0 {
667 *out_len = 0
668 return 0 as *u8
669 }
670 // DEBT-EATEN 2026-07-15: the old fixed 4 GiB cap SILENTLY TRUNCATED bigger files (a 9 GB gguf would
671 // short-read into plausible-garbage tensors -- the worst failure class). Now the buffer is sized from
672 // the file itself (lseek END), so ANY size reads fully; the 4 GiB figure remains only as the fallback
673 // for special files where size is unknowable (/proc, pipes: lseek <= 0). Physical pages still
674 // allocate on-demand. For zero-copy any-size READ-ONLY access prefer sys_map_file (below).
675 let fsz: i64 = sys_lseek(fd, 0, 2)
676 sys_lseek(fd, 0, 0)
677 var cap: i64 = SYS_MAGIC_4294967296
678 if fsz > 0 { cap = fsz }
679 let buf: *u8 = sys_mmap(cap + 16)
680 var total: i64 = 0
681 var go: i64 = 1
682 while go == 1 {
683 let base: i64 = buf as i64
684 let tail: *u8 = (base + total) as *u8
685 let n: i64 = sys_read(fd, tail, cap - total)
686 if n <= 0 { go = 0 }
687 if n > 0 { total = total + n }
688 if total >= cap { go = 0 }
689 }
690 sys_close(fd)
691 // Null-terminate for the lexer.
692 let bbase: i64 = buf as i64
693 let term: *u8 = (bbase + total) as *u8
694 term[0] = 0
695 *out_len = total
696 return buf
697}
698
699// Read-only FILE-BACKED map of the whole file (PROT_READ=1, MAP_PRIVATE=2): any size, zero-copy -- only
700// touched pages become resident (the lazy-MoE shape: a 9 GB model serves in ~active-set RSS, and load
701// time is ~0 because nothing is copied). NO NUL pad (a file mapping cannot be extended) -- BINARY
702// consumers only; text/lexer callers keep sys_read_file. Returns 0 on failure; *out_len = file size.
703// Read-only by construction (PROT_READ; writes fault -- Rule 26-friendly).
704func sys_map_file(path: *u8, out_len: *i64) -> *u8 {
705 *out_len = 0
706 let fd: i64 = sys_openat_rd(path)
707 if fd < 0 { return 0 as *u8 }
708 let fsz: i64 = sys_lseek(fd, 0, 2)
709 if fsz <= 0 { sys_close(fd); return 0 as *u8 }
710 let r: i64 = __syscall(SYS_MMAP, 0, fsz, 1, 2, fd, 0)
711 sys_close(fd)
712 if r <= 0 { return 0 as *u8 }
713 *out_len = fsz
714 return r as *u8
715}
716
717// Sleep for `ms` milliseconds against CLOCK_MONOTONIC (relative).
718// Returns 0 on success, negative errno on failure. Caller-supplied
719// budget: ms <= 0 is a no-op; very large values are accepted as-is
720// (the kernel will saturate to its own clamp). Defined at the bottom
721// of this file so sys_mmap is in scope (single-pass parser).
722func sys_sleep_ms(ms: i64) -> i64 {
723 if ms <= 0 { return 0 }
724 // struct timespec { sec: i64, nsec: i64 } -- 16 bytes RV64.
725 let req: *u8 = sys_mmap(16)
726 let rem: *u8 = sys_mmap(16)
727 let secs: i64 = ms / 1000
728 let nsec: i64 = (ms - secs * 1000) * SYS_MAGIC_1000000 // remainder ms -> ns
729 let req_sec: *i64 = req as *i64
730 let req_nsec: *i64 = ((req as i64) + 8) as *i64
731 req_sec[0] = secs
732 req_nsec[0] = nsec
733 // clock_nanosleep(CLOCK_MONOTONIC=1, flags=0, req, rem). On EINTR (-4) a signal (e.g. SIGCHLD from a
734 // reaped child) cut the sleep short and wrote the leftover into rem -- RESUME it, otherwise a caller
735 // that uses the sleep as a timer (the torrent pool's 2s tick) gets spun into a busy loop by child
736 // deaths and any tick-based budget collapses to milliseconds. A sleep must sleep its full duration.
737 var r: i64 = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0)
738 var guard: i64 = 0
739 while r == (0 - 4) {
740 if guard > SYS_MAGIC_100000 { r = 0 } else {
741 let rs: *i64 = rem as *i64
742 let rn: *i64 = ((rem as i64) + 8) as *i64
743 req_sec[0] = rs[0]
744 req_nsec[0] = rn[0]
745 r = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0)
746 guard = guard + 1
747 }
748 }
749 sys_munmap(req, 16); sys_munmap(rem, 16) // FREE the timespec pages -- every call mmap'd 2 pages; in a
750 // long-running poll loop (the supervisor's 15s tick) that leaked ~8KB/iter until mmap -> -12 -> SEGFAULT.
751 return r
752}
753
754// ---- sockets (RV64 generic syscall numbers) ----------------------
755//
756// Source uses RV64 numbers; the x86_64 backend's
757// x86ctx_rv64_to_x86_64_syscall table translates at codegen time.
758// Numbers from arch/arm64/include/asm/unistd.h (RV64 inherits the
759// generic ABI).
760
761// Socket-family syscall numbers via @ifdef macro -- mirrors the
762// pattern already used for SYS_READ/WRITE/MMAP/etc. above. Without
763// this gate, --target x86_64 compiled the RV64 numbers as literals
764// into the `syscall` instruction (e.g. 198 = sched_setaffinity on
765// x86_64, not socket) and any daemon using sys_socket() died with
766// ENOSYS before printing its banner -- caught by the nx_signaling
767// stone S2 deploy on 2026-05-20 (see [[project-cross-isa-syscall-
768// unification-gap-2026-05-20]]).
769@ifdef TARGET_X86_64
770const SYS_SOCKET: i64 = 41
771const SYS_BIND: i64 = 49
772const SYS_LISTEN: i64 = 50
773const SYS_ACCEPT: i64 = 43
774const SYS_CONNECT: i64 = 42
775const SYS_SETSOCKOPT: i64 = 54
776const SYS_SENDTO: i64 = 44
777const SYS_RECVFROM: i64 = 45
778const SYS_SHUTDOWN: i64 = 48
779@endif
780
781@ifndef TARGET_X86_64
782const SYS_SOCKET: i64 = 198
783const SYS_BIND: i64 = 200
784const SYS_LISTEN: i64 = 201
785const SYS_ACCEPT: i64 = 202
786const SYS_CONNECT: i64 = 203
787const SYS_SETSOCKOPT: i64 = 208
788const SYS_SENDTO: i64 = 206
789const SYS_RECVFROM: i64 = 207
790const SYS_SHUTDOWN: i64 = 210
791@endif
792
793// Socket-option constants used by nx_http_server / nx_https_server.
794const SOL_SOCKET: i64 = 1
795const SO_REUSEADDR: i64 = 2
796// Receive/send timeouts (Linux x86_64). optval is a struct timeval
797// {tv_sec: i64, tv_usec: i64} (16 bytes). Essential on PUBLIC sockets:
798// without them, a single silent/slow client hangs a blocking read
799// forever -> trivial DoS on a single-threaded accept loop.
800const SO_SNDTIMEO: i64 = 21
801const SO_RCVTIMEO: i64 = 20
802
803// setsockopt(2) -- set a socket option. Defined BEFORE its first caller
804// (sys_set_socket_timeout, below): NishiLang forbids forward references,
805// so the definition must precede every use.
806func sys_setsockopt(fd: i64, level: i64, optname: i64,
807 optval: *u8, optlen: i64) -> i64 {
808 return __syscall(SYS_SETSOCKOPT, fd, level, optname, optval, optlen, 0)
809}
810
811// Set a receive+send timeout (in whole seconds) on a socket fd.
812// tv is munmap'd before return (LEAK FIXED 2026-07-16): this is called once per PROBE by the daemon
813// supervisor (35/cycle forever -> ~800MB VSZ/day) and once per CONNECTION by fork-per-connection daemons.
814// The unfreed page-per-call ballooned VSZ until heuristic overcommit made fork() return -ENOMEM (the
815// proven pid=-12 failure class) -- likely the historical VSZ pressure behind the vsz_watchdog.
816func sys_set_socket_timeout(fd: i64, secs: i64) -> i64 {
817 let tv: *i64 = (sys_mmap(16)) as *i64
818 tv[0] = secs // tv_sec
819 tv[1] = 0 // tv_usec
820 sys_setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv as *u8, 16)
821 sys_setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, tv as *u8, 16)
822 sys_munmap(tv as *u8, 16)
823 return 0
824}
825
826// alarm(2): deliver SIGALRM after `secs` seconds (0 cancels a pending alarm). No SIGALRM handler is installed, so
827// the default action TERMINATES the process. Used as a per-request watchdog inside a forked request-child: a
828// pathologically-slow page can then never hang the child forever (which would leak its buffers + pile up procs).
829const SYS_ALARM: i64 = 37
830func sys_alarm(secs: i64) -> i64 { return __syscall(SYS_ALARM, secs, 0, 0, 0, 0, 0) }
831
832const AF_INET: i64 = 2
833const SOCK_STREAM: i64 = 1
834const SOCK_DGRAM: i64 = 2
835
836func sys_socket(domain: i64, sock_type: i64, protocol: i64) -> i64 {
837 return __syscall(SYS_SOCKET, domain, sock_type, protocol, 0, 0, 0)
838}
839func sys_bind(fd: i64, addr: *u8, addr_len: i64) -> i64 {
840 return __syscall(SYS_BIND, fd, addr, addr_len, 0, 0, 0)
841}
842func sys_listen(fd: i64, backlog: i64) -> i64 {
843 return __syscall(SYS_LISTEN, fd, backlog, 0, 0, 0, 0)
844}
845// accept(2) -- accept the next pending connection on a listening socket.
846// Single-arg form (kernel ignores NULL addr/addr_len writes). Existing
847// nx_http_server callers use this signature; the 3-arg form is provided
848// as sys_accept_with_addr for outliers needing peer address.
849func sys_accept(fd: i64) -> i64 {
850 return __syscall(SYS_ACCEPT, fd, 0, 0, 0, 0, 0)
851}
852func sys_accept_with_addr(fd: i64, addr: *u8, addr_len: *i64) -> i64 {
853 return __syscall(SYS_ACCEPT, fd, addr, addr_len, 0, 0, 0)
854}
855// shutdown(2) -- half-close a socket. how: 0=RD, 1=WR, 2=RDWR.
856func sys_shutdown(fd: i64, how: i64) -> i64 {
857 return __syscall(SYS_SHUTDOWN, fd, how, 0, 0, 0, 0)
858}
859func sys_connect(fd: i64, addr: *u8, addr_len: i64) -> i64 {
860 return __syscall(SYS_CONNECT, fd, addr, addr_len, 0, 0, 0)
861}
862func sys_sendto(fd: i64, buf: *u8, n: i64, flags: i64,
863 dest_addr: *u8, addr_len: i64) -> i64 {
864 return __syscall(SYS_SENDTO, fd, buf, n, flags, dest_addr, addr_len)
865}
866func sys_recvfrom(fd: i64, buf: *u8, n: i64, flags: i64,
867 src_addr: *u8, addr_len: *i64) -> i64 {
868 return __syscall(SYS_RECVFROM, fd, buf, n, flags, src_addr, addr_len)
869}