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}