code wiki / (root) / nx_syscalls.nx

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}