code wiki / _hdl_build / nx_nas_book_census.nx

nx_nas_book_census.nx source

↩ module page · 388 lines · 20049 B

1// nx_nas_book_census.nx -- SOVEREIGN, TYPE-DRIVEN NAS book census (Reader-arc RUNG 0). 2// 3// MISSION: the old indexer reported 148 books -- a ~37x undercount. ROOT CAUSE: it (a) mounted 4// only 5 of 26 NAS shares, (b) classified books BY SHARE (nas_scans->"book") instead of by FILE 5// TYPE, (c) included #recycle junk. This organ walks ALL book-bearing mounts with getdents64 (its 6// OWN walk -- no find/grep/python/sqlite), classifies BY FILE EXTENSION across every share, excludes 7// Synology junk (#recycle / @eaDir), and AUTHORS the true book index + a census log. The count is 8// whatever the walk MEASURES -- nothing is hardcoded (no 5760, no 148). 9// 10// COMPOSITION (anti-reinvention): reuses sys_getdents64 + dirent_reclen/dirent_type/dirent_name from 11// nx_syscalls.nx, and the proven raw-offset getdents64 batch loop shape from _si_walk_probe2.nx:37-70 12// (struct-free; the NxDirent struct path crashes signal-11 under nx_cc_sovereign -- LANDMINE avoided). 13// The ONLY new logic is: (1) an EXPLICIT directory work-stack (the proven walkers are all depth-1; we 14// wrap the depth-1 loop in a bounded push/pop stack to descend the Calibre tree -- NOT self-recursion, 15// JPL bounded-loop discipline), (2) the type/extension classifier, (3) the junk-exclusion substring 16// filter. idx format mirrors nx_media_server.nx ms_handle_list exactly: one absolute path per line, 17// '\n'-terminated, no header/footer (server splits on \n, basename = after last '/'). 18// 19// DEDUP: dedup is STRUCTURAL. The 10 roots are disjoint subtrees and /mnt/nas_home (the duplicate 20// Synology tree of /mnt/nas_homes) is deliberately EXCLUDED, so a single walk of distinct roots 21// cannot revisit any absolute path -- the walk itself is the dedup. Symlink loops are guarded by 22// skipping DT_LNK directories (never pushed) and a hard iteration cap (bounded loop). 23// 24// x86-ONLY lane: sys_getdents64 from nx_syscalls.nx gates on TARGET_X86_64 -> emits 217 (rv64 numbers 25// HANG on this native lane per nx_media_gather.nx). We never reimplement __syscall. 26// license_tier: ORIGINAL 27import "nx_syscalls.nx" 28import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 29const K_MAGIC_268435456: i64 = 268435456 30const K_MAGIC_4000000: i64 = 4000000 31const K_MAGIC_1048640: i64 = 1048640 32const K_MAGIC_8192: i64 = 8192 33const K_MAGIC_1048576: i64 = 1048576 34const K_MAGIC_8000000: i64 = 8000000 35 36// ---- io helpers (mirror nx_sprawl_eval sp_* / nx_media_server ms_*) ---- 37func bc_w(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 38func bc_wn(fd: i64, buf: *u8, n: i64) -> i64 { sys_write(fd, buf, 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 bc_n(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 44func bc_p(s: *u8) -> i64 { bc_w(1, s); return 0 } 45func bc_pn(v: i64) -> i64 { bc_n(1, v); return 0 } 46 47func bc_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 48 49// case-insensitive ASCII lower of a byte 50func bc_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 51 52// substring scan over a NUL-terminated string (for #recycle / @eaDir exclusion) 53func bc_has(hay: *u8, needle: *u8) -> i64 { 54 let hn: i64 = bc_slen(hay) 55 let nl: i64 = bc_slen(needle) 56 if nl == 0 { return 0 } 57 var i: i64 = 0 58 while i + nl <= hn { 59 var k: i64 = 0 60 var hit: i64 = 1 61 while k < nl { if hay[i+k] != needle[k] { hit = 0; k = nl } else { k = k + 1 } } 62 if hit == 1 { return 1 } 63 i = i + 1 64 } 65 return 0 66} 67 68// ---- REFERENCE list of the canonical book-bearing shares (SUPERSEDED by runtime auto-discovery of 69// /mnt/nas_* in main; kept as documentation of where books historically lived). ---- 70const NROOT: i64 = 10 71func bc_root(i: i64) -> *u8 { 72 if i == 0 { return "/mnt/nas_homes" as *u8 } 73 if i == 1 { return "/mnt/nas_ai" as *u8 } 74 if i == 2 { return "/mnt/nas_scans" as *u8 } 75 if i == 3 { return "/mnt/nas_audiobooks" as *u8 } 76 if i == 4 { return "/mnt/nas_share" as *u8 } 77 if i == 5 { return "/mnt/nas_gatsby" as *u8 } 78 if i == 6 { return "/mnt/nas_movies" as *u8 } 79 if i == 7 { return "/mnt/nas_photo" as *u8 } 80 if i == 8 { return "/mnt/nas_3d_printer" as *u8 } 81 if i == 9 { return "/mnt/nas_music" as *u8 } 82 return "" as *u8 83} 84 85// ---- BOOK EXTENSION SET (dispatch table; NUL-terminated literals, already lowercase) ---- 86// returns extension index 0..NEXT-1 for a matched extension, else -1. caller passes a 87// lowercased, NUL-terminated extension (no leading dot). 88const NEXT: i64 = 19 89func bc_ext_name(i: i64) -> *u8 { 90 if i == 0 { return "epub" as *u8 } 91 if i == 1 { return "mobi" as *u8 } 92 if i == 2 { return "azw" as *u8 } 93 if i == 3 { return "azw3" as *u8 } 94 if i == 4 { return "azw4" as *u8 } 95 if i == 5 { return "kfx" as *u8 } 96 if i == 6 { return "cbz" as *u8 } 97 if i == 7 { return "cbr" as *u8 } 98 if i == 8 { return "cbx" as *u8 } 99 if i == 9 { return "cb7" as *u8 } 100 if i == 10 { return "djvu" as *u8 } 101 if i == 11 { return "fb2" as *u8 } 102 if i == 12 { return "lit" as *u8 } 103 if i == 13 { return "prc" as *u8 } 104 if i == 14 { return "pdb" as *u8 } 105 if i == 15 { return "chm" as *u8 } 106 if i == 16 { return "htmlz" as *u8 } 107 if i == 17 { return "ibooks" as *u8 } 108 if i == 18 { return "pdf" as *u8 } 109 return "" as *u8 110} 111 112// exact (already-lowercased) string equality 113func bc_streq(a: *u8, b: *u8) -> i64 { 114 var i: i64 = 0 115 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 116 if b[i] != (0 as u8) { return 0 } 117 return 1 118} 119 120// match lowercased extension against the book set -> ext id 0..18, else -1 121func bc_ext_id(extlc: *u8) -> i64 { 122 var i: i64 = 0 123 while i < NEXT { if bc_streq(extlc, bc_ext_name(i)) == 1 { return i } i = i + 1 } 124 return 0 - 1 125} 126 127func main() -> i64 { 128 bc_p("BOOK-CENSUS: start (sovereign type-driven NAS walk)\n" as *u8) 129 130 // ---- output sinks (additive; idx truncates for idempotent re-run, log appends) ---- 131 let idxfd: i64 = sys_openat_wr("knowledge/staging/media/idx_book_true.wsl" as *u8, 0x1a4) 132 if idxfd < 0 { bc_p("BOOK-CENSUS-FAIL idx-open\n" as *u8); sys_exit(1); return 1 } 133 let logfd: i64 = sys_openat_append("knowledge/status/nas_book_census.log" as *u8, 0x1a4) 134 if logfd < 0 { bc_p("BOOK-CENSUS-FAIL log-open\n" as *u8); sys_exit(1); return 1 } 135 136 // ---- arenas (mmap; bounded) ---- 137 // path arena: store NUL-terminated directory paths for the work-stack. 138 let DIR_ARENA: i64 = K_MAGIC_268435456 // 256 MiB of dir-path bytes 139 let parena: *u8 = sys_mmap(DIR_ARENA + 64) 140 var pused: i64 = 0 141 // stack of (offset-into-parena, share-id) -- two parallel i64 arrays. 142 let STKCAP: i64 = K_MAGIC_4000000 // up to 4M pending dirs (bounded) 143 let stkoff: *i64 = sys_mmap(STKCAP * 8) as *i64 144 let stkshr: *i64 = sys_mmap(STKCAP * 8) as *i64 145 var sp: i64 = 0 146 // scratch buffers 147 let dirbuf: *u8 = sys_mmap(K_MAGIC_1048640) // getdents64 batch buffer (1 MiB) 148 let childbuf: *u8 = sys_mmap(K_MAGIC_8192) // assembled child absolute path 149 let extbuf: *u8 = sys_mmap(64) // lowercased extension scratch 150 let linebuf: *u8 = sys_mmap(K_MAGIC_8192) // idx line (path + '\n') 151 152 // ---- counters ---- 153 var total: i64 = 0 154 var dirs_walked: i64 = 0 155 var excl_recycle: i64 = 0 156 var excl_eadir: i64 = 0 157 var open_fail: i64 = 0 158 let extcnt: *i64 = sys_mmap(NEXT * 8) as *i64 159 var ei: i64 = 0; while ei < NEXT { extcnt[ei] = 0; ei = ei + 1 } 160 // per-share counters + root registry sized for AUTO-DISCOVERED roots (was the static NROOT=10) 161 let MAXROOT: i64 = 64 162 let shrcnt: *i64 = sys_mmap(MAXROOT * 8) as *i64 163 var si: i64 = 0; while si < MAXROOT { shrcnt[si] = 0; si = si + 1 } 164 let rootoff: *i64 = sys_mmap(MAXROOT * 8) as *i64 // parena offset of each discovered root path 165 var nroots: i64 = 0 166 167 // ---- AUTO-DISCOVER mount roots: every /mnt/nas_* dir EXCEPT the nas_home dup tree ---- 168 // A hardcoded root list is exactly what caused the original 37x undercount -- shares get silently 169 // missed. The census now covers WHATEVER IS MOUNTED: mount a share, it is censused. nas_home is the 170 // Synology double-mount of nas_homes and is structurally excluded so no absolute path is revisited. 171 let mntfd: i64 = sys_openat_rd("/mnt" as *u8) 172 if mntfd < 0 { bc_p("BOOK-CENSUS-FAIL mnt-open\n" as *u8); sys_exit(1); return 1 } 173 var mdone: i64 = 0 174 while mdone == 0 { 175 let mnb: i64 = sys_getdents64(mntfd, dirbuf, K_MAGIC_1048576) 176 if mnb <= 0 { mdone = 1 } 177 else { 178 var moff: i64 = 0 179 while moff < mnb { 180 let mrec: *u8 = (dirbuf as i64 + moff) as *u8 181 let mrl: i64 = dirent_reclen(mrec) 182 if mrl <= 0 { moff = mnb } 183 else { 184 let mnm: *u8 = dirent_name(mrec) 185 // prefix match "nas_" (n=110 a=97 s=115 _=95) 186 var isnas: i64 = 0 187 if mnm[0] == (110 as u8) { if mnm[1] == (97 as u8) { if mnm[2] == (115 as u8) { if mnm[3] == (95 as u8) { isnas = 1 } } } } 188 // exclude EXACTLY "nas_home" (dup of nas_homes): h=104 o=111 m=109 e=101 then NUL@8 189 if isnas == 1 { 190 if mnm[4] == (104 as u8) { if mnm[5] == (111 as u8) { if mnm[6] == (109 as u8) { if mnm[7] == (101 as u8) { if mnm[8] == (0 as u8) { isnas = 0 } } } } } 191 } 192 if isnas == 1 { 193 if nroots < MAXROOT { 194 // assemble "/mnt/" + name into childbuf, copy into parena, push as a root 195 let pfx: *u8 = "/mnt/" as *u8 196 var w: i64 = 0 197 while pfx[w] != (0 as u8) { childbuf[w] = pfx[w]; w = w + 1 } 198 var z: i64 = 0 199 while mnm[z] != (0 as u8) { childbuf[w] = mnm[z]; w = w + 1; z = z + 1 } 200 childbuf[w] = 0 as u8 201 let rbase: i64 = pused 202 var q: i64 = 0 203 while q < w { parena[pused] = childbuf[q]; pused = pused + 1; q = q + 1 } 204 parena[pused] = 0 as u8; pused = pused + 1 205 rootoff[nroots] = rbase 206 stkoff[sp] = rbase 207 stkshr[sp] = nroots 208 sp = sp + 1 209 nroots = nroots + 1 210 } 211 } 212 moff = moff + mrl 213 } 214 } 215 } 216 } 217 sys_close(mntfd) 218 bc_p("BOOK-CENSUS: discovered roots=" as *u8); bc_pn(nroots); bc_p("\n" as *u8) 219 220 // ---- bounded walk: pop a dir, getdents64 it, classify files, push child dirs ---- 221 let ITER_CAP: i64 = K_MAGIC_8000000 // hard upper bound on dirs processed (symlink-loop guard) 222 var iters: i64 = 0 223 while sp > 0 { 224 if iters >= ITER_CAP { sp = 0 } 225 else { 226 iters = iters + 1 227 sp = sp - 1 228 let doff: i64 = stkoff[sp] 229 let shr: i64 = stkshr[sp] 230 let dpath: *u8 = (parena as i64 + doff) as *u8 231 let dlen: i64 = bc_slen(dpath) 232 233 let dfd: i64 = sys_openat_rd(dpath) 234 if dfd < 0 { open_fail = open_fail + 1 } 235 else { 236 dirs_walked = dirs_walked + 1 237 var done: i64 = 0 238 while done == 0 { 239 let nb: i64 = sys_getdents64(dfd, dirbuf, K_MAGIC_1048576) 240 if nb <= 0 { done = 1 } 241 else { 242 var off: i64 = 0 243 while off < nb { 244 let rec: *u8 = (dirbuf as i64 + off) as *u8 245 let rl: i64 = dirent_reclen(rec) 246 if rl <= 0 { off = nb } 247 else { 248 let nm: *u8 = dirent_name(rec) 249 let dt: i64 = dirent_type(rec) 250 // skip "." and ".." (dotlike) 251 var skip: i64 = 0 252 if nm[0] == (46 as u8) { 253 if nm[1] == (0 as u8) { skip = 1 } 254 else { if nm[1] == (46 as u8) { if nm[2] == (0 as u8) { skip = 1 } } } 255 } 256 if skip == 0 { 257 // assemble childpath = dpath + "/" + nm (into childbuf) 258 var w: i64 = 0 259 var z: i64 = 0 260 while z < dlen { childbuf[w] = dpath[z]; w = w + 1; z = z + 1 } 261 childbuf[w] = 47 as u8; w = w + 1 262 var nl: i64 = 0 263 while nm[nl] != (0 as u8) { childbuf[w] = nm[nl]; w = w + 1; nl = nl + 1 } 264 childbuf[w] = 0 as u8 265 let clen: i64 = w 266 267 // junk exclusion (Synology): skip entirely -- no descend, no classify 268 var junk: i64 = 0 269 if bc_has(childbuf, "#recycle" as *u8) == 1 { junk = 1; excl_recycle = excl_recycle + 1 } 270 else { if bc_has(childbuf, "@eaDir" as *u8) == 1 { junk = 1; excl_eadir = excl_eadir + 1 } } 271 272 if junk == 0 { 273 if dt == 4 { 274 // DT_DIR: push child onto work-stack (descend later) 275 if sp < STKCAP { 276 if pused + clen + 1 < DIR_ARENA { 277 let cbase: i64 = pused 278 var q: i64 = 0 279 while q < clen { parena[pused] = childbuf[q]; pused = pused + 1; q = q + 1 } 280 parena[pused] = 0 as u8; pused = pused + 1 281 stkoff[sp] = cbase 282 stkshr[sp] = shr 283 sp = sp + 1 284 } 285 } 286 } else { 287 if dt == 10 { } else { 288 // DT_REG (8) OR DT_UNKNOWN (0; drvfs/9p often returns 0) -> classify by ext. 289 // find last '.' in nm 290 var dotp: i64 = 0 - 1 291 var x: i64 = 0 292 while nm[x] != (0 as u8) { if nm[x] == (46 as u8) { dotp = x } x = x + 1 } 293 var matched: i64 = 0 294 if dotp >= 0 { 295 // lowercase the extension into extbuf 296 var e: i64 = 0 297 var y: i64 = dotp + 1 298 while nm[y] != (0 as u8) { if e < 63 { extbuf[e] = bc_lc(nm[y] as i64) as u8; e = e + 1 } y = y + 1 } 299 extbuf[e] = 0 as u8 300 let id: i64 = bc_ext_id(extbuf) 301 if id >= 0 { 302 matched = 1 303 extcnt[id] = extcnt[id] + 1 304 } else { 305 // audiobooks-as-.zip: count .zip ONLY under /nas_audiobooks/ 306 if bc_streq(extbuf, "zip" as *u8) == 1 { 307 if bc_has(childbuf, "/nas_audiobooks/" as *u8) == 1 { matched = 1 } 308 } 309 } 310 } 311 if matched == 1 { 312 total = total + 1 313 shrcnt[shr] = shrcnt[shr] + 1 314 // append absolute childpath + '\n' to idx 315 var li: i64 = 0 316 while li < clen { linebuf[li] = childbuf[li]; li = li + 1 } 317 linebuf[clen] = 10 as u8 318 bc_wn(idxfd, linebuf, clen + 1) 319 } 320 } 321 } 322 } 323 } 324 off = off + rl 325 } 326 } 327 } 328 } 329 sys_close(dfd) 330 } 331 } 332 } 333 sys_close(idxfd) 334 335 // ---- walk-completion verdict: GREEN iff total>0 and the iteration cap did not abort the walk ---- 336 var green: i64 = 1 337 if total <= 0 { green = 0 } 338 if iters >= ITER_CAP { green = 0 } 339 340 // ---- author the census log line BY the organ (additive append) ---- 341 bc_w(logfd, "NAS-BOOK-CENSUS ts=" as *u8); bc_n(logfd, sys_now_realtime_sec()) 342 bc_w(logfd, " total=" as *u8); bc_n(logfd, total) 343 bc_w(logfd, " dirs_walked=" as *u8); bc_n(logfd, dirs_walked) 344 bc_w(logfd, " open_fail=" as *u8); bc_n(logfd, open_fail) 345 bc_w(logfd, " excluded_recycle=" as *u8); bc_n(logfd, excl_recycle) 346 bc_w(logfd, " excluded_eadir=" as *u8); bc_n(logfd, excl_eadir) 347 bc_w(logfd, " iters=" as *u8); bc_n(logfd, iters) 348 // per-extension (nonzero only) 349 bc_w(logfd, " ext{" as *u8) 350 var pi: i64 = 0 351 while pi < NEXT { 352 if extcnt[pi] > 0 { bc_w(logfd, " " as *u8); bc_w(logfd, bc_ext_name(pi)); bc_w(logfd, "=" as *u8); bc_n(logfd, extcnt[pi]) } 353 pi = pi + 1 354 } 355 bc_w(logfd, " }" as *u8) 356 // per-share (nonzero only) 357 bc_w(logfd, " share{" as *u8) 358 var ki: i64 = 0 359 while ki < nroots { 360 if shrcnt[ki] > 0 { bc_w(logfd, " " as *u8); bc_w(logfd, (parena as i64 + rootoff[ki]) as *u8); bc_w(logfd, "=" as *u8); bc_n(logfd, shrcnt[ki]) } 361 ki = ki + 1 362 } 363 bc_w(logfd, " }" as *u8) 364 if green == 1 { bc_w(logfd, " verdict=GREEN\n" as *u8) } else { bc_w(logfd, " verdict=RED\n" as *u8) } 365 sys_close(logfd) 366 367 // ---- stdout markers for the gate ---- 368 bc_p("BOOK-CENSUS: total=" as *u8); bc_pn(total) 369 bc_p(" dirs=" as *u8); bc_pn(dirs_walked) 370 bc_p(" recycle_excl=" as *u8); bc_pn(excl_recycle) 371 bc_p(" eadir_excl=" as *u8); bc_pn(excl_eadir) 372 bc_p("\nBOOK-CENSUS ext:" as *u8) 373 var qi: i64 = 0 374 while qi < NEXT { if extcnt[qi] > 0 { bc_p(" " as *u8); bc_p(bc_ext_name(qi)); bc_p("=" as *u8); bc_pn(extcnt[qi]) } qi = qi + 1 } 375 bc_p("\nBOOK-CENSUS share:" as *u8) 376 var mi: i64 = 0 377 while mi < nroots { if shrcnt[mi] > 0 { bc_p(" " as *u8); bc_p((parena as i64 + rootoff[mi]) as *u8); bc_p("=" as *u8); bc_pn(shrcnt[mi]) } mi = mi + 1 } 378 bc_p("\n" as *u8) 379 380 if green == 1 { 381 bc_p("BOOK-CENSUS-OK\n" as *u8) 382 sys_exit(0) 383 return 0 384 } 385 bc_p("BOOK-CENSUS-FAIL walk-incomplete-or-empty\n" as *u8) 386 sys_exit(1) 387 return 1 388}