nx_fsops_lib.nx source
↩ module page · 580 lines · 28495 B
1// nx_fsops_lib.nx -- CONSOLIDATED filesystem tool (MCP name: nx_fs, tool #4 of the 15), LIBRARY half.
2// (Source is named nx_fsops because nx_fs.nx is the safety-enveloped file-I/O STDLIB -- a different thing.)
3// READ-ONLY first increment: `read` (bounded file read) + `ls` (typed dir listing). Retires ssh-cat for
4// remote reads per rule 27 (api-first, no shell plumbing).
5//
6// BOUNDARY DEFENSE (rule 12 -- MCP callers are EXTERNAL input): `read` REFUSES any path that matches the
7// secret DENY-LIST: compiled-in default needles (secret/key/token/passw/.pem, matched case-insensitively
8// against the WHOLE path) plus data-driven extras from fs_read_deny.conf (one lowercase needle per line,
9// CWD-relative -- rule 11: policy in data, not code). The tools-api runs where key material lives; an
10// arbitrary-read tool that could return opaque_keys.bin or tools_cap_secret.key would convert a read-cap
11// into a key-theft primitive. Over-blocking is the SAFE failure direction for v1.
12// WRITE/EDIT increment (2026-07-16): fsx_write (ATOMIC tmp+fsync+rename) + fsx_edit (exact-string replace
13// with the Claude-Edit UNIQUENESS contract). Exposed as the SEPARATE tools-api name `nx_fs_write` (its own
14// cap class per knowledge/mcp/exposure_policy.txt: read=broad, write=cap) -- the `nx_fs` name stays read-only.
15// The write DENY is a superset of the read deny (never clobber key material) PLUS the OS device/kernel/
16// firmware namespace via the nx_os_fs seam (rule 26 never-brick BY CONSTRUCTION -- not config-disableable)
17// PLUS the tool-registry escalation surface ("allowlist") PLUS data-driven extras (fs_write_deny.conf).
18// license_tier: ORIGINAL
19import "nx_syscalls.nx"
20import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
21import "nx_vsz_watchdog_core.nx" // vw_read (bounded, procfs-safe) / vw_slen / vw_contains -- proven helpers
22import "nx_os_fs.nx" // osf_write_forbidden -- device/firmware-namespace deny (OS seam, rule 26)
23import "nx_os_proc.nx" // osp_selfpid -- unique atomic-write tmp suffix (no torn tmp under concurrency)
24const FSX_MAGIC_4095: i64 = 4095
25
26const FSX_READ_CAP: i64 = 1048576 // max bytes returned by `read` (truncation is MARKED, never silent)
27const FSX_DENY_CAP: i64 = 8192 // fs_read_deny.conf read cap
28const FSX_PATH_CAP: i64 = 1024 // lowercased path work buffer
29const FSX_DENT_BUF: i64 = 65536 // getdents64 batch buffer (matches the proven vsz/heal sizing)
30const FSX_LS_CAP: i64 = 200 // scale-law: max ls entries EMITTED; true total ALWAYS declared (65KB-dump fix)
31const FSX_RC_ABSENT: i64 = 3 // exit: path absent/unreadable (mirrors nx_fileop's exists convention)
32const FSX_RC_DENIED: i64 = 5 // exit: deny-list refused the read
33const FSX_UPPER_A: i64 = 65 // 'A' (ASCII lowercasing)
34const FSX_UPPER_Z: i64 = 90 // 'Z'
35const FSX_CASE_OFF: i64 = 32 // 'a' - 'A'
36const FSX_ASCII_0: i64 = 48 // '0' (decimal print)
37
38func fsx_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
39// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
40// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
41// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
42// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
43func fsx_putn(v: i64) -> i64 { nxi_out(v); return 0 }
44// lowercase copy of s into out (bounded), returns length
45func fsx_lower(s: *u8, out: *u8, cap: i64) -> i64 {
46 var i: i64 = 0
47 while s[i] != (0 as u8) {
48 if i >= cap - 1 { out[i] = 0 as u8; return i }
49 var c: i64 = s[i] as i64
50 if c >= FSX_UPPER_A { if c <= FSX_UPPER_Z { c = c + FSX_CASE_OFF } }
51 out[i] = c as u8
52 i = i + 1
53 }
54 out[i] = 0 as u8
55 return i
56}
57// exact NUL-terminated string equality
58func fsx_seq(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 0 } return 1 }
59// is `needle` (NUL-terminated, lowercase) contained in lowercase path lp[0..ln)?
60// ---------- compare-and-swap decision (seq1422/seq1456) ----------
61//
62// PURE, and in the LIB on purpose: the decision used to live inside the CLI's
63// main(), where a gate cannot reach it -- which is exactly how it shipped
64// refusing every correct expectation (seq1422). A rule nothing can drive is a
65// rule nothing can prove.
66//
67// tok is the raw argv token (`expect=<n>` / `expect=any` / a bare number);
68// cur is the file's real size. Returns 1 = ALLOW, 0 = REFUSE.
69func fsx_cas_val(tok: *u8) -> *u8 {
70 var i: i64 = 0
71 while tok[i] != (0 as u8) {
72 if tok[i] == (61 as u8) { return ((tok as i64) + i + 1) as *u8 }
73 i = i + 1
74 }
75 return tok
76}
77func fsx_cas_ok(cur: i64, tok: *u8) -> i64 {
78 let v: *u8 = fsx_cas_val(tok)
79 if fsx_seq(v, "any" as *u8) == 1 { return 1 }
80 var n: i64 = 0
81 var i: i64 = 0
82 var got: i64 = 0
83 while v[i] != (0 as u8) {
84 let c: i64 = v[i] as i64
85 if c >= 48 { if c <= 57 { n = n * 10 + (c - 48); got = 1 } }
86 i = i + 1
87 }
88 if got == 0 { return 0 }
89 if n == cur { return 1 }
90 return 0
91}
92
93func fsx_deny_hit(lp: *u8, ln: i64, needle: *u8) -> i64 {
94 let nl: i64 = vw_slen(needle)
95 if nl == 0 { return 0 }
96 return vw_contains(lp, ln, needle, nl)
97}
98// data-driven deny extras: one lowercase needle per line in `conf`; 1 = some line matches the path.
99// Factored out so the read deny (fs_read_deny.conf) and write deny (fs_write_deny.conf) share ONE scanner.
100func fsx_conf_deny(lp: *u8, ln: i64, conf: *u8) -> i64 {
101 let cb: *u8 = sys_mmap(FSX_DENY_CAP)
102 let cn: i64 = vw_read(conf, cb, FSX_DENY_CAP - 1)
103 if cn > 0 {
104 var ls: i64 = 0
105 var i: i64 = 0
106 while i <= cn {
107 var eol: i64 = 0
108 if i == cn { eol = 1 } else { if cb[i] == (10 as u8) { eol = 1 } }
109 if eol == 1 {
110 if i > ls {
111 cb[i] = 0 as u8 // terminate the line in place
112 if fsx_deny_hit(lp, ln, (cb as i64 + ls) as *u8) == 1 { return 1 }
113 }
114 ls = i + 1
115 }
116 i = i + 1
117 }
118 }
119 return 0
120}
121const FSX_SNIFF_CAP: i64 = 4096
122
123func fsx_isalnum(c: i64) -> i64 {
124 if c >= 48 { if c <= 57 { return 1 } }
125 if c >= 97 { if c <= 122 { return 1 } }
126 if c >= 65 { if c <= 90 { return 1 } }
127 return 0
128}
129
130func fsx_ends_with(lp: *u8, ln: i64, suf: *u8) -> i64 {
131 let sl: i64 = vw_slen(suf)
132 if sl == 0 { return 0 }
133 if sl > ln { return 0 }
134 var i: i64 = 0
135 while i < sl {
136 if lp[ln - sl + i] != suf[i] { return 0 }
137 i = i + 1
138 }
139 return 1
140}
141
142func fsx_basename_is(lp: *u8, ln: i64, name: *u8) -> i64 {
143 let nl: i64 = vw_slen(name)
144 if nl == 0 { return 0 }
145 if nl > ln { return 0 }
146 if fsx_ends_with(lp, ln, name) == 0 { return 0 }
147 if nl == ln { return 1 }
148 let c: i64 = lp[ln - nl - 1] as i64
149 if c == 47 { return 1 }
150 if c == 92 { return 1 }
151 return 0
152}
153
154// Whole-word containment: bounded by non-alphanumeric on BOTH sides, so `api_secret.txt` is denied and
155// `secretary_notes.md` is not.
156func fsx_word_has(lp: *u8, ln: i64, w: *u8) -> i64 {
157 let wl: i64 = vw_slen(w)
158 if wl == 0 { return 0 }
159 if wl > ln { return 0 }
160 var i: i64 = 0
161 while i + wl <= ln {
162 var eq: i64 = 1
163 var k: i64 = 0
164 while k < wl { if lp[i + k] != w[k] { eq = 0; k = wl } else { k = k + 1 } }
165 if eq == 1 {
166 var lb: i64 = 1
167 if i > 0 { if fsx_isalnum(lp[i - 1] as i64) == 1 { lb = 0 } }
168 var rb: i64 = 1
169 if i + wl < ln { if fsx_isalnum(lp[i + wl] as i64) == 1 { rb = 0 } }
170 if lb == 1 { if rb == 1 { return 1 } }
171 }
172 i = i + 1
173 }
174 return 0
175}
176
177// CONTENT LEG: sniff the leading bytes for what a secret actually IS. This is the half a name-only list
178// can never do -- it denies a private key no matter what it is called, including `notes.txt`.
179// A CERTIFICATE is deliberately NOT denied: certs are public by definition, and denying them is the same
180// category error as denying the tokenizer.
181func fsx_content_secret(path: *u8) -> i64 {
182 let fd: i64 = sys_openat_rd(path)
183 if fd < 0 { return 0 }
184 let b: *u8 = sys_mmap(FSX_SNIFF_CAP)
185 let n: i64 = sys_read(fd, b, FSX_SNIFF_CAP - 1)
186 sys_close(fd)
187 if n <= 0 { return 0 }
188 if vw_contains(b, n, "PRIVATE KEY-----" as *u8, 16) == 1 { return 1 }
189 if vw_contains(b, n, "OPENSSH PRIVATE KEY" as *u8, 19) == 1 { return 1 }
190 if vw_contains(b, n, "PGP PRIVATE KEY BLOCK" as *u8, 21) == 1 { return 1 }
191 if vw_contains(b, n, "PuTTY-User-Key-File" as *u8, 19) == 1 { return 1 }
192 return 0
193}
194
195// DENY check: 1 = refuse this path. SOTA-2026 REWRITE (2026-07-31).
196//
197// THE OLD RULE WAS WRONG IN BOTH DIRECTIONS, measured on real paths:
198// OVER-BLOCKED substring "token" denied runtime/nx_tokenizer.nx -- the compiler's own tokenizer, which
199// contains no secret -- and blocked BOTH nx_fs read AND nx_fs_write on it, while
200// nx_shelltool grep returned the same bytes freely. It cost real work and bought nothing.
201// substring "key" likewise denies monkey / keyword / keyboard.
202// UNDER-BLOCKED `id_rsa`, the canonical SSH private key filename, contains NONE of
203// secret/key/token/passw/.pem and sailed straight through.
204// A denylist that blocks source and passes private keys is not a security control -- it is a rename away
205// from useless in one direction and a permanent nuisance in the other.
206//
207// REPLACEMENT -- two INDEPENDENT legs, either one denies:
208// (1) PATH leg: real secret-bearing EXTENSIONS and exact BASENAMES, matched at a true suffix/segment
209// boundary, plus whole-word `secret`/`password`. No substring-anywhere matching survives.
210// (2) CONTENT leg: PEM/OpenSSH/PGP/PuTTY private-key armour, which catches a secret regardless of name.
211// Net effect: strictly MORE secrets denied (id_rsa, a renamed key, a key with no extension) and strictly
212// FEWER ordinary sources blocked.
213func fsx_denied(path: *u8) -> i64 {
214 let lp: *u8 = sys_mmap(FSX_PATH_CAP)
215 let ln: i64 = fsx_lower(path, lp, FSX_PATH_CAP)
216
217 if fsx_ends_with(lp, ln, ".pem" as *u8) == 1 { return 1 }
218 if fsx_ends_with(lp, ln, ".key" as *u8) == 1 { return 1 }
219 if fsx_ends_with(lp, ln, ".cap" as *u8) == 1 { return 1 }
220 if fsx_ends_with(lp, ln, ".p12" as *u8) == 1 { return 1 }
221 if fsx_ends_with(lp, ln, ".pfx" as *u8) == 1 { return 1 }
222 if fsx_ends_with(lp, ln, ".jks" as *u8) == 1 { return 1 }
223 if fsx_ends_with(lp, ln, ".ppk" as *u8) == 1 { return 1 }
224 if fsx_ends_with(lp, ln, "_rsa" as *u8) == 1 { return 1 }
225 if fsx_ends_with(lp, ln, "_dsa" as *u8) == 1 { return 1 }
226 if fsx_ends_with(lp, ln, "_ecdsa" as *u8) == 1 { return 1 }
227 if fsx_ends_with(lp, ln, "_ed25519" as *u8) == 1 { return 1 }
228
229 if fsx_basename_is(lp, ln, ".env" as *u8) == 1 { return 1 }
230 if fsx_basename_is(lp, ln, "credentials" as *u8) == 1 { return 1 }
231 if fsx_basename_is(lp, ln, "shadow" as *u8) == 1 { return 1 }
232 if fsx_basename_is(lp, ln, "opaque_keys.bin" as *u8) == 1 { return 1 }
233
234 // CALIBRATED BY WORD FREQUENCY, not by one uniform rule -- the gate proved a uniform rule wrong in
235 // BOTH directions within minutes. `secret` and `passw` are high-signal and essentially absent from
236 // ordinary source, so SUBSTRING matching is correct for them and catches mysecret_key.bin. `key` and
237 // `token` are common English fragments (tokenizer, monkey, keyword, keyboard) and must NEVER be
238 // substring-matched -- that is what denied the compiler's own tokenizer. They are covered instead by
239 // the extension/suffix rules above and by the content leg below.
240 if fsx_deny_hit(lp, ln, "secret" as *u8) == 1 { return 1 }
241 if fsx_deny_hit(lp, ln, "passw" as *u8) == 1 { return 1 }
242 if fsx_deny_hit(lp, ln, "credential" as *u8) == 1 { return 1 }
243
244 if fsx_content_secret(path) == 1 { return 1 }
245
246 return fsx_conf_deny(lp, ln, "fs_read_deny.conf" as *u8)
247}
248// read: emit up to `cap` bytes of path to stdout. Returns bytes emitted; -1 absent; -2 DENIED.
249// deniedp/absent are ALSO visible in the CLI exit code. Truncation is marked with a trailing banner.
250// Failure reporter that KEEPS THE ERRNO. sys_openat_rd returns -errno, and the old message printed
251// "ABSENT" for every negative -- so EACCES (-13, EXISTS but unopenable) read as "missing", which are
252// OPPOSITE remedies. Cost a real hour on 2026-08-01: knowledge/foundation existed with mode 0100 and
253// every instrument in the stack called it absent (the mkdirp read-back that printed the errno cracked
254// the case in one call). rc>=0 means a probe re-open SUCCEEDED: the earlier read failed for a
255// non-open reason (an empty file), so say THAT. Always returns -1 (callers' contract unchanged;
256// the -2 DENIED sentinel stays distinct).
257func fsx_fail(path: *u8, rc: i64) -> i64 {
258 if rc >= 0 { sys_close(rc); fsx_puts("NX-FS EMPTY: 0 bytes: " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 }
259 if rc == 0 - 13 {
260 fsx_puts("NX-FS PERMISSION (EACCES): exists but this process may not open it: " as *u8)
261 fsx_puts(path); fsx_puts("\n" as *u8)
262 return 0 - 1
263 }
264 if rc == 0 - 2 { fsx_puts("NX-FS ABSENT: " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 }
265 fsx_puts("NX-FS ERROR rc=" as *u8); fsx_putn(rc)
266 fsx_puts(": " as *u8); fsx_puts(path); fsx_puts("\n" as *u8)
267 return 0 - 1
268}
269
270func fsx_read(path: *u8, cap: i64) -> i64 {
271 if fsx_denied(path) == 1 {
272 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
273 return 0 - (2 as i64) // DENIED sentinel (distinct from -1 absent)
274 }
275 var want: i64 = cap
276 if want <= 0 { want = FSX_READ_CAP }
277 if want > FSX_READ_CAP { want = FSX_READ_CAP }
278 let buf: *u8 = sys_mmap(want + 1)
279 let n: i64 = vw_read(path, buf, want)
280 // vw_read flattens the errno (-1 for every failure); re-probe the open ONLY on the failure path
281 // so the message can distinguish absent / permission / empty. Zero cost on success.
282 if n <= 0 { return fsx_fail(path, sys_openat_rd(path)) }
283 sys_write(1, buf, n)
284 if n == want {
285 fsx_puts("\n[NX-FS TRUNCATED at " as *u8); fsx_putn(n); fsx_puts(" bytes]\n" as *u8)
286 }
287 return n
288}
289// WINDOWED read (eats debt seq222: the tools-call transport caps ~64KB, so files past the cap were
290// unreadable over MCP): emit up to `cap` bytes starting at byte `off`. Same deny-list as fsx_read.
291// A separate function (NOT an fsx_read arity change) so every existing caller keeps its exact contract.
292func fsx_read_at(path: *u8, cap: i64, off: i64) -> i64 {
293 if fsx_denied(path) == 1 {
294 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
295 return 0 - (2 as i64)
296 }
297 var want: i64 = cap
298 if want <= 0 { want = FSX_READ_CAP }
299 if want > FSX_READ_CAP { want = FSX_READ_CAP }
300 let fd: i64 = sys_openat_rd(path)
301 if fd < 0 { return fsx_fail(path, fd) }
302 if off > 0 { if sys_lseek(fd, off, 0) < 0 { sys_close(fd); fsx_puts("NX-FS ABSENT: seek failed " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 } }
303 let buf: *u8 = sys_mmap(want + 1)
304 var got: i64 = 0
305 var sc: i64 = 1
306 while sc == 1 {
307 let r: i64 = sys_read(fd, ((buf as i64 + got) as *u8), want - got)
308 if r <= 0 { sc = 0 } else { got = got + r; if got >= want { sc = 0 } }
309 }
310 sys_close(fd)
311 if got <= 0 { fsx_puts("NX-FS EOF: no bytes at offset " as *u8); fsx_putn(off); fsx_puts(" in " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 }
312 sys_write(1, buf, got)
313 if got == want {
314 fsx_puts("\n[NX-FS WINDOW off=" as *u8); fsx_putn(off); fsx_puts(" n=" as *u8); fsx_putn(got); fsx_puts(" -- more remains]\n" as *u8)
315 }
316 return got
317}
318const FSX_LINES_SCAN: i64 = 1048576 // line-addressing scan window (matches the proven read cap)
319const FSX_LINES_MAXOUT: i64 = 262144 // max bytes emitted by one `lines` call (transport-friendly)
320const FSX_LINES_DEFN: i64 = 40 // default line count when the caller omits it
321const FSX_LINES_MAXN: i64 = 400 // max lines per call
322
323// LINE-ADDRESSED read -- THE MISSING PRIMITIVE (measured 2026-07-20): `grep` reports file:LINE but `read`
324// takes BYTES, so the two did NOT compose -- locating one function in a remote file meant hand
325// binary-searching byte offsets (cost one subagent 70K tokens + 22 calls for a single extraction).
326// Emits lines [start, start+count) 1-based, then a DECLARED envelope banner (scale-law: a caller can
327// NEVER be silently windowed -- scanned bytes, scan cap, over-window and clip flags are all stated).
328// Same deny-list as fsx_read. Returns bytes emitted; -1 absent; -2 DENIED.
329func fsx_read_lines(path: *u8, start: i64, count: i64) -> i64 {
330 if fsx_denied(path) == 1 {
331 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
332 return 0 - (2 as i64)
333 }
334 var s: i64 = start
335 if s < 1 { s = 1 }
336 var c: i64 = count
337 if c <= 0 { c = FSX_LINES_DEFN }
338 if c > FSX_LINES_MAXN { c = FSX_LINES_MAXN }
339 let buf: *u8 = sys_mmap(FSX_LINES_SCAN + 1)
340 let n: i64 = vw_read(path, buf, FSX_LINES_SCAN)
341 if n <= 0 { return fsx_fail(path, sys_openat_rd(path)) }
342 // walk to the first byte of line `s`; cur > s afterwards means we ran off the end (fail-loud, not empty)
343 var i: i64 = 0
344 var cur: i64 = 1
345 while cur < s {
346 if i >= n { cur = s + 1 } else {
347 if buf[i] == (10 as u8) { cur = cur + 1 }
348 i = i + 1
349 }
350 }
351 if cur > s {
352 fsx_puts("NX-FS LINES: start line " as *u8); fsx_putn(s)
353 fsx_puts(" is beyond EOF (scanned " as *u8); fsx_putn(n); fsx_puts(" bytes)\n" as *u8)
354 return 0
355 }
356 let from: i64 = i
357 var lines_out: i64 = 0
358 var j: i64 = i
359 var go: i64 = 1
360 while go == 1 {
361 if j >= n { go = 0 } else {
362 if buf[j] == (10 as u8) {
363 lines_out = lines_out + 1
364 j = j + 1
365 if lines_out >= c { go = 0 }
366 } else { j = j + 1 }
367 }
368 }
369 var outn: i64 = j - from
370 var clipped: i64 = 0
371 if outn > FSX_LINES_MAXOUT { outn = FSX_LINES_MAXOUT; clipped = 1 }
372 if outn > 0 { sys_write(1, ((buf as i64 + from) as *u8), outn) }
373 fsx_puts("\n[NX-FS LINES start=" as *u8); fsx_putn(s)
374 fsx_puts(" lines=" as *u8); fsx_putn(lines_out)
375 fsx_puts(" next=" as *u8); fsx_putn(s + lines_out)
376 fsx_puts(" bytes=" as *u8); fsx_putn(outn)
377 fsx_puts(" scanned=" as *u8); fsx_putn(n)
378 fsx_puts(" scan_cap=" as *u8); fsx_putn(FSX_LINES_SCAN)
379 if n >= FSX_LINES_SCAN { fsx_puts(" FILE-EXCEEDS-SCAN-WINDOW" as *u8) }
380 if clipped == 1 { fsx_puts(" BYTE-CLIPPED" as *u8) }
381 fsx_puts("]\n" as *u8)
382 return outn
383}
384// ==== WRITE/EDIT half (cap class: write; tools-api name nx_fs_write) =========================
385const FSX_MODE_RW: i64 = 0x1a4 // 0644 -- the ecosystem's file-create mode idiom
386const FSX_DEC: i64 = 10 // decimal base (pid rendering in the tmp suffix)
387const FSX_EDIT_OUT: i64 = 2097152 // edit output buffer (2x read cap: bounded replacement growth)
388const FSX_TMP_ROOM: i64 = 32 // reserved room for ".nxw" + pid digits + NUL in the tmp name
389const FSX_RC_IO: i64 = 4 // exit: io failure (open/short-write/rename)
390const FSX_RC_NOMATCH: i64 = 6 // exit: edit found 0 occurrences (file UNCHANGED)
391const FSX_RC_AMBIG: i64 = 7 // exit: edit found >1 occurrences without `all` (file UNCHANGED)
392
393// write-DENY: read deny (never clobber key material) + OS device/firmware namespace (rule 26, seam,
394// BY CONSTRUCTION) + registry-escalation needle + fs_write_deny.conf extras (data-driven).
395func fsx_write_denied(path: *u8) -> i64 {
396 if fsx_denied(path) == 1 { return 1 }
397 if osf_write_forbidden(path) == 1 { return 1 }
398 let lp: *u8 = sys_mmap(FSX_PATH_CAP)
399 let ln: i64 = fsx_lower(path, lp, FSX_PATH_CAP)
400 if fsx_deny_hit(lp, ln, "allowlist" as *u8) == 1 { return 1 }
401 return fsx_conf_deny(lp, ln, "fs_write_deny.conf" as *u8)
402}
403// ATOMIC full-file write: content lands via <path>.nxw<pid> + fsync + rename, so a reader NEVER sees a
404// torn file and concurrent writers each land whole (last rename wins; pid suffix = no shared tmp).
405// Returns bytes written; -2 DENIED; -3 io error (path too long / open / short write / rename).
406func fsx_write(path: *u8, body: *u8, blen: i64) -> i64 {
407 if fsx_write_denied(path) == 1 {
408 fsx_puts("NX-FS DENIED: write refused (secret/device-namespace/allowlist deny)\n" as *u8)
409 return 0 - (2 as i64)
410 }
411 let plen: i64 = vw_slen(path)
412 if plen + FSX_TMP_ROOM >= FSX_PATH_CAP { return 0 - (3 as i64) }
413 let tmp: *u8 = sys_mmap(FSX_PATH_CAP)
414 var i: i64 = 0
415 while i < plen { tmp[i] = path[i]; i = i + 1 }
416 let suf: *u8 = ".nxw" as *u8
417 var s: i64 = 0
418 while suf[s] != (0 as u8) { tmp[i] = suf[s]; i = i + 1; s = s + 1 }
419 var pid: i64 = osp_selfpid()
420 if pid < 0 { pid = 0 }
421 if pid == 0 { tmp[i] = FSX_ASCII_0 as u8; i = i + 1 } else {
422 let ds: *u8 = sys_mmap(FSX_TMP_ROOM)
423 var k: i64 = 0
424 while pid > 0 { ds[k] = (FSX_ASCII_0 + (pid % FSX_DEC)) as u8; pid = pid / FSX_DEC; k = k + 1 }
425 while k > 0 { tmp[i] = ds[k-1]; i = i + 1; k = k - 1 }
426 }
427 tmp[i] = 0 as u8
428 let fd: i64 = sys_openat_wr(tmp, FSX_MODE_RW)
429 if fd < 0 { return 0 - (3 as i64) }
430 var off: i64 = 0
431 while off < blen {
432 let w: i64 = sys_write(fd, ((body as i64 + off) as *u8), blen - off)
433 if w <= 0 { sys_close(fd); return 0 - (3 as i64) }
434 off = off + w
435 }
436 sys_fsync(fd)
437 sys_close(fd)
438 // PRESERVE the original file's mode across tmp+rename (debt eaten 2026-07-18: an edit of an
439 // executable script used to land 0644 -- the exec bit vanished and the cron runner broke with
440 // rc=126). st_mode = u32 at stat offset 24; keep the permission bits (low 12) only.
441 let sb: *u8 = sys_mmap(160)
442 if sys_fstatat(path, sb) == 0 {
443 let m0: i64 = sb[24] as i64
444 let m1: i64 = sb[25] as i64
445 let om: i64 = (m0 + (m1 * 256)) & FSX_MAGIC_4095
446 if om != FSX_MODE_RW { nx_chmod(tmp, om) }
447 }
448 if sys_renameat(tmp, path) < 0 { return 0 - (3 as i64) }
449 return blen
450}
451// count non-overlapping occurrences of nee[0..nl) in hay[0..hn)
452func fsx_count_occ(hay: *u8, hn: i64, nee: *u8, nl: i64) -> i64 {
453 if nl <= 0 { return 0 }
454 var c: i64 = 0
455 var i: i64 = 0
456 while i + nl <= hn {
457 var m: i64 = 1
458 var j: i64 = 0
459 while j < nl { if hay[i+j] != nee[j] { m = 0; j = nl } else { j = j + 1 } }
460 if m == 1 { c = c + 1; i = i + nl } else { i = i + 1 }
461 }
462 return c
463}
464// replace occurrences of nee with rep into out (allf=0: first only; 1: all). Returns new length; -1 overflow.
465func fsx_replace(hay: *u8, hn: i64, nee: *u8, nl: i64, rep: *u8, rl: i64, out: *u8, ocap: i64, allf: i64) -> i64 {
466 var o: i64 = 0
467 var i: i64 = 0
468 var used: i64 = 0
469 while i < hn {
470 var m: i64 = 0
471 if i + nl <= hn { if nl > 0 {
472 var ok: i64 = 1
473 if allf == 0 { if used == 1 { ok = 0 } }
474 if ok == 1 {
475 m = 1
476 var j: i64 = 0
477 while j < nl { if hay[i+j] != nee[j] { m = 0; j = nl } else { j = j + 1 } }
478 }
479 } }
480 if m == 1 {
481 if o + rl > ocap { return 0 - 1 }
482 var k: i64 = 0
483 while k < rl { out[o] = rep[k]; o = o + 1; k = k + 1 }
484 i = i + nl
485 used = 1
486 } else {
487 if o + 1 > ocap { return 0 - 1 }
488 out[o] = hay[i]
489 o = o + 1
490 i = i + 1
491 }
492 }
493 return o
494}
495// EDIT: exact-string replace with the UNIQUENESS contract (the Claude-Edit SOTA semantic):
496// 0 matches -> -6 NOMATCH (file untouched); >1 without allf -> -7 AMBIGUOUS (file untouched);
497// otherwise replace (allf=1: every occurrence) and land ATOMICALLY via fsx_write.
498// Returns new byte length; -1 absent; -2 DENIED; -3 io/overflow; -6 nomatch; -7 ambiguous.
499func fsx_edit(path: *u8, olds: *u8, news: *u8, allf: i64) -> i64 {
500 if fsx_write_denied(path) == 1 {
501 fsx_puts("NX-FS DENIED: edit refused (secret/device-namespace/allowlist deny)\n" as *u8)
502 return 0 - (2 as i64)
503 }
504 let buf: *u8 = sys_mmap(FSX_READ_CAP + 1)
505 let n: i64 = vw_read(path, buf, FSX_READ_CAP)
506 if n <= 0 { return 0 - 1 }
507 if n == FSX_READ_CAP { return 0 - (3 as i64) } // file at/over the edit cap: refuse rather than corrupt
508 let ol: i64 = vw_slen(olds)
509 let cnt: i64 = fsx_count_occ(buf, n, olds, ol)
510 if cnt == 0 { return 0 - FSX_RC_NOMATCH }
511 if cnt > 1 { if allf == 0 { return 0 - FSX_RC_AMBIG } }
512 let out: *u8 = sys_mmap(FSX_EDIT_OUT)
513 let nn: i64 = fsx_replace(buf, n, olds, ol, news, vw_slen(news), out, FSX_EDIT_OUT, allf)
514 if nn < 0 { return 0 - (3 as i64) }
515 let w: i64 = fsx_write(path, out, nn)
516 if w < 0 { return w }
517 return nn
518}
519
520// ls: one entry per line "<t> <name>" (t: d=dir f=file l=link o=other; . and .. skipped).
521// Returns entry count; -1 if the dir cannot be opened.
522// PAGING (2026-08-05). The cap was always honest -- it declared total= and truncated=1 -- but an
523// honest refusal is not access: knowledge/status/ holds 1027 entries, so 827 of them were simply
524// UNREACHABLE through this tool, and a worker that listed it reported "queue empty" over a job that
525// was sitting right there. u2605u2605u2605u2605u2605DECLARING A TRUNCATION IS NOT THE SAME AS OFFERING A WAY PAST IT --
526// a loud cap with no next page is still a wall. `skip` is that way past.
527// Contract preserved exactly (rule 19): fsx_ls(dir) keeps its old signature and behaviour.
528func fsx_ls(dir: *u8) -> i64 { return fsx_ls_from(dir, 0) }
529
530func fsx_ls_from(dir: *u8, skip: i64) -> i64 {
531 let fd: i64 = sys_openat_rd(dir)
532 if fd < 0 { return fsx_fail(dir, fd) }
533 let dbuf: *u8 = sys_mmap(FSX_DENT_BUF)
534 var cnt: i64 = 0
535 var shown: i64 = 0
536 var run: i64 = 1
537 while run == 1 {
538 let n: i64 = sys_getdents64(fd, dbuf, FSX_DENT_BUF)
539 if n <= 0 { run = 0 } else {
540 var off: i64 = 0
541 while off < n {
542 let rec: *u8 = ((dbuf as i64 + off) as *u8)
543 let reclen: i64 = dirent_reclen(rec)
544 if reclen <= 0 { off = n } else {
545 let name: *u8 = dirent_name(rec)
546 // skip "." and ".."
547 var isdot: i64 = 0
548 if fsx_seq(name, "." as *u8) == 1 { isdot = 1 }
549 if fsx_seq(name, ".." as *u8) == 1 { isdot = 1 }
550 if isdot == 0 {
551 if cnt >= skip { if shown < FSX_LS_CAP {
552 let t: i64 = dirent_type(rec)
553 if t == DT_DIR { fsx_puts("d " as *u8) } else {
554 if t == DT_REG { fsx_puts("f " as *u8) } else {
555 if t == DT_LNK { fsx_puts("l " as *u8) } else { fsx_puts("o " as *u8) } } }
556 fsx_puts(name)
557 fsx_puts("\n" as *u8)
558 shown = shown + 1
559 } }
560 cnt = cnt + 1
561 }
562 off = off + reclen
563 }
564 }
565 }
566 }
567 sys_close(fd)
568 // SCALE-LAW: cap the emitted list but ALWAYS declare the true total; truncation is LOUD not silent
569 fsx_puts("NX-FS-LS skip=" as *u8)
570 fsx_putn(skip)
571 fsx_puts(" shown=" as *u8)
572 fsx_putn(shown)
573 fsx_puts(" total=" as *u8)
574 fsx_putn(cnt)
575 // u26a0THE OLD PREDICATE (cnt > shown) BECOMES A LIE THE MOMENT skip EXISTS: the LAST page would
576 // still report truncated=1 forever, so a caller paging until truncated=0 would never stop.
577 // What actually remains is everything past the window just emitted.
578 if cnt > skip + shown { fsx_puts(" truncated=1 (more remain -- next page: ls <dir> " as *u8); fsx_putn(skip + shown); fsx_puts(")\n" as *u8) } else { fsx_puts(" truncated=0\n" as *u8) }
579 return cnt
580}