nx_nxwreap_lib.nx source
↩ module page · 429 lines · 21486 B
1// nx_nxwreap_lib.nx -- THE CLASSIFIER for LEAKED WRITE-GUARD SCRATCH FILES, and the ONE place the
2// decision to remove one is taken. No `main`: the CLI (nx_nxwreap) and the gate (nx_nxwreap_gate)
3// both import THIS, so the reaper and its proof cannot disagree about what a scratch file is.
4//
5// ---------------------------------------------------------------------------------------------
6// THE DEFECT, MEASURED 2026-08-25.
7// nx_fs_write lands every atomic write through a temp named <path>.nxw<pid> and renames it away on
8// success (nx_fsops_lib.fsx_write). BOTH of its failure paths -- a short write and a failed rename
9// -- ALREADY unlink that temp, so an ordinary FAILED write leaves nothing behind. What is left is
10// the single case in which no code of ours runs at all: a writer KILLED between the open and the
11// rename. Eleven agents were killed mid-write on this estate today by spend/session/weekly limits,
12// and `nx_shelltool glob \"*.nxw*\" buildroot/runtime` returned matches=192 corpus_complete=1.
13// THE INFLOW FOR *FAILED* WRITES IS ALREADY CLOSED. THIS ORGAN EXISTS FOR THE *KILLED* WRITER,
14// WHICH NO amount OF CLEANUP CODE IN THE WRITER CAN EVER REACH.
15//
16// THE HARM IS NOT THE DISK, IT IS THE CENSUS. A scratch file is a FULL COPY OF A SOURCE FILE lying
17// beside that source file, so `nx_shelltool find <name> buildroot/runtime` returns the fossils --
18// `find nx_organ_ship` returned 9 rows of which 5 were .nxw* -- and any name or content scan that
19// does not filter by extension can read PRESENT off a fossil after the real file was already fixed.
20// A STALE FULL COPY BESIDE THE FILE IT COPIES IS A FALSE-NEGATIVE-ON-ABSENCE GENERATOR.
21// BOUNDED HONESTLY, because the scope of a claim is part of the claim: the EXT-FILTERED path is NOT
22// affected. `nx_absent <pattern> <dir> nx` cannot match `....nxw12848`, since that name does not end
23// in `.nx`. The blast radius is every UNFILTERED name/content scan, and only those.
24//
25// ---------------------------------------------------------------------------------------------
26// FAIL-SAFE BY CONSTRUCTION, INHERITED RATHER THAN RE-STATED.
27// The predicate is nx_lock_reap_core.lr_should_reap -- reap iff the file EXISTS AND its owner is NOT
28// alive AND it is older than the threshold; everything else is 0. It is NOT re-implemented here.
29// This organ contributes exactly one thing the lock reaper cannot: the OWNER IS IN THE FILENAME.
30//
31// WHY /proc/<pid> AND NOT lr_owner_alive's CMDLINE NEEDLE. The lock reaper identifies an owner by
32// scanning every /proc/<pid>/cmdline for a substring, because a lock file does not say who holds it.
33// A write-guard scratch file DOES say: the pid is its own suffix. Testing /proc/<pid> directly is
34// one stat instead of a whole-/proc walk, and it is exact rather than a substring guess.
35//
36// PID REUSE IS REAL, AND IT IS RESOLVED IN THE SAFE DIRECTION. A dead writer's pid can be recycled
37// by an unrelated process, and nothing in the filename can distinguish that from the original writer
38// still running. So: IF /proc/<pid> EXISTS WE DO NOT REAP, even though the true owner may be long
39// dead. That leaves some litter -- the HARMLESS failure. The other direction unlinks the in-flight
40// temp of a LIVE writer, destroying a write that was about to succeed -- the HARMFUL one.
41// WHERE A GUARD MUST BE WRONG, MAKE IT WRONG TOWARD DOING NOTHING.
42// The cost is bounded and NOW MEASURED, not estimated. Over the full live population on 2026-08-25
43// -- 192 scratch files, corpus_complete=1, owner-liveness checked for EVERY one against /proc, no
44// sampling -- exactly TWO carried a pid that was live: 4552 and 17945. Reading their /proc/<pid>/comm
45// showed `kworker/u16:1` and `kworker/4:0`. BOTH ARE KERNEL WORKER THREADS, so neither can possibly
46// have written `nx_docportal_admin_daemon.nx.nxw4552` or `nx_build_admit.nx.nxw17945`: both are pid
47// reuse, caught in the act. The guard therefore retains 2 of 192 reapable files -- 10 permil -- and
48// destroys none. That is the trade priced, in the safe direction.
49//
50// THE EXACT FIX EXISTS AND IS DELIBERATELY NOT IMPLEMENTED HERE. /proc/<pid>/stat field 22 is the
51// process start time in clock ticks since boot; combined with `btime` from /proc/stat it yields an
52// absolute start epoch, and A PROCESS THAT STARTED AFTER THE FILE WAS WRITTEN CANNOT BE ITS WRITER.
53// That would resolve reuse exactly and drop the 10 permil to zero. It is left as a named contract
54// rather than written blind because it needs a USER_HZ assumption and a live experiment to settle,
55// and adding untested arithmetic to the one path whose whole job is to refuse would be a correction
56// shipped without the experiment it demands. Litter is retried on every run; a destroyed write is not.
57//
58// ---------------------------------------------------------------------------------------------
59// THE AGE THRESHOLD IS DERIVED FROM TWO SIBLING CALIBRATIONS, NOT PICKED.
60// (a) nx_organ_ship.nx OS_TIMEOUT_MS = 900000 ms -- the ship loop's own declared bound on a single
61// in-flight subprocess operation (\"a build under admit backoff can legitimately wait minutes\").
62// nw_ship_bound_s() DERIVES 900 s from it rather than carrying a second copy of the number.
63// (b) nx_jobclaim_lib.nx JR_DEFAULT_MAX_AGE_SEC = 3600 s -- the estate's existing answer to the
64// SAME question shape: how old must an artifact left behind by a process that never came back
65// be, before it is provably abandoned.
66// NW_DEFAULT_MAX_AGE_SEC ADOPTS (b), because 3600 >= 900 satisfies BOTH bounds at once and reusing a
67// sibling's calibration beats inventing a budget. A gate tooth asserts that relation arithmetically,
68// so lowering this below the ship-loop bound turns the gate RED instead of passing silently.
69// Extra margin is not waste here: on a saturated array (this estate has measured btrfs D-state
70// stalls and load 16-19) a single fsync can genuinely block for minutes, and the whole point of
71// the threshold is to outlive the slowest legitimate write, never to be tight.
72// IT IS ITS OWN CONF KEY, DELIBERATELY NOT SHARED WITH jobclaim_maxage.conf: one constant serving
73// two unrelated purposes can never be tuned for either.
74//
75// license_tier: ORIGINAL
76import "nx_syscalls.nx"
77import "nx_lock_reap_core.nx"
78
79// ---- THE NAME CONTRACT. Spelled once; the producer is nx_fsops_lib.fsx_write. -----------------
80const NW_SUFFIX: *u8 = ".nxw" as *u8
81const NW_SUF_LEN: i64 = 4
82const NW_PROC: *u8 = "/proc/" as *u8
83const NW_SLASH: i64 = 47
84const NW_CH_ZERO: i64 = 48
85const NW_CH_NINE: i64 = 57
86const NW_B10: i64 = 10
87// Linux pid_max is at most 2^22 = 7 digits. 10 is a generous ceiling that still makes an i64
88// overflow impossible on a hostile name, and a run longer than it is REFUSED rather than truncated.
89const NW_PID_MAX_DIGITS: i64 = 10
90
91// ---- NAMED REFUSALS. NEVER 0: pid 0 is the kernel swapper, a real pid, so a caller that read 0 as
92// \"no pid\" would be reading a live process id as an absence. Both refusals are NEGATIVE. --------
93const NW_PID_NOT_NXW: i64 = 0 - 1
94const NW_PID_MALFORMED: i64 = 0 - 2
95
96// ---- THE DECISIONS. Exactly one per examined entry, each naming its own rule. -----------------
97const NW_D_REAP: i64 = 0
98const NW_D_LIVE_OWNER: i64 = 1
99const NW_D_TOO_YOUNG: i64 = 2
100const NW_D_MALFORMED: i64 = 3
101const NW_D_VANISHED: i64 = 4
102const NW_D_NOT_REGULAR: i64 = 5
103const NW_D_PATHLONG: i64 = 6
104const NW_D_NOT_NXW: i64 = 7
105
106// ---- COUNTER LAYOUT. Slots 1..8 are a PARTITION over every entry the scan examined and MUST sum
107// to NW_C_TOTAL. Slots 9..11 are a SEPARATE AXIS over the reap bucket alone -- folding them into the
108// partition would double-count, and a new bucket that overlaps an existing partition must be
109// declared an axis rather than a member. NW_C_DIRS is neither: it counts directories, not entries.
110const NW_C_TOTAL: i64 = 0
111const NW_C_REAP: i64 = 1
112const NW_C_LIVE_OWNER: i64 = 2
113const NW_C_TOO_YOUNG: i64 = 3
114const NW_C_MALFORMED: i64 = 4
115const NW_C_VANISHED: i64 = 5
116const NW_C_NOT_REGULAR: i64 = 6
117const NW_C_PATHLONG: i64 = 7
118const NW_C_NOT_NXW: i64 = 8
119const NW_C_UNLINKED: i64 = 9
120const NW_C_UNLINK_FAIL: i64 = 10
121const NW_C_WOULD: i64 = 11
122const NW_C_DIRS: i64 = 12
123const NW_C_SLOTS: i64 = 13
124
125// info[] so a caller can PRINT THE VALUES and not merely a verdict.
126const NW_I_PID: i64 = 0
127const NW_I_AGE: i64 = 1
128const NW_I_ALIVE: i64 = 2
129const NW_I_SLOTS: i64 = 3
130
131const NW_I64_BYTES: i64 = 8
132const NW_DENTBUF: i64 = 65536
133const NW_PATHBUF: i64 = 4096
134const NW_PIDBUF: i64 = 64
135const NW_CONFBUF: i64 = 256
136
137// ---- THE THRESHOLD, DERIVED. See the header for why 3600 and not 900. ------------------------
138const NW_SHIP_TIMEOUT_MS: i64 = 900000 // == nx_organ_ship.nx OS_TIMEOUT_MS, cited by name
139const NW_MS_PER_S: i64 = 1000
140const NW_DEFAULT_MAX_AGE_SEC: i64 = 3600 // == nx_jobclaim_lib.nx JR_DEFAULT_MAX_AGE_SEC
141const NW_MAXAGE_CONF: *u8 = "knowledge/status/nxwreap_maxage.conf" as *u8
142const NW_SRC_CONF: i64 = 0
143const NW_SRC_DEFAULT: i64 = 1
144
145func nw_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
146func nw_num(v: i64) -> i64 { nxi_out(v); return 0 }
147
148// The ship loop's bound in SECONDS, derived from its own millisecond constant so the two can never
149// drift apart by a hand-converted second copy.
150func nw_ship_bound_s() -> i64 { return NW_SHIP_TIMEOUT_MS / NW_MS_PER_S }
151
152// argv > conf > code default (rule 17). srcp[0] records WHICH one was used, because a default that
153// silently stands in for a missing conf is a number nobody knows the provenance of.
154func nw_maxage(srcp: *i64) -> i64 {
155 srcp[0] = NW_SRC_DEFAULT
156 let b: *u8 = sys_mmap(NW_CONFBUF)
157 let n: i64 = ccz_read(NW_MAXAGE_CONF, b, NW_CONFBUF - 1)
158 if n <= 0 { sys_munmap(b, NW_CONFBUF); return NW_DEFAULT_MAX_AGE_SEC }
159 let ep: *i64 = sys_mmap(NW_I64_BYTES) as *i64
160 let v: i64 = ccz_num_at(b, n, 0, ep)
161 sys_munmap(ep as *u8, NW_I64_BYTES)
162 sys_munmap(b, NW_CONFBUF)
163 if v <= 0 { return NW_DEFAULT_MAX_AGE_SEC }
164 srcp[0] = NW_SRC_CONF
165 return v
166}
167
168func nw_maxage_src_name(s: i64) -> *u8 {
169 if s == NW_SRC_CONF { return "knowledge/status/nxwreap_maxage.conf" as *u8 }
170 return "the code default (conf absent or unparseable -- stated, not hidden)" as *u8
171}
172
173func nw_decision_name(d: i64) -> *u8 {
174 if d == NW_D_REAP { return "REAP" as *u8 }
175 if d == NW_D_LIVE_OWNER { return "REFUSED-LIVE-OWNER" as *u8 }
176 if d == NW_D_TOO_YOUNG { return "REFUSED-TOO-YOUNG" as *u8 }
177 if d == NW_D_MALFORMED { return "REFUSED-MALFORMED-NAME" as *u8 }
178 if d == NW_D_VANISHED { return "REFUSED-VANISHED" as *u8 }
179 if d == NW_D_NOT_REGULAR { return "REFUSED-NOT-A-REGULAR-FILE" as *u8 }
180 if d == NW_D_PATHLONG { return "REFUSED-PATH-TOO-LONG" as *u8 }
181 if d == NW_D_NOT_NXW { return "NOT-A-SCRATCH-NAME" as *u8 }
182 return "REFUSED-UNCLASSIFIED" as *u8
183}
184
185func nw_decision_why(d: i64) -> *u8 {
186 if d == NW_D_REAP { return "old, and no process holds the pid in its name -- the writer was killed before the rename" as *u8 }
187 if d == NW_D_LIVE_OWNER { return "/proc/<pid> EXISTS: this may be a write in flight, and pid reuse is resolved toward doing nothing" as *u8 }
188 if d == NW_D_TOO_YOUNG { return "younger than the threshold -- a legitimate slow write on a saturated array looks exactly like this" as *u8 }
189 if d == NW_D_MALFORMED { return "the marker is present but the suffix is not a digit run -- refused BY NAME, never read as pid 0" as *u8 }
190 if d == NW_D_VANISHED { return "gone between the directory read and the stat -- another writer finished; a reap that invented its victim would report work it did not do" as *u8 }
191 if d == NW_D_NOT_REGULAR { return "the name matches but the entry is not a regular file -- out of contract, never unlinked" as *u8 }
192 if d == NW_D_PATHLONG { return "dir plus name exceeds the path buffer -- refused rather than truncated, because a truncated path names a DIFFERENT file" as *u8 }
193 if d == NW_D_NOT_NXW { return "no .nxw marker at all -- not this organ's subject" as *u8 }
194 return "unclassified" as *u8
195}
196
197func nw_ctr() -> *i64 {
198 let c: *i64 = sys_mmap(NW_C_SLOTS * NW_I64_BYTES) as *i64
199 var i: i64 = 0
200 while i < NW_C_SLOTS { c[i] = 0; i = i + 1 }
201 return c
202}
203
204// THE PID IS IN THE NAME. Returns the pid encoded in a write-guard scratch name, or a NAMED negative
205// refusal. The LAST marker wins: a real name observed live is
206// `nx_opaque_login_smoke.nx.laneFprobe.nxw8847`, so a first-match scan would parse `laneFprobe...`
207// and refuse a file that is perfectly well formed.
208func nw_pid_of(name: *u8, nlen: i64) -> i64 {
209 let suf: *u8 = NW_SUFFIX
210 var mark: i64 = 0 - 1
211 var i: i64 = 0
212 while i + NW_SUF_LEN <= nlen {
213 var k: i64 = 0
214 var ok: i64 = 1
215 while k < NW_SUF_LEN {
216 if name[i+k] != suf[k] { ok = 0; k = NW_SUF_LEN } else { k = k + 1 }
217 }
218 if ok == 1 { mark = i }
219 i = i + 1
220 }
221 if mark < 0 { return NW_PID_NOT_NXW }
222 let dstart: i64 = mark + NW_SUF_LEN
223 let ndig: i64 = nlen - dstart
224 if ndig <= 0 { return NW_PID_MALFORMED }
225 if ndig > NW_PID_MAX_DIGITS { return NW_PID_MALFORMED }
226 var v: i64 = 0
227 var j: i64 = dstart
228 while j < nlen {
229 let c: i64 = name[j] as i64
230 if c < NW_CH_ZERO { return NW_PID_MALFORMED }
231 if c > NW_CH_NINE { return NW_PID_MALFORMED }
232 v = v * NW_B10 + (c - NW_CH_ZERO)
233 j = j + 1
234 }
235 if v <= 0 { return NW_PID_MALFORMED }
236 return v
237}
238
239// Does /proc/<pid> exist? A non-positive pid can never be resolved, so it is reported ALIVE and the
240// file is therefore never reaped -- the fail-safe direction, and the reason nw_pid_of must not
241// return 0 for a refusal.
242func nw_pid_alive(pid: i64, pbuf: *u8) -> i64 {
243 if pid <= 0 { return 1 }
244 var o: i64 = 0
245 o = ccz_cat_str(pbuf, o, NW_PROC)
246 o = ccz_cat_num(pbuf, o, pid)
247 pbuf[o] = 0 as u8
248 return lr_exists(pbuf)
249}
250
251// Classify ONE directory entry. Leaves the composed path in pathbuf whenever it reached the stat,
252// so a caller acting on NW_D_REAP unlinks EXACTLY the path this function measured.
253func nw_classify(dir: *u8, name: *u8, nlen: i64, dtype: i64, now: i64, max_age_s: i64,
254 pathbuf: *u8, pidbuf: *u8, info: *i64) -> i64 {
255 info[NW_I_PID] = 0
256 info[NW_I_AGE] = 0 - 1
257 info[NW_I_ALIVE] = 0
258 let pid: i64 = nw_pid_of(name, nlen)
259 if pid == NW_PID_NOT_NXW { return NW_D_NOT_NXW }
260 if pid == NW_PID_MALFORMED { return NW_D_MALFORMED }
261 info[NW_I_PID] = pid
262 // A scratch file is a REGULAR file. DT_UNKNOWN is not a refusal -- some filesystems simply do
263 // not fill d_type, and the stat below is the authority in that case.
264 var reg: i64 = 0
265 if dtype == DT_REG { reg = 1 }
266 if dtype == DT_UNKNOWN { reg = 1 }
267 if reg == 0 { return NW_D_NOT_REGULAR }
268 let dlen: i64 = ccz_slen(dir)
269 if dlen + nlen + 2 >= NW_PATHBUF { return NW_D_PATHLONG }
270 var o: i64 = 0
271 o = ccz_cat_str(pathbuf, o, dir)
272 pathbuf[o] = NW_SLASH as u8
273 o = o + 1
274 o = ccz_cat_str(pathbuf, o, name)
275 let ex: i64 = lr_exists(pathbuf)
276 if ex == 0 { return NW_D_VANISHED }
277 let age: i64 = lr_age_s(pathbuf, now)
278 info[NW_I_AGE] = age
279 let alive: i64 = nw_pid_alive(pid, pidbuf)
280 info[NW_I_ALIVE] = alive
281 // THE DECISION IS NOT RE-IMPLEMENTED. lr_should_reap owns it.
282 if lr_should_reap(ex, alive, age, max_age_s) == 1 { return NW_D_REAP }
283 if alive == 1 { return NW_D_LIVE_OWNER }
284 return NW_D_TOO_YOUNG
285}
286
287func nw_bump(out: *i64, d: i64) -> i64 {
288 if d == NW_D_REAP { out[NW_C_REAP] = out[NW_C_REAP] + 1; return 0 }
289 if d == NW_D_LIVE_OWNER { out[NW_C_LIVE_OWNER] = out[NW_C_LIVE_OWNER] + 1; return 0 }
290 if d == NW_D_TOO_YOUNG { out[NW_C_TOO_YOUNG] = out[NW_C_TOO_YOUNG] + 1; return 0 }
291 if d == NW_D_MALFORMED { out[NW_C_MALFORMED] = out[NW_C_MALFORMED] + 1; return 0 }
292 if d == NW_D_VANISHED { out[NW_C_VANISHED] = out[NW_C_VANISHED] + 1; return 0 }
293 if d == NW_D_NOT_REGULAR { out[NW_C_NOT_REGULAR] = out[NW_C_NOT_REGULAR] + 1; return 0 }
294 if d == NW_D_PATHLONG { out[NW_C_PATHLONG] = out[NW_C_PATHLONG] + 1; return 0 }
295 out[NW_C_NOT_NXW] = out[NW_C_NOT_NXW] + 1
296 return 0
297}
298
299func nw_sum(out: *i64) -> i64 {
300 var s: i64 = 0
301 s = s + out[NW_C_REAP]
302 s = s + out[NW_C_LIVE_OWNER]
303 s = s + out[NW_C_TOO_YOUNG]
304 s = s + out[NW_C_MALFORMED]
305 s = s + out[NW_C_VANISHED]
306 s = s + out[NW_C_NOT_REGULAR]
307 s = s + out[NW_C_PATHLONG]
308 s = s + out[NW_C_NOT_NXW]
309 return s
310}
311
312func nw_axis_sum(out: *i64) -> i64 {
313 return out[NW_C_UNLINKED] + out[NW_C_UNLINK_FAIL] + out[NW_C_WOULD]
314}
315
316// THE WORKLIST TRAVELS WITH THE COUNT. A bare `reap=17` forces the next reader to re-derive by hand
317// exactly the scan this organ just performed.
318func nw_report(path: *u8, info: *i64, apply: i64, out: *i64) -> i64 {
319 nw_puts(" REAPABLE path=" as *u8)
320 nw_puts(path)
321 nw_puts(" pid=" as *u8)
322 nw_num(info[NW_I_PID])
323 nw_puts(" age_s=" as *u8)
324 nw_num(info[NW_I_AGE])
325 nw_puts(" owner_alive=0" as *u8)
326 if apply == 1 {
327 let rc: i64 = lr_unlink(path)
328 if rc == 0 {
329 out[NW_C_UNLINKED] = out[NW_C_UNLINKED] + 1
330 nw_puts(" -> REAPED\n" as *u8)
331 return 0
332 }
333 out[NW_C_UNLINK_FAIL] = out[NW_C_UNLINK_FAIL] + 1
334 nw_puts(" -> REAP-FAILED rc=" as *u8)
335 nw_num(rc)
336 nw_puts("\n" as *u8)
337 return 0
338 }
339 out[NW_C_WOULD] = out[NW_C_WOULD] + 1
340 nw_puts(" -> WOULD-REAP (dry run: nothing was written)\n" as *u8)
341 return 0
342}
343
344// Walk ONE directory and classify every entry. apply=0 is DRY and is the default every caller is
345// expected to pass; only an explicit 1 unlinks anything.
346// Returns 0 when the directory was read, -1 when it could not be opened. THOSE ARE DIFFERENT: a
347// directory that could not be looked at must never be reported in the same word as an empty one.
348// getdents64 is looped until it returns 0 -- ONE call is a prefix, not a listing.
349func nw_scan(dir: *u8, now: i64, max_age_s: i64, apply: i64, out: *i64) -> i64 {
350 let fd: i64 = sys_openat_rd(dir)
351 if fd < 0 { return 0 - 1 }
352 out[NW_C_DIRS] = out[NW_C_DIRS] + 1
353 // Allocated ONCE, outside the entry loop: 12,548 entries times a 64 KB buffer is the hot-loop
354 // allocation class this estate has already paid for once.
355 let dbuf: *u8 = sys_mmap(NW_DENTBUF)
356 let pathbuf: *u8 = sys_mmap(NW_PATHBUF)
357 let pidbuf: *u8 = sys_mmap(NW_PIDBUF)
358 let info: *i64 = sys_mmap(NW_I_SLOTS * NW_I64_BYTES) as *i64
359 var run: i64 = 1
360 while run == 1 {
361 let n: i64 = sys_getdents64(fd, dbuf, NW_DENTBUF)
362 if n <= 0 { run = 0 } else {
363 var off: i64 = 0
364 while off < n {
365 let rec: *u8 = ((dbuf as i64 + off) as *u8)
366 let reclen: i64 = dirent_reclen(rec)
367 if reclen <= 0 { off = n } else {
368 let name: *u8 = dirent_name(rec)
369 let nlen: i64 = ccz_slen(name)
370 let d: i64 = nw_classify(dir, name, nlen, dirent_type(rec), now, max_age_s, pathbuf, pidbuf, info)
371 out[NW_C_TOTAL] = out[NW_C_TOTAL] + 1
372 nw_bump(out, d)
373 if d == NW_D_REAP { nw_report(pathbuf, info, apply, out) }
374 off = off + reclen
375 }
376 }
377 }
378 }
379 sys_close(fd)
380 sys_munmap(dbuf, NW_DENTBUF)
381 sys_munmap(pathbuf, NW_PATHBUF)
382 sys_munmap(pidbuf, NW_PIDBUF)
383 sys_munmap(info as *u8, NW_I_SLOTS * NW_I64_BYTES)
384 return 0
385}
386
387// A PARTITION IS A CLAIM: PRINT THE PARTS AND CHECK THEY SUM. `.` and `..` are examined like any
388// other entry and land in not_nxw -- counted, never quietly skipped, so the total is the population.
389func nw_print_partition(out: *i64) -> i64 {
390 nw_puts(" dirs_scanned=" as *u8)
391 nw_num(out[NW_C_DIRS])
392 nw_puts(" entries_examined=" as *u8)
393 nw_num(out[NW_C_TOTAL])
394 nw_puts("\n PARTITION reap=" as *u8)
395 nw_num(out[NW_C_REAP])
396 nw_puts(" live_owner=" as *u8)
397 nw_num(out[NW_C_LIVE_OWNER])
398 nw_puts(" too_young=" as *u8)
399 nw_num(out[NW_C_TOO_YOUNG])
400 nw_puts(" malformed=" as *u8)
401 nw_num(out[NW_C_MALFORMED])
402 nw_puts(" vanished=" as *u8)
403 nw_num(out[NW_C_VANISHED])
404 nw_puts(" not_regular=" as *u8)
405 nw_num(out[NW_C_NOT_REGULAR])
406 nw_puts(" pathlong=" as *u8)
407 nw_num(out[NW_C_PATHLONG])
408 nw_puts(" not_nxw=" as *u8)
409 nw_num(out[NW_C_NOT_NXW])
410 let s: i64 = nw_sum(out)
411 nw_puts(" SUM=" as *u8)
412 nw_num(s)
413 nw_puts(" total=" as *u8)
414 nw_num(out[NW_C_TOTAL])
415 if s == out[NW_C_TOTAL] { nw_puts(" PARTITION-SUMS\n" as *u8) } else { nw_puts(" PARTITION-LEAK\n" as *u8) }
416 nw_puts(" AXIS (over the reap bucket only -- a SEPARATE axis, not a partition member) unlinked=" as *u8)
417 nw_num(out[NW_C_UNLINKED])
418 nw_puts(" reap_failed=" as *u8)
419 nw_num(out[NW_C_UNLINK_FAIL])
420 nw_puts(" would_reap=" as *u8)
421 nw_num(out[NW_C_WOULD])
422 let a: i64 = nw_axis_sum(out)
423 nw_puts(" AXIS-SUM=" as *u8)
424 nw_num(a)
425 nw_puts(" reap=" as *u8)
426 nw_num(out[NW_C_REAP])
427 if a == out[NW_C_REAP] { nw_puts(" AXIS-SUMS\n" as *u8) } else { nw_puts(" AXIS-LEAK\n" as *u8) }
428 return 0
429}