nx_shell_lib.nx source
↩ module page · 166 lines · 9011 B
1// nx_shell_lib.nx -- GENERAL SOVEREIGN SHELL CAPABILITY (the shared library for the Nishi shell toolchain).
2// Extracted (rule 15) so the gate (nx_shell) and the drivable tool (nx_shell_run) share ONE capability, not copies.
3// Pure library: no main. Imports only nx_syscalls (the kernel last-mile). NEVER-BRICK: spawns userspace, writes 0
4// persistent hardware state -> brick-safe by construction (cardinal 26).
5// UI g_puts/g_pn
6// STR sh_slen/sh_streq
7// R1 sh_tokenize (command line -> argv)
8// R3 sh_builtin (echo/cd/true/false in-process)
9// R4 sh_grep (substring) / sh_grep_stream (pipeline filter: fd0->fd1) / sh_run_capture (pipe-capture)
10// R2 sh_run (fork+execve+wait) / sh_exec_one (builtin-or-exec one command)
11// R5 sh_split_ops / sh_exec_line (; && ||)
12// R6/full sh_split_pipe / sh_stage_exec / sh_run_pipeline (N-stage |) / sh_exec_full (pipelines + control)
13import "nx_syscalls.nx"
14import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
15import "nx_regex_lib.nx"
16const K_MAGIC_65536: i64 = 65536
17
18func g_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
19// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
20// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
21// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
22// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
23func g_pn(v: i64) -> i64 { nxi_out(v); return 0 }
24func sh_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
25func sh_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } if b[i]==(0 as u8) { return 1 } return 0 }
26
27func sh_tokenize(line: *u8, copy: *u8, argv: *i64, maxtok: i64) -> i64 {
28 var i: i64=0; var argc: i64=0; var intok: i64=0
29 while line[i]!=(0 as u8) {
30 let c: i64 = line[i] as i64
31 if c==32 { copy[i]=0 as u8; intok=0 }
32 else { copy[i]=c as u8; if intok==0 { if argc<maxtok { argv[argc]=(copy+i as i64) as i64; argc=argc+1 } intok=1 } }
33 i=i+1
34 }
35 copy[i]=0 as u8; argv[argc]=0; return argc
36}
37
38func sh_builtin(argv: *i64, argc: i64) -> i64 {
39 let cmd: *u8 = argv[0] as *u8
40 if sh_streq(cmd, "true" as *u8)==1 { return 0 }
41 if sh_streq(cmd, "false" as *u8)==1 { return 1 }
42 if sh_streq(cmd, "cd" as *u8)==1 { if argc>1 { sys_chdir(argv[1] as *u8) } return 0 }
43 if sh_streq(cmd, "echo" as *u8)==1 { var i: i64=1; while i<argc { g_puts(argv[i] as *u8); if i<argc-1 { g_puts(" " as *u8) } i=i+1 } g_puts("\n" as *u8); return 0 }
44 return 0-1
45}
46
47// grep is now REGEX-powered (nx_regex_lib): `needle` is a regex pattern (literals still match literally => backward-compatible)
48func sh_grep(buf: *u8, n: i64, needle: *u8) -> i64 { if sh_slen(needle)==0 { return 0 } return re_search(needle, buf, n) }
49func sh_grep_stream(pattern: *u8) -> i64 {
50 let buf: *u8 = sys_mmap(K_MAGIC_65536); var n: i64=0; var go: i64=1
51 while go==1 { let k: i64=sys_read(0, buf+n as i64, K_MAGIC_65536-n); if k<=0 { go=0 } else { n=n+k; if n>=K_MAGIC_65536 { go=0 } } }
52 var start: i64=0; var i: i64=0
53 while i<n {
54 if buf[i]==(10 as u8) { if sh_grep(buf+start as i64, i-start, pattern)==1 { sys_write(1, buf+start as i64, i-start+1) } start=i+1 }
55 i=i+1
56 }
57 if start<n { if sh_grep(buf+start as i64, n-start, pattern)==1 { sys_write(1, buf+start as i64, n-start) } }
58 return 0
59}
60
61func sh_run(path: *u8, argv: *i64) -> i64 {
62 let pid: i64 = sys_fork()
63 if pid==0 { sys_execve(path, argv, 0 as *i64); sys_exit(127); return 0 }
64 let st: *i64 = sys_mmap(8); sys_wait4(pid, st, 0); return wait_exit_code(st[0])
65}
66func sh_run_capture(path: *u8, argv: *i64, out: *u8, cap: i64) -> i64 {
67 let pb: *i64 = sys_mmap(16); sys_pipe2(pb, 0)
68 let rfd: i64 = pb[0] & 0xFFFFFFFF; let wfd: i64 = (pb[0] >> 32) & 0xFFFFFFFF
69 let pid: i64 = sys_fork()
70 if pid==0 { sys_dup3(wfd, 1, 0); sys_close(rfd); sys_close(wfd); sys_execve(path, argv, 0 as *i64); sys_exit(127); return 0 }
71 sys_close(wfd)
72 var total: i64=0; var go: i64=1
73 while go==1 { let k: i64=sys_read(rfd, out+total as i64, cap-total); if k<=0 { go=0 } else { total=total+k; if total>=cap { go=0 } } }
74 sys_close(rfd); let st: *i64=sys_mmap(8); sys_wait4(pid, st, 0); return total
75}
76
77func sh_exec_one(cmdline: *u8) -> i64 {
78 let cp: *u8 = sys_mmap(256); let av: *i64 = sys_mmap(32*8) as *i64
79 let ac: i64 = sh_tokenize(cmdline, cp, av, 32)
80 if ac==0 { return 0 }
81 let b: i64 = sh_builtin(av, ac)
82 if b != (0-1) { return b }
83 return sh_run(av[0] as *u8, av)
84}
85
86func sh_split_ops(line: *u8, copy: *u8, segp: *i64, sego: *i64, maxseg: i64) -> i64 {
87 var i: i64=0; var segc: i64=1
88 segp[0]=copy as i64; sego[0]=0
89 while line[i]!=(0 as u8) {
90 let c: i64 = line[i] as i64
91 if c==38 { if (line[i+1] as i64)==38 { copy[i]=0 as u8; copy[i+1]=0 as u8; if segc<maxseg { segp[segc]=(copy+(i+2) as i64) as i64; sego[segc]=1; segc=segc+1 } i=i+2 } else { copy[i]=c as u8; i=i+1 } }
92 else { if c==124 { if (line[i+1] as i64)==124 { copy[i]=0 as u8; copy[i+1]=0 as u8; if segc<maxseg { segp[segc]=(copy+(i+2) as i64) as i64; sego[segc]=2; segc=segc+1 } i=i+2 } else { copy[i]=c as u8; i=i+1 } }
93 else { if c==59 { copy[i]=0 as u8; if segc<maxseg { segp[segc]=(copy+(i+1) as i64) as i64; sego[segc]=0; segc=segc+1 } i=i+1 }
94 else { copy[i]=c as u8; i=i+1 } } }
95 }
96 copy[i]=0 as u8; return segc
97}
98func sh_exec_line(line: *u8) -> i64 {
99 let copy: *u8 = sys_mmap(512); let segp: *i64 = sys_mmap(16*8) as *i64; let sego: *i64 = sys_mmap(16*8) as *i64
100 let nseg: i64 = sh_split_ops(line, copy, segp, sego, 16)
101 var prev: i64=0; var s: i64=0
102 while s<nseg {
103 let op: i64=sego[s]; var run: i64=1
104 if op==1 { if prev!=0 { run=0 } }
105 if op==2 { if prev==0 { run=0 } }
106 if run==1 { prev=sh_exec_one(segp[s] as *u8) }
107 s=s+1
108 }
109 return prev
110}
111
112// split a pipeline segment on single '|' into stage command-lines (NUL-terminated in `copy`). returns nstages.
113func sh_split_pipe(line: *u8, copy: *u8, stages: *i64, maxst: i64) -> i64 {
114 var i: i64=0; var nst: i64=1
115 stages[0]=copy as i64
116 while line[i]!=(0 as u8) { let c: i64=line[i] as i64; if c==124 { copy[i]=0 as u8; if nst<maxst { stages[nst]=(copy+(i+1) as i64) as i64; nst=nst+1 } } else { copy[i]=c as u8 } i=i+1 }
117 copy[i]=0 as u8; return nst
118}
119// run ONE pipeline stage in the current (child) process after stdin/stdout are wired; never returns (exits).
120func sh_stage_exec(stage_line: *u8) -> i64 {
121 let cp: *u8 = sys_mmap(256); let av: *i64 = sys_mmap(32*8) as *i64
122 let ac: i64 = sh_tokenize(stage_line, cp, av, 32)
123 if ac==0 { sys_exit(0); return 0 }
124 if sh_streq(av[0] as *u8, "grep" as *u8)==1 { sh_grep_stream(av[1] as *u8); sys_exit(0); return 0 }
125 let b: i64 = sh_builtin(av, ac); if b != (0-1) { sys_exit(b); return 0 }
126 sys_execve(av[0] as *u8, av, 0 as *i64); sys_exit(127); return 0
127}
128// run an N-stage pipeline (stages joined by '|'); stage1.stdout->stage2.stdin->... ; returns last stage exit code.
129func sh_run_pipeline(segment: *u8) -> i64 {
130 let copy: *u8 = sys_mmap(512); let stages: *i64 = sys_mmap(8*8) as *i64
131 let nst: i64 = sh_split_pipe(segment, copy, stages, 8)
132 if nst==1 { return sh_exec_one(stages[0] as *u8) }
133 let prd: *i64 = sys_mmap(8*8) as *i64; let pwr: *i64 = sys_mmap(8*8) as *i64
134 var p: i64=0
135 while p<nst-1 { let pb: *i64=sys_mmap(16); sys_pipe2(pb,0); prd[p]=pb[0]&0xFFFFFFFF; pwr[p]=(pb[0]>>32)&0xFFFFFFFF; p=p+1 }
136 let pids: *i64 = sys_mmap(8*8) as *i64
137 var i: i64=0
138 while i<nst {
139 let pid: i64=sys_fork()
140 if pid==0 {
141 if i>0 { sys_dup3(prd[i-1],0,0) }
142 if i<nst-1 { sys_dup3(pwr[i],1,0) }
143 var c: i64=0; while c<nst-1 { sys_close(prd[c]); sys_close(pwr[c]); c=c+1 }
144 sh_stage_exec(stages[i] as *u8); sys_exit(127); return 0
145 }
146 pids[i]=pid; i=i+1
147 }
148 var c2: i64=0; while c2<nst-1 { sys_close(prd[c2]); sys_close(pwr[c2]); c2=c2+1 }
149 var last: i64=0; var w: i64=0
150 while w<nst { let st: *i64=sys_mmap(8); sys_wait4(pids[w], st, 0); if w==nst-1 { last=wait_exit_code(st[0]) } w=w+1 }
151 return last
152}
153// the COMPLETE shell-line executor: control flow (; && ||) where each segment is itself a pipeline (|).
154func sh_exec_full(line: *u8) -> i64 {
155 let copy: *u8 = sys_mmap(512); let segp: *i64 = sys_mmap(16*8) as *i64; let sego: *i64 = sys_mmap(16*8) as *i64
156 let nseg: i64 = sh_split_ops(line, copy, segp, sego, 16)
157 var prev: i64=0; var s: i64=0
158 while s<nseg {
159 let op: i64=sego[s]; var run: i64=1
160 if op==1 { if prev!=0 { run=0 } }
161 if op==2 { if prev==0 { run=0 } }
162 if run==1 { prev=sh_run_pipeline(segp[s] as *u8) }
163 s=s+1
164 }
165 return prev
166}