sonar

Scan files at memory bandwidth speed.
Log | Files | Refs

commit a2e9d3f38ef8770ee7d2407055dfb60b83112a5c
parent b65b98b0476133970c7bc70285c076776b8f5c62
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Sun, 20 Sep 2026 07:43:00 -0300

ntfs: put extra names at a fixed slot so a replay repeats itself

Extra names claimed the next free slot, so replaying a projection appended a
second node for every hard link unless the caller emptied the tree first. That
requirement is what forced the builder to zero the node count mid-scan, in full
view of a thread resolving paths from it.

Sizing for records and links together, and seating each link at its ordinal above
the record range, makes a repeated projection write the same slots as the pass
before it. Nothing has to be emptied, and the claim goes with it.

Diffstat:
Mntfs/tree.odin | 20++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/ntfs/tree.odin b/ntfs/tree.odin @@ -66,7 +66,10 @@ and names are borrowed from the table's arenas rather than copied, so repeating allocates nothing. The table must therefore outlive the tree. */ to_tree :: proc(m: ^Mft, t: ^scan.Tree, p: ^Projection) -> scan.Error { - if err := scan.reserve(t, u32(len(m.entries))); err != nil { + // Records take slot per slot, extra names the range directly above them. Sizing + // for both up front is what lets a replay land on the same slots as the pass + // before it, so repeating one never has to empty the tree first. + if err := scan.reserve(t, u32(len(m.entries) + len(m.links))); err != nil { return err } if p.settled == nil { @@ -80,7 +83,6 @@ to_tree :: proc(m: ^Mft, t: ^scan.Tree, p: ^Projection) -> scan.Error { } clear(&p.fresh) - w := scan.writer(t, 0) used, bytes: u64 for i in 0 ..< u32(len(m.entries)) { @@ -120,14 +122,12 @@ to_tree :: proc(m: ^Mft, t: ^scan.Tree, p: ^Projection) -> scan.Error { } // Extra names for a file already counted. They belong in the tree so a file can - // be found at every path it has, but their bytes must not be counted twice. Only - // the ones not done before, or repeating this would add a node per link each - // time. Links are merged when the scan ends, so this is empty until then. - for l in m.links[p.links_done:] { - index, err := scan.claim(&w, 1) - if err != nil { - return err - } + // be found at every path it has, but their bytes must not be counted twice. Each + // sits at its ordinal above the record range, so a link written twice is written + // to the same slot. Links are merged when the scan ends, so this is empty + // until then. + for l, k in m.links[p.links_done:] { + index := u32(len(m.entries) + p.links_done + k) n := scan.node(t, index) n.parent = l.parent n.name = l.name