code wiki / (root) / nx_nxwreap_lib.nx

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}