commit 17d958e303f8ee131ba092bba12e64b3cafcecf2
parent c853f8bfac1302ef74c28630be418a1e7864dcd4
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Sun, 20 Sep 2026 14:09:12 -0300
style: run odinfmt over the files this branch touched
Formatting only, no behaviour: the binary's output over /usr and /usr/share/doc
is unchanged line for line, and the tests pass as before.
Only the files this branch already changed. The repository is not odinfmt-clean
as a whole, and rewriting the twenty-odd untouched files would bury real changes
in alignment churn. Some of what is here is that older drift rather than this
branch's, since a formatter takes the whole file or none of it.
Diffstat:
11 files changed, 145 insertions(+), 50 deletions(-)
diff --git a/builder.odin b/builder.odin
@@ -20,15 +20,15 @@ readers have finished with, charges it, and publishes. If a pass takes longer th
frame the UI simply draws the previous answer.
*/
Builder :: struct {
- table: ^ntfs.Mft,
- tree: ^scan.Tree,
- out: ^scan.Publisher,
- mount: string,
- scanning: ^b32, // cleared by the reader thread when the table is finished
- started: time.Tick,
+ table: ^ntfs.Mft,
+ tree: ^scan.Tree,
+ out: ^scan.Publisher,
+ mount: string,
+ scanning: ^b32, // cleared by the reader thread when the table is finished
+ started: time.Tick,
projection: ntfs.Projection,
- roll: Rollup,
- done: b32,
+ roll: Rollup,
+ done: b32,
}
builder_run :: proc(b: ^Builder) {
@@ -88,7 +88,10 @@ builder_pass :: proc(b: ^Builder, exact: bool) {
complete = exact,
}
for i in 0 ..< count {
- s.rows[i] = {node = top[i].record, bytes = top[i].bytes}
+ s.rows[i] = {
+ node = top[i].record,
+ bytes = top[i].bytes,
+ }
}
scan.publish(b.out, s)
}
diff --git a/main.odin b/main.odin
@@ -16,8 +16,6 @@ import "core:strconv"
import "core:strings"
import "core:time"
-// Only referenced inside `when ODIN_DEBUG`; @(require) keeps release builds from
-// rejecting the import as unused.
@(require) import "debug"
import "ntfs"
import "scan"
@@ -26,13 +24,10 @@ import "walk"
TOP_N :: 20
main :: proc() {
- os.exit(debug_main())
+ os.exit(run())
}
-// Deferred cleanup must run before os.exit, so the debug wiring lives one frame down.
-debug_main :: proc() -> int {
- // Assertion failures print a call chain in every build; symbols need -debug.
- context.assertion_failure_proc = trace.assertion_failure_proc
+run :: proc() -> int {
when ODIN_DEBUG {
da: debug.Allocator
debug.init(&da, context.allocator)
@@ -40,16 +35,17 @@ debug_main :: proc() -> int {
defer debug.report(&da)
context.allocator = debug.allocator(&da)
}
- return run()
-}
-run :: proc() -> int {
defer free_all(context.temp_allocator)
- target := "C:"
+ // Assertion failures print a call chain in every build; symbols need -debug.
+ context.assertion_failure_proc = trace.assertion_failure_proc
+
+ target := os.user_home_dir(context.allocator) or_else "/"
opts: ntfs.Read_Options
wcfg: walk.Config
live := false
+
for arg in os.args[1:] {
switch arg {
case "-h", "--help", "/?":
@@ -148,7 +144,9 @@ run :: proc() -> int {
if err != nil {
#partial switch err {
case .Access_Denied:
- fmt.eprintln("error: access denied. Reading a raw volume needs an administrator prompt.")
+ fmt.eprintln(
+ "error: access denied. Reading a raw volume needs an administrator prompt.",
+ )
case .Not_Ntfs:
fmt.eprintln("error: not an NTFS volume")
case .Open_Failed:
@@ -182,7 +180,12 @@ run :: proc() -> int {
print_native(&m, elapsed)
}
fmt.println()
- fmt.printfln("tree %d slots, %d used, read in %.0f ms", t.count, t.nodes_done, time.duration_milliseconds(elapsed))
+ fmt.printfln(
+ "tree %d slots, %d used, read in %.0f ms",
+ t.count,
+ t.nodes_done,
+ time.duration_milliseconds(elapsed),
+ )
totals := make([]u64, t.count)
defer delete(totals)
@@ -301,9 +304,25 @@ print_native :: proc(m: ^ntfs.Mft, elapsed: time.Duration) {
}
volume_bytes := m.boot.total_sectors * u64(m.boot.bytes_per_sector)
fmt.println()
- fmt.printfln("volume %s, %d B clusters, %d B records", human(volume_bytes), m.boot.bytes_per_cluster, m.boot.record_size)
- fmt.printfln("mft %d slots, %d records, %d unreadable, read in %.0f ms", m.stats.records, m.stats.records_read, m.stats.records_bad, time.duration_milliseconds(elapsed))
- fmt.printfln("in use %d files, %d directories, %d extra hard links", files, m.stats.directories, len(m.links))
+ fmt.printfln(
+ "volume %s, %d B clusters, %d B records",
+ human(volume_bytes),
+ m.boot.bytes_per_cluster,
+ m.boot.record_size,
+ )
+ fmt.printfln(
+ "mft %d slots, %d records, %d unreadable, read in %.0f ms",
+ m.stats.records,
+ m.stats.records_read,
+ m.stats.records_bad,
+ time.duration_milliseconds(elapsed),
+ )
+ fmt.printfln(
+ "in use %d files, %d directories, %d extra hard links",
+ files,
+ m.stats.directories,
+ len(m.links),
+ )
fmt.printfln("attributed %s summed from every file run list", human(allocated))
/*
@@ -317,13 +336,24 @@ print_native :: proc(m: ^ntfs.Mft, elapsed: time.Duration) {
fmt.printfln("on disk %s marked allocated in $Bitmap", human(on_disk))
if on_disk >= allocated {
gap := on_disk - allocated
- fmt.printfln("unclaimed %s (%.2f%%) allocated but charged to no file", human(gap), 100 * f64(gap) / f64(on_disk))
+ fmt.printfln(
+ "unclaimed %s (%.2f%%) allocated but charged to no file",
+ human(gap),
+ 100 * f64(gap) / f64(on_disk),
+ )
} else {
- fmt.printfln("overcount %s more attributed than $Bitmap reports allocated", human(allocated - on_disk))
+ fmt.printfln(
+ "overcount %s more attributed than $Bitmap reports allocated",
+ human(allocated - on_disk),
+ )
}
}
if m.stats.resident_files > 0 {
- fmt.printfln("resident %d files hold %s inside their MFT records, charged to $MFT", m.stats.resident_files, human(m.stats.resident_bytes))
+ fmt.printfln(
+ "resident %d files hold %s inside their MFT records, charged to $MFT",
+ m.stats.resident_files,
+ human(m.stats.resident_bytes),
+ )
}
}
diff --git a/ntfs/volume_other.odin b/ntfs/volume_other.odin
@@ -33,7 +33,14 @@ Reading a block device is a matter of permissions on the device rather than of
privilege as it is on Windows: membership of the group owning it is what grants it,
and a group added to a live session only takes effect on the next login.
*/
-volume_open :: proc(path: string, mode := IO_Mode.Unbuffered, allocator := context.allocator) -> (v: Volume, err: Error) {
+volume_open :: proc(
+ path: string,
+ mode := IO_Mode.Unbuffered,
+ allocator := context.allocator,
+) -> (
+ v: Volume,
+ err: Error,
+) {
f, open_err := os.open(path, {.Read})
if open_err != nil {
if open_err == io.Error.Permission_Denied {
diff --git a/ntfs/volume_other_test.odin b/ntfs/volume_other_test.odin
@@ -43,7 +43,13 @@ test_volume_read_at_offsets :: proc(t: ^testing.T) {
off, size := span[0], span[1]
buf := make([]byte, size, context.temp_allocator)
testing.expect_value(t, volume_read_at(&v, buf, u64(off)), Error.None)
- testing.expectf(t, slice.equal(buf, content[off:off + size]), "wrong bytes at %d for %d", off, size)
+ testing.expectf(
+ t,
+ slice.equal(buf, content[off:off + size]),
+ "wrong bytes at %d for %d",
+ off,
+ size,
+ )
}
}
diff --git a/scan/scan_test.odin b/scan/scan_test.odin
@@ -4,7 +4,10 @@ import "core:testing"
@(test)
test_choose_prefers_the_mft_when_it_can_be_read :: proc(t: ^testing.T) {
- ntfs := Target{fs = .Ntfs, volume = `\\.\C:`}
+ ntfs := Target {
+ fs = .Ntfs,
+ volume = `\\.\C:`,
+ }
testing.expect_value(t, choose(ntfs, elevated = true).engine, Engine.Mft)
// Without the privilege the reader is unavailable rather than merely slower, so
@@ -30,7 +33,11 @@ test_choose_walks_what_it_cannot_read_directly :: proc(t: ^testing.T) {
test_choose_reads_an_image_without_elevation :: proc(t: ^testing.T) {
// An image is a file like any other: opening it is the whole of the permission
// needed, so asking for more would send a reader that cannot work.
- image := Target{fs = .Ntfs, volume = "disk.img", image = true}
+ image := Target {
+ fs = .Ntfs,
+ volume = "disk.img",
+ image = true,
+ }
testing.expect_value(t, choose(image, elevated = false).engine, Engine.Mft)
}
diff --git a/scan/target.odin b/scan/target.odin
@@ -65,12 +65,12 @@ target_destroy :: proc(t: ^Target) {
// themselves, which keeps this package free of a dependency on any of them.
Engine :: enum {
None, // nothing here can be scanned
- Mft, // read the NTFS master file table whole
+ Mft, // read the NTFS master file table whole
Walk, // enumerate directories
}
Choice :: struct {
- engine: Engine,
+ engine: Engine,
// The MFT reader is far faster but needs a raw volume handle. When this is set,
// Walk is what will run unless the caller elevates and asks again.
elevation_would_help: bool,
diff --git a/scan/target_other.odin b/scan/target_other.odin
@@ -125,7 +125,14 @@ The longest mount point that prefixes the path wins, which is what makes a files
mounted inside another resolve to the inner one. Names are returned owned.
*/
@(private = "file")
-mounted_at :: proc(path: string, allocator := context.allocator) -> (device, mount: string, fs: Filesystem, found: bool) {
+mounted_at :: proc(
+ path: string,
+ allocator := context.allocator,
+) -> (
+ device, mount: string,
+ fs: Filesystem,
+ found: bool,
+) {
when ODIN_OS != .Linux {
// Other unixes report their mounts through getmntinfo rather than a file.
return "", "", .Unknown, false
diff --git a/scan/tree.odin b/scan/tree.odin
@@ -48,12 +48,12 @@ identifier. One that discovers as it goes calls `claim` for a run of slots. Eith
way a slot belongs to one worker, so filling it needs no lock.
*/
Tree :: struct {
- blocks: [MAX_BLOCKS]^Block,
- count: u32, // slots handed out
- committed: u32, // slots backed by an allocated block
- mutex: sync.Mutex, // guards block allocation only
- arenas: []virtual.Arena, // one per writer; names live here for the tree's life
- root: u32,
+ blocks: [MAX_BLOCKS]^Block,
+ count: u32, // slots handed out
+ committed: u32, // slots backed by an allocated block
+ mutex: sync.Mutex, // guards block allocation only
+ arenas: []virtual.Arena, // one per writer; names live here for the tree's life
+ root: u32,
// Polled by a UI rather than pushed to it, so sampling costs a reader nothing.
nodes_done: u64,
bytes_done: u64,
diff --git a/scan/tree_test.odin b/scan/tree_test.odin
@@ -79,13 +79,28 @@ test_path_walks_to_a_root :: proc(t: ^testing.T) {
first, _ := claim(&w, 3)
testing.expect_value(t, first, u32(0))
- node(&tree, 0)^ = Node{parent = 0, name = "C:", flags = {.Used, .Directory}}
- node(&tree, 1)^ = Node{parent = 0, name = "Windows", flags = {.Used, .Directory}}
- node(&tree, 2)^ = Node{parent = 1, name = "notepad.exe", flags = {.Used}}
+ node(&tree, 0)^ = Node {
+ parent = 0,
+ name = "C:",
+ flags = {.Used, .Directory},
+ }
+ node(&tree, 1)^ = Node {
+ parent = 0,
+ name = "Windows",
+ flags = {.Used, .Directory},
+ }
+ node(&tree, 2)^ = Node {
+ parent = 1,
+ name = "notepad.exe",
+ flags = {.Used},
+ }
// Written with the separator of the machine showing the path, not the one the
// names came from, so this is built rather than spelled.
- want := strings.concatenate({"C:", SEPARATOR, "Windows", SEPARATOR, "notepad.exe"}, context.temp_allocator)
+ want := strings.concatenate(
+ {"C:", SEPARATOR, "Windows", SEPARATOR, "notepad.exe"},
+ context.temp_allocator,
+ )
testing.expect_value(t, path(&tree, 2, context.temp_allocator), want)
testing.expect_value(t, path(&tree, 0, context.temp_allocator), "C:")
}
@@ -98,8 +113,16 @@ test_path_stops_at_a_cycle :: proc(t: ^testing.T) {
defer tree_destroy(&tree)
w := writer(&tree)
claim(&w, 2)
- node(&tree, 0)^ = Node{parent = 1, name = "a", flags = {.Used, .Directory}}
- node(&tree, 1)^ = Node{parent = 0, name = "b", flags = {.Used, .Directory}}
+ node(&tree, 0)^ = Node {
+ parent = 1,
+ name = "a",
+ flags = {.Used, .Directory},
+ }
+ node(&tree, 1)^ = Node {
+ parent = 0,
+ name = "b",
+ flags = {.Used, .Directory},
+ }
p := path(&tree, 0, context.temp_allocator)
testing.expect(t, len(p) > 0, "a cycle produced no path at all")
diff --git a/walk/read_linux_test.odin b/walk/read_linux_test.odin
@@ -34,7 +34,11 @@ test_a_sparse_file_is_charged_what_it_occupies :: proc(t: ^testing.T) {
// The same length, actually written.
body := make([]byte, 1 << 20, context.temp_allocator)
slice.fill(body, 'x')
- testing.expect(t, os.write_entire_file(fmt.tprintf("%s/solid.bin", root), body) == nil, "could not write")
+ testing.expect(
+ t,
+ os.write_entire_file(fmt.tprintf("%s/solid.bin", root), body) == nil,
+ "could not write",
+ )
tree: shared.Tree
testing.expect_value(t, shared.tree_init(&tree, 1), shared.Error.None)
diff --git a/walk/walk_test.odin b/walk/walk_test.odin
@@ -34,11 +34,19 @@ fixture :: proc(t: ^testing.T, name: string) -> string {
}
testing.expect(t, os.make_directory(root) == nil, "could not make the root")
testing.expect(t, os.make_directory(fmt.tprintf("%s/sub", root)) == nil, "could not make sub")
- testing.expect(t, os.make_directory(fmt.tprintf("%s/sub/deep", root)) == nil, "could not make deep")
+ testing.expect(
+ t,
+ os.make_directory(fmt.tprintf("%s/sub/deep", root)) == nil,
+ "could not make deep",
+ )
write(t, fmt.tprintf("%s/a.txt", root), 100)
write(t, fmt.tprintf("%s/sub/b.txt", root), 5000)
write(t, fmt.tprintf("%s/sub/deep/c.txt", root), 1)
- testing.expect(t, os.symlink("sub", fmt.tprintf("%s/link", root)) == nil, "could not make the symlink")
+ testing.expect(
+ t,
+ os.symlink("sub", fmt.tprintf("%s/link", root)) == nil,
+ "could not make the symlink",
+ )
return root
}