nx_proc.nx source
↩ module page · 149 lines · 5123 B
1// nx_proc.nx -- process management syscalls (fork / execve / wait4 / clone).
2//
3// Used by:
4// - Test harnesses that fork off child workers
5// - Build tools that exec compilers / linkers / assemblers
6// - Sandboxes that spawn restricted subprocesses
7// - Watchdogs that fork supervisors
8//
9// All syscalls go through __syscall. We accept i64 for argv / envp
10// pointer arrays since NishiLang doesn't have *char ** in the type
11// system today.
12//
13// Exit-status macros: nx_proc_wait_exited / nx_proc_wait_status /
14// nx_proc_wait_signaled mirror the WIFEXITED / WEXITSTATUS / WIFSIGNALED
15// glibc macros for inspecting wait4's status word.
16
17// nx_safety_envelope:
18// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
19// sil_target: SIL1
20// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
21// verdict: NOT_YET_EVALUATED
22
23import "syscalls.nx"
24
25// Linux RV64 syscall numbers (excerpt).
26const NX_SYS_CLONE: i64 = 220
27const NX_SYS_EXECVE: i64 = 221
28const NX_SYS_EXIT: i64 = 93
29const NX_SYS_EXIT_GRP: i64 = 94
30const NX_SYS_WAIT4: i64 = 260
31const NX_SYS_GETPID: i64 = 172
32const NX_SYS_GETPPID: i64 = 173
33
34// clone flags
35const NX_CLONE_VM: i64 = 0x100
36const NX_CLONE_FS: i64 = 0x200
37const NX_CLONE_FILES: i64 = 0x400
38const NX_CLONE_SIGHAND: i64 = 0x800
39const NX_CLONE_THREAD: i64 = 0x10000
40
41// fork(): clone(SIGCHLD) on Linux. RV64 has no sys_fork; clone
42// with no special flags + SIGCHLD as the signal mask achieves it.
43//
44// Returns:
45// 0 in the child
46// pid in the parent
47// -errno on failure
48const NX_SIGCHLD_NUM: i64 = 17
49
50func nx_proc_fork() -> i64 {
51 return __syscall(NX_SYS_CLONE, NX_SIGCHLD_NUM, 0, 0, 0, 0, 0)
52}
53
54// execve(path, argv, envp). argv / envp are NULL-terminated arrays
55// of *u8 pointers. Caller passes them as i64 pointer values; we
56// hand them to the kernel verbatim.
57//
58// Returns:
59// no return on success (process is replaced)
60// -errno on failure
61func nx_proc_execve(path: *u8, argv: i64, envp: i64) -> i64 {
62 return __syscall(NX_SYS_EXECVE, path as i64, argv, envp, 0, 0, 0)
63}
64
65// wait4(pid, status_ptr, options, rusage_ptr). status_ptr receives
66// the encoded exit status; pass 0 for rusage_ptr if you don't care
67// about resource accounting.
68//
69// pid semantics:
70// -1 = any child
71// >0 = specific pid
72// 0 = any child in same process group
73// <-1 = any child in process group |pid|
74//
75// Returns:
76// pid of reaped child
77// 0 if WNOHANG was set and no child is ready
78// -errno on failure
79func nx_proc_wait4(pid: i64, status: *i64, options: i64) -> i64 {
80 return __syscall(NX_SYS_WAIT4, pid, status as i64, options, 0, 0, 0)
81}
82
83// Convenience: wait for any child, blocking, no rusage.
84func nx_proc_wait_any(status: *i64) -> i64 {
85 return nx_proc_wait4(0 - 1, status, 0)
86}
87
88// WIFEXITED: child exited normally (low byte of status == 0).
89func nx_proc_wait_exited(status: i64) -> i64 {
90 if (status & 0x7F) == 0 { return 1 }
91 return 0
92}
93
94// WEXITSTATUS: extract the 8-bit exit code (when WIFEXITED).
95func nx_proc_wait_status(status: i64) -> i64 {
96 return (status >> 8) & 0xFF
97}
98
99// WIFSIGNALED: child killed by signal (low 7 bits != 0 and != 0x7F).
100func nx_proc_wait_signaled(status: i64) -> i64 {
101 let low: i64 = status & 0x7F
102 if low == 0 { return 0 }
103 if low == 0x7F { return 0 }
104 return 1
105}
106
107// WTERMSIG: which signal killed the child (low 7 bits).
108func nx_proc_wait_termsig(status: i64) -> i64 {
109 return status & 0x7F
110}
111
112// getpid / getppid pass-through for completeness.
113func nx_proc_getpid() -> i64 {
114 return __syscall(NX_SYS_GETPID, 0, 0, 0, 0, 0, 0)
115}
116
117func nx_proc_getppid() -> i64 {
118 return __syscall(NX_SYS_GETPPID, 0, 0, 0, 0, 0, 0)
119}
120
121// ---- self-test ---------------------------------------------------
122
123func main() -> i64 {
124 // Constants are within Linux range.
125 if NX_SYS_CLONE != 220 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
126 if NX_SYS_EXECVE != 221 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
127 if NX_SIGCHLD_NUM != 17 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
128
129 // Status decoding edge cases.
130 // Child exited with code 5: status = 5 << 8 = 0x500.
131 if nx_proc_wait_exited(0x500) != 1 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
132 if nx_proc_wait_status(0x500) != 5 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
133 if nx_proc_wait_signaled(0x500) != 0 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
134
135 // Child killed by SIGTERM (15): status = 15.
136 if nx_proc_wait_exited(15) != 0 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
137 if nx_proc_wait_signaled(15) != 1 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
138 if nx_proc_wait_termsig(15) != 15 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
139
140 // Child stopped (low 7 = 0x7F): not exited, not killed.
141 if nx_proc_wait_exited(0x7F) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
142 if nx_proc_wait_signaled(0x7F) != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
143
144 // getpid should return a positive number.
145 let pid: i64 = nx_proc_getpid()
146 if pid <= 0 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
147
148 return 0
149}