code wiki / (root) / nx_treehash.nx

nx_treehash.nx

buildroot/runtime/nx_treehash.nx

15034 B311 linesdepth 5pulls 5 transitivereach 0 importersview sourcekind tool
docsdependenciesstructsconstsfunctions

about

nx_treehash.nx -- CROSS-TREE CONTENT-IDENTITY MANIFEST. The hash half of nx_treediff. THE GAP IT CLOSES (measured 2026-08-06, tree-canon-single-writer lane): nx_treediff emits `<bytes> <relpath>` and nx_treediverge compares those SIZES. Both organs say so honestly in their own output -- treediff prints "size is a SCREEN, not proof of identity" and treediverge's usage line says "(by BYTES, a FLOOR)". They were right, and they were built that way for a stated reason: "it needs no hash primitive on either side." THAT REASON EXPIRED. nx_sha256.nx has shipped the whole time and nx_filehash wired it to a PATH. Meanwhile the hourly treediverge beat reported divergent=5943 for days while a content-level census of the same two trees reported 17,164 of 17,579. Both numbers were honest measurements of DIFFERENT THINGS: 5,943 files differ in LENGTH; the rest differ in BYTES AT EQUAL LENGTH, which a size screen cannot see by construction. => A SCREEN THAT DOCUMENTS ITS OWN FLOOR STILL GETS READ AS A TOTAL ONCE IT IS WIRED TO A BEAT. The trend log made a floor look like a burn-down. This organ removes the excuse: content identity is now as cheap to measure as length, so nothing has to infer identity from size again. DESIGN: same walker, same caps, same in-band envelope as nx_treediff -- deliberately, so the two manifests are directly comparable and this organ inherits a proven traversal rather than a new one. The row is a STRICT SUPERSET of treediff's: `<sha256-64hex> <bytes> <relpath>`, so a consumer can still do the cheap size compare AND the exact content compare from one file. *UNREADABLE IS NEVER A HASH. nx_filehash banked the law: a hasher that digests an empty buffer for a missing file emits a real-looking 64-hex answer for a file that does not exist. Here an unreadable file emits 64 '-' (fixed width, cannot collide with hex, never equal to itself) and is COUNTED in the envelope, so a permission error can never masquerade as convergence. *MEMORY IS THE REAL RISK AND IT IS FREED EXPLICITLY. Hashing means READING EVERY FILE'S BYTES -- the exact shape of the leak class this estate has been bitten by repeatedly (the unfreed per-call mmap that took 28.5GB of a 36GB host, and nx_treediff's own per-entry path buffers). Every mmap here is matched by a munmap. THE SUBTLE ONE: sys_read_file reserves `filesize+16`, but for a file whose lseek(END) is 0 it falls back to a 4 GiB reservation -- so a zero-byte .nx freed as "0+16" would leak 4 GiB of address space per occurrence and hit RLIMIT_AS. That case is freed at its true reserved size. DIALECT: plain-if, no empty literals, <=6 params, consts above use. license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26).

dependencies 2 imports · 0 importers

nx_syscalls.nx nx_sha256.nx nx_treehash.nx

imports: nx_syscalls.nxnx_sha256.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main sys_mmap nxa_die sys_write sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ th_puts sys_write ↻ sys_exit ↻ th_walk sys_openat_rd sys_mmap ↻ sys_getdents64 dirent_reclen dirent_type dirent_name th_cat th_walk ↻ th_ends_ext th_hash_file sys_mmap ↻ sys_read_file sys_openat_rd ↻ sys_lseek sys_mmap ↻ sys_read sys_munmap sys_close sys_munmap ↻ sha256_digest

structs

none

consts

40const TH_MAX_FILES: i64 = 40000 // upper bound on emitted rows; declared, never silent
41const TH_MAX_DEPTH: i64 = 8 // recursion depth cap
42const TH_PATH_CAP: i64 = 1024
43const TH_DIRENT_BUF: i64 = 65536
44const TH_OUTBUF: i64 = 8388608 // 8 MiB (~20k rows x ~130B, with headroom)
45const TH_DIGEST_BYTES: i64 = 32
46const TH_HEX_CHARS: i64 = 64
47const TH_NUMBUF: i64 = 64
49const TH_EMPTY_RESERVE: i64 = 4294967296

functions

59func th_puts(s: *u8) -> i64
called by 2: th_putnmain calls 1: sys_write
65func th_putn(v: i64) -> i64
called by 1: main calls 2: sys_writeth_puts
76func th_cat(d: *u8, o: i64, s: *u8) -> i64
called by 1: th_walk
93func th_ends_ext(nm: *u8) -> i64
called by 1: th_walk
107func th_digest_hex(d: *u8) -> i64
called by 1: th_walk
121func th_hex_unreadable() -> i64
called by 1: th_walk
128func th_hash_file(path: *u8, out: *u8) -> i64
143func th_row(hx: *u8, sz: i64, rel: *u8) -> i64
called by 1: th_walk
169func th_walk(dir: *u8, rel: *u8, depth: i64) -> i64
229func main(argc: i64, argv: *i64) -> i64