code wiki / (root) / nx_fcntl.nx

nx_fcntl.nx source

↩ module page · 166 lines · 6483 B

1// nx_fcntl.nx -- fcntl(2) + open-flag constants. 2// 3// The existing fs.nx + sys_openat in syscalls.nx open files with 4// minimal flag awareness. Real callers need to pick read/write/ 5// create/append/truncate/sync/cloexec semantics, set close-on-exec 6// on existing fds, mark fds nonblocking, query / change file 7// descriptor flags. 8// 9// This module provides the constants + a thin wrapper around the 10// fcntl(2) syscall (number 25 on RV64). 11// 12// Pairs with fs.nx + nx_proc (so children don't inherit 13// FD_CLOEXEC fds across exec). 14 15// nx_safety_envelope: 16// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 17// sil_target: SIL1 18// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 19// verdict: NOT_YET_EVALUATED 20 21import "syscalls.nx" 22 23// open() / openat() flags (Linux RV64). 24const NX_O_RDONLY: i64 = 0x0 25const NX_O_WRONLY: i64 = 0x1 26const NX_O_RDWR: i64 = 0x2 27const NX_O_CREAT: i64 = 0x40 28const NX_O_EXCL: i64 = 0x80 29const NX_O_TRUNC: i64 = 0x200 30const NX_O_APPEND: i64 = 0x400 31const NX_O_NONBLOCK: i64 = 0x800 32const NX_O_SYNC: i64 = 0x101000 33const NX_O_DIRECTORY: i64 = 0x10000 34const NX_O_CLOEXEC: i64 = 0x80000 35 36// fcntl() commands. 37const NX_F_DUPFD: i64 = 0 38const NX_F_GETFD: i64 = 1 39const NX_F_SETFD: i64 = 2 40const NX_F_GETFL: i64 = 3 41const NX_F_SETFL: i64 = 4 42const NX_F_DUPFD_CLOEXEC: i64 = 1030 43 44// fd flags (for F_GETFD / F_SETFD). 45const NX_FD_CLOEXEC: i64 = 1 46 47// AT_FDCWD passed as dirfd to *at(2) syscalls. 48// Linux value is -100; encoded as the signed two's-complement 49// representation since NishiLang const initialisers can't hold 50// expressions like (0 - 100) yet. 51const NX_AT_FDCWD: i64 = 0xFFFFFFFFFFFFFF9C 52 53// Per-target syscall numbers. Cross-target via @ifdef TARGET_X86_64 54// per [[feedback-hardware-agnostic-is-robustness]] (the bit-level 55// no-proliferation cardinal applied to syscall ABI: same shape as 56// nx_syscalls.nx's SYS_* block). 57 58@ifdef TARGET_X86_64 59const NX_SYS_FCNTL: i64 = 72 60const NX_SYS_OPENAT: i64 = 257 61const NX_SYS_DUP: i64 = 32 62const NX_SYS_DUP3: i64 = 292 63@endif 64 65@ifndef TARGET_X86_64 66// ⚠ fcntl is PINNED TO THE x86_64 NUMBER (72), NOT the RISC-V 25, and that is deliberate. 67// This branch is the LIVE one (TARGET_X86_64 is hard-pinned undefined, see nx_syscalls.nx:72) and 68// every other constant here is a RISC-V number that the nx_cc x86 backend TRANSLATES. The backend's 69// translation table has NO ENTRY FOR fcntl, so a literal 25 falls through to x86 mremap and returns 70// EINVAL -- which made nx_fcntl fail ALWAYS, which made nx_connect_bounded bail on its first line, 71// which took outbound TCP away from EVERY organ built after 2026-08-01 (debt 1785566771). 72// PROVEN: nx_fcntlprobe showed NX_SYS_FCNTL=25 and nx_fcntl(1,F_GETFL)=-22 on a plain fd AND a socket. 73// THE REAL FIX IS IN THE COMPILER -- add fcntl to the x86 syscall translation table (source is 74// laptop-side nishi-core/nxc2). Revert this pin the moment that lands, or RISC-V targets will break. 75const NX_SYS_FCNTL: i64 = 72 76const NX_SYS_OPENAT: i64 = 56 77const NX_SYS_DUP: i64 = 23 78const NX_SYS_DUP3: i64 = 24 79@endif 80 81// fcntl(fd, cmd, arg). 82func nx_fcntl(fd: i64, cmd: i64, arg: i64) -> i64 { 83 return __syscall(NX_SYS_FCNTL, fd, cmd, arg, 0, 0, 0) 84} 85 86// Mark a fd close-on-exec. 87func nx_fcntl_set_cloexec(fd: i64) -> i64 { 88 let cur: i64 = nx_fcntl(fd, NX_F_GETFD, 0) 89 if cur < 0 { return cur } 90 return nx_fcntl(fd, NX_F_SETFD, cur | NX_FD_CLOEXEC) 91} 92 93// Mark a fd nonblocking. 94func nx_fcntl_set_nonblock(fd: i64) -> i64 { 95 let cur: i64 = nx_fcntl(fd, NX_F_GETFL, 0) 96 if cur < 0 { return cur } 97 return nx_fcntl(fd, NX_F_SETFL, cur | NX_O_NONBLOCK) 98} 99 100// dup(oldfd) -> new fd referring to same open file. 101func nx_dup(oldfd: i64) -> i64 { 102 return __syscall(NX_SYS_DUP, oldfd, 0, 0, 0, 0, 0) 103} 104 105// dup3(oldfd, newfd, flags) -- Linux's dup2 replacement. flags 106// can include O_CLOEXEC. 107func nx_dup3(oldfd: i64, newfd: i64, flags: i64) -> i64 { 108 return __syscall(NX_SYS_DUP3, oldfd, newfd, flags, 0, 0, 0) 109} 110 111// openat with flag-aware signature. path is NUL-terminated. mode 112// is the file-creation permission bits used when O_CREAT is set 113// (e.g. 0o644 for "rw-r--r--" -> 0x1A4). 114func nx_openat(dirfd: i64, path: *u8, flags: i64, mode: i64) -> i64 { 115 return __syscall(NX_SYS_OPENAT, dirfd, path as i64, flags, mode, 0, 0) 116} 117 118// Convenience: open in cwd, read-only, no creation. 119func nx_open_rd(path: *u8) -> i64 { 120 return nx_openat(NX_AT_FDCWD, path, NX_O_RDONLY, 0) 121} 122 123// Convenience: open in cwd, write-only, create + truncate, mode 0o644. 124func nx_open_wr(path: *u8) -> i64 { 125 return nx_openat(NX_AT_FDCWD, path, NX_O_WRONLY | NX_O_CREAT | NX_O_TRUNC, 0x1A4) 126} 127 128// Convenience: open in cwd, read-write append, no create. 129func nx_open_append(path: *u8) -> i64 { 130 return nx_openat(NX_AT_FDCWD, path, NX_O_WRONLY | NX_O_APPEND, 0) 131} 132 133// ---- self-test --------------------------------------------------- 134 135func main() -> i64 { 136 // Constants are within Linux range. 137 if NX_O_RDONLY != 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 138 if NX_O_WRONLY != 1 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 139 if NX_O_CREAT != 0x40 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 140 if NX_O_TRUNC != 0x200 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 141 if NX_O_CLOEXEC != 0x80000 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 142 143 if NX_F_GETFD != 1 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 144 if NX_F_SETFD != 2 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 145 if NX_FD_CLOEXEC != 1 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 146 if NX_AT_FDCWD != (0 - 100) { return __syscall(93, 9, 0, 0, 0, 0, 0) } 147 148 // Composed flags for "open for write, create if missing, 149 // truncate, close-on-exec". 150 let flags: i64 = NX_O_WRONLY | NX_O_CREAT | NX_O_TRUNC | NX_O_CLOEXEC 151 let expected: i64 = 1 | 0x40 | 0x200 | 0x80000 152 if flags != expected { return __syscall(93, 10, 0, 0, 0, 0, 0) } 153 154 // dup of stderr should produce a fresh fd >= 3 (or possibly an 155 // error if we're somehow out of fds; just check non-positive 156 // means error). 157 let dup_fd: i64 = nx_dup(2) 158 if dup_fd < 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 159 sys_close(dup_fd) 160 161 // F_GETFD on stderr should succeed (return 0 or FD_CLOEXEC). 162 let fl: i64 = nx_fcntl(2, NX_F_GETFD, 0) 163 if fl < 0 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 164 165 return 0 166}