commit f9291789afacefe4e45359a117b4eeac39a009a9
parent 63847c8b4b9d2a2ee39d6042e527be24c4f48ae2
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Sun, 20 Sep 2026 14:15:47 -0300
style: run odinfmt over the repository
Every file now matches the configuration beside it, so a formatted change shows
only what it changed. Formatting only: the release binary's output over /usr,
/usr/share/doc and /home/jfm is unchanged line for line, the tests pass, and a
second pass moves nothing.
debug.odin accounts for most of it. It was written well past a hundred columns
and had never been through a formatter.
Diffstat:
17 files changed, 698 insertions(+), 209 deletions(-)
diff --git a/debug/debug.odin b/debug/debug.odin
@@ -94,7 +94,10 @@ PATTERN_GUARD :: 0xFD
PATTERN_DEAD :: 0xDD
PATTERN_FRESH :: 0xCD
-#assert(GUARD >= 8 && (GUARD & (GUARD - 1)) == 0, "DEBUG_ALLOC_GUARD must be a power of two of at least 8")
+#assert(
+ GUARD >= 8 && (GUARD & (GUARD - 1)) == 0,
+ "DEBUG_ALLOC_GUARD must be a power of two of at least 8",
+)
Issue_Kind :: enum {
Overflow,
@@ -203,7 +206,12 @@ Allocator :: struct {
root: string, // directory of the file that called init; paths print relative to it
}
-init :: proc(da: ^Allocator, backing: mem.Allocator, internals := context.allocator, loc := #caller_location) {
+init :: proc(
+ da: ^Allocator,
+ backing: mem.Allocator,
+ internals := context.allocator,
+ loc := #caller_location,
+) {
da.backing = backing
da.internals = internals
da.live.allocator = internals
@@ -282,7 +290,10 @@ allocator_proc :: proc(
old_memory: rawptr,
old_size: int,
loc := #caller_location,
-) -> (result: []byte, err: mem.Allocator_Error) {
+) -> (
+ result: []byte,
+ err: mem.Allocator_Error,
+) {
da := (^Allocator)(data)
// Anything this allocator does internally must not come back through itself.
context.allocator = da.internals
@@ -300,7 +311,15 @@ allocator_proc :: proc(
return do_resize(da, old_memory, old_size, size, alignment, mode == .Resize, loc)
case .Query_Features:
if set := (^mem.Allocator_Mode_Set)(old_memory); set != nil {
- set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features}
+ set^ = {
+ .Alloc,
+ .Alloc_Non_Zeroed,
+ .Free,
+ .Free_All,
+ .Resize,
+ .Resize_Non_Zeroed,
+ .Query_Features,
+ }
}
return nil, nil
case .Query_Info:
@@ -311,7 +330,16 @@ allocator_proc :: proc(
// Obtain a guarded block from the backing allocator. No bookkeeping.
@(private)
-raw_alloc :: proc(da: ^Allocator, size, alignment: int, zeroed: bool, loc: runtime.Source_Code_Location) -> (l: Live, user: []byte, err: mem.Allocator_Error) {
+raw_alloc :: proc(
+ da: ^Allocator,
+ size, alignment: int,
+ zeroed: bool,
+ loc: runtime.Source_Code_Location,
+) -> (
+ l: Live,
+ user: []byte,
+ err: mem.Allocator_Error,
+) {
if size < 0 {
return {}, nil, .Invalid_Argument
}
@@ -335,12 +363,26 @@ raw_alloc :: proc(da: ^Allocator, size, alignment: int, zeroed: bool, loc: runti
}
sanitizer.address_poison(raw[:front])
sanitizer.address_poison(raw[front + size:])
- l = Live{raw = raw_data(raw), total = total, front = front, size = size, alignment = align}
+ l = Live {
+ raw = raw_data(raw),
+ total = total,
+ front = front,
+ size = size,
+ alignment = align,
+ }
return l, user, nil
}
@(private)
-do_alloc :: proc(da: ^Allocator, size, alignment: int, zeroed: bool, loc: runtime.Source_Code_Location) -> ([]byte, mem.Allocator_Error) {
+do_alloc :: proc(
+ da: ^Allocator,
+ size, alignment: int,
+ zeroed: bool,
+ loc: runtime.Source_Code_Location,
+) -> (
+ []byte,
+ mem.Allocator_Error,
+) {
l, user, alloc_err := raw_alloc(da, size, alignment, zeroed, loc)
if alloc_err != nil {
return nil, alloc_err
@@ -362,7 +404,12 @@ do_alloc :: proc(da: ^Allocator, size, alignment: int, zeroed: bool, loc: runtim
}
@(private)
-do_free :: proc(da: ^Allocator, ptr: rawptr, given_size: int, loc: runtime.Source_Code_Location) -> mem.Allocator_Error {
+do_free :: proc(
+ da: ^Allocator,
+ ptr: rawptr,
+ given_size: int,
+ loc: runtime.Source_Code_Location,
+) -> mem.Allocator_Error {
if ptr == nil {
return nil
}
@@ -370,22 +417,40 @@ do_free :: proc(da: ^Allocator, ptr: rawptr, given_size: int, loc: runtime.Sourc
if !ok {
op := make_site(da, loc)
if d, dead_ok := da.dead[ptr]; dead_ok {
- raise(da, Issue{
- kind = .Double_Free, ptr = ptr, size = d.size,
- alloc = d.alloc, has_alloc = true,
- first_free = d.free, has_first_free = true, by_resize = d.by_resize,
- op = op, stage = "free",
- })
+ raise(
+ da,
+ Issue {
+ kind = .Double_Free,
+ ptr = ptr,
+ size = d.size,
+ alloc = d.alloc,
+ has_alloc = true,
+ first_free = d.free,
+ has_first_free = true,
+ by_resize = d.by_resize,
+ op = op,
+ stage = "free",
+ },
+ )
} else {
raise(da, Issue{kind = .Bad_Free, ptr = ptr, op = op, stage = "free"})
}
return .Invalid_Pointer
}
if given_size != 0 && given_size != l.size {
- raise(da, Issue{
- kind = .Size_Mismatch, ptr = ptr, size = l.size, given_size = given_size,
- alloc = l.alloc, has_alloc = true, op = make_site(da, loc), stage = "free",
- })
+ raise(
+ da,
+ Issue {
+ kind = .Size_Mismatch,
+ ptr = ptr,
+ size = l.size,
+ given_size = given_size,
+ alloc = l.alloc,
+ has_alloc = true,
+ op = make_site(da, loc),
+ stage = "free",
+ },
+ )
}
verify_guards(da, l, "free", loc)
retire(da, l, ptr, loc, false)
@@ -409,7 +474,16 @@ do_free_all :: proc(da: ^Allocator, loc: runtime.Source_Code_Location) {
}
@(private)
-do_resize :: proc(da: ^Allocator, ptr: rawptr, old_size, size, alignment: int, zeroed: bool, loc: runtime.Source_Code_Location) -> ([]byte, mem.Allocator_Error) {
+do_resize :: proc(
+ da: ^Allocator,
+ ptr: rawptr,
+ old_size, size, alignment: int,
+ zeroed: bool,
+ loc: runtime.Source_Code_Location,
+) -> (
+ []byte,
+ mem.Allocator_Error,
+) {
if ptr == nil {
return do_alloc(da, size, alignment, zeroed, loc)
}
@@ -420,22 +494,40 @@ do_resize :: proc(da: ^Allocator, ptr: rawptr, old_size, size, alignment: int, z
if !ok {
op := make_site(da, loc)
if d, dead_ok := da.dead[ptr]; dead_ok {
- raise(da, Issue{
- kind = .Double_Free, ptr = ptr, size = d.size,
- alloc = d.alloc, has_alloc = true,
- first_free = d.free, has_first_free = true, by_resize = d.by_resize,
- op = op, stage = "resize",
- })
+ raise(
+ da,
+ Issue {
+ kind = .Double_Free,
+ ptr = ptr,
+ size = d.size,
+ alloc = d.alloc,
+ has_alloc = true,
+ first_free = d.free,
+ has_first_free = true,
+ by_resize = d.by_resize,
+ op = op,
+ stage = "resize",
+ },
+ )
} else {
raise(da, Issue{kind = .Bad_Resize, ptr = ptr, op = op, stage = "resize"})
}
return nil, .Invalid_Pointer
}
if old_size != 0 && old_size != l.size {
- raise(da, Issue{
- kind = .Size_Mismatch, ptr = ptr, size = l.size, given_size = old_size,
- alloc = l.alloc, has_alloc = true, op = make_site(da, loc), stage = "resize",
- })
+ raise(
+ da,
+ Issue {
+ kind = .Size_Mismatch,
+ ptr = ptr,
+ size = l.size,
+ given_size = old_size,
+ alloc = l.alloc,
+ has_alloc = true,
+ op = make_site(da, loc),
+ stage = "resize",
+ },
+ )
}
verify_guards(da, l, "resize", loc)
@@ -474,7 +566,13 @@ do_resize :: proc(da: ^Allocator, ptr: rawptr, old_size, size, alignment: int, z
// Move a live block into quarantine: poison it, record when and where it died.
@(private)
-retire :: proc(da: ^Allocator, l: Live, ptr: rawptr, loc: runtime.Source_Code_Location, by_resize: bool) {
+retire :: proc(
+ da: ^Allocator,
+ l: Live,
+ ptr: rawptr,
+ loc: runtime.Source_Code_Location,
+ by_resize: bool,
+) {
mem.set(ptr, PATTERN_DEAD, l.size)
// From the user block to the end of the backing allocation is now off limits.
sanitizer.address_poison(ptr, l.total - l.front)
@@ -500,9 +598,14 @@ retire :: proc(da: ^Allocator, l: Live, ptr: rawptr, loc: runtime.Source_Code_Lo
st.last_death = da.phase
}
- da.dead[ptr] = Dead{
- raw = l.raw, total = l.total, front = l.front, size = l.size,
- alloc = l.alloc, free = free_site, by_resize = by_resize,
+ da.dead[ptr] = Dead {
+ raw = l.raw,
+ total = l.total,
+ front = l.front,
+ size = l.size,
+ alloc = l.alloc,
+ free = free_site,
+ by_resize = by_resize,
}
append(&da.dead_queue, ptr)
da.dead_bytes += l.total
@@ -576,31 +679,65 @@ verify_guards :: proc(da: ^Allocator, l: Live, stage: string, loc: runtime.Sourc
user_ptr := rawptr(uintptr(l.raw) + uintptr(l.front))
if c, bad := scan(block[:l.front], PATTERN_GUARD); bad {
c.offset -= l.front
- raise(da, Issue{
- kind = .Underflow, ptr = user_ptr, size = l.size,
- alloc = l.alloc, has_alloc = true, op = make_site(da, loc), stage = stage, corruption = c,
- })
+ raise(
+ da,
+ Issue {
+ kind = .Underflow,
+ ptr = user_ptr,
+ size = l.size,
+ alloc = l.alloc,
+ has_alloc = true,
+ op = make_site(da, loc),
+ stage = stage,
+ corruption = c,
+ },
+ )
refill(l.raw, l.front, PATTERN_GUARD) // report each corruption once
}
if c, bad := scan(block[l.front + l.size:], PATTERN_GUARD); bad {
c.offset += l.size
- raise(da, Issue{
- kind = .Overflow, ptr = user_ptr, size = l.size,
- alloc = l.alloc, has_alloc = true, op = make_site(da, loc), stage = stage, corruption = c,
- })
+ raise(
+ da,
+ Issue {
+ kind = .Overflow,
+ ptr = user_ptr,
+ size = l.size,
+ alloc = l.alloc,
+ has_alloc = true,
+ op = make_site(da, loc),
+ stage = stage,
+ corruption = c,
+ },
+ )
refill(raw_data(block[l.front + l.size:]), GUARD, PATTERN_GUARD)
}
}
@(private)
-verify_poison :: proc(da: ^Allocator, d: Dead, ptr: rawptr, stage: string, loc: runtime.Source_Code_Location) {
+verify_poison :: proc(
+ da: ^Allocator,
+ d: Dead,
+ ptr: rawptr,
+ stage: string,
+ loc: runtime.Source_Code_Location,
+) {
if c, bad := scan(mem.byte_slice(ptr, d.size), PATTERN_DEAD); bad {
- raise(da, Issue{
- kind = .Write_After_Free, ptr = ptr, size = d.size,
- alloc = d.alloc, has_alloc = true,
- first_free = d.free, has_first_free = true, by_resize = d.by_resize,
- op = make_site(da, loc), stage = stage, corruption = c,
- })
+ raise(
+ da,
+ Issue {
+ kind = .Write_After_Free,
+ ptr = ptr,
+ size = d.size,
+ alloc = d.alloc,
+ has_alloc = true,
+ first_free = d.free,
+ has_first_free = true,
+ by_resize = d.by_resize,
+ op = make_site(da, loc),
+ stage = stage,
+ corruption = c,
+ },
+ )
refill(ptr, d.size, PATTERN_DEAD)
}
}
@@ -633,7 +770,11 @@ scan :: proc(b: []byte, pattern: byte) -> (c: Corruption, bad: bool) {
@(private)
make_site :: proc(da: ^Allocator, loc: runtime.Source_Code_Location) -> Site {
- s := Site{loc = loc, phase = da.phase, seq = da.seq}
+ s := Site {
+ loc = loc,
+ phase = da.phase,
+ seq = da.seq,
+ }
when BACKTRACES {
s.bt = trace.capture()
}
@@ -642,10 +783,15 @@ make_site :: proc(da: ^Allocator, loc: runtime.Source_Code_Location) -> Site {
@(private)
site_stats :: proc(da: ^Allocator, loc: runtime.Source_Code_Location) -> ^Site_Stats {
- key := Site_Key{file = loc.file_path, line = loc.line}
+ key := Site_Key {
+ file = loc.file_path,
+ line = loc.line,
+ }
st, ok := &da.sites[key]
if !ok {
- da.sites[key] = Site_Stats{loc = loc}
+ da.sites[key] = Site_Stats {
+ loc = loc,
+ }
st = &da.sites[key]
}
return st
@@ -675,7 +821,11 @@ address is poisoned but not what it was; this adds the block, its allocation sit
and its free site, which is what the reader needs to fix the bug.
*/
@(private)
-asan_death :: proc "c" (pc, bp, sp, addr_unused: rawptr, is_write_unused: i32, access_size_unused: uint) {
+asan_death :: proc "c" (
+ pc, bp, sp, addr_unused: rawptr,
+ is_write_unused: i32,
+ access_size_unused: uint,
+) {
// The runtime invokes this without arguments; the parameters hold whatever was in
// the registers. The report accessors are the reliable source.
_, _, _, _, _, _ = pc, bp, sp, addr_unused, is_write_unused, access_size_unused
@@ -696,7 +846,12 @@ asan_death :: proc "c" (pc, bp, sp, addr_unused: rawptr, is_write_unused: i32, a
}
fmt.eprintln()
- fmt.eprintfln("== debug allocator: about the faulting address %p (%s of %d byte(s)) ==", addr, "write" if is_write else "read", access_size)
+ fmt.eprintfln(
+ "== debug allocator: about the faulting address %p (%s of %d byte(s)) ==",
+ addr,
+ "write" if is_write else "read",
+ access_size,
+ )
a := uintptr(addr)
for user, l in da.live {
lo := uintptr(l.raw)
@@ -707,9 +862,16 @@ asan_death :: proc "c" (pc, bp, sp, addr_unused: rawptr, is_write_unused: i32, a
fmt.eprintln(" the block is still live")
print_site(da, "allocated ", l.alloc)
if l.resizes > 0 {
- fmt.eprintfln(" resized %d time(s), last at %s:%d", l.resizes, display_path(da, l.last_resize.file_path), l.last_resize.line)
+ fmt.eprintfln(
+ " resized %d time(s), last at %s:%d",
+ l.resizes,
+ display_path(da, l.last_resize.file_path),
+ l.last_resize.line,
+ )
}
- fmt.eprintln(" meaning an access outside a live block's bounds. Check the index or length used for this access against the allocation size above.")
+ fmt.eprintln(
+ " meaning an access outside a live block's bounds. Check the index or length used for this access against the allocation size above.",
+ )
return
}
for user, d in da.dead {
@@ -721,13 +883,19 @@ asan_death :: proc "c" (pc, bp, sp, addr_unused: rawptr, is_write_unused: i32, a
print_site(da, "allocated ", d.alloc)
print_site(da, "moved by " if d.by_resize else "freed ", d.free)
if d.by_resize {
- fmt.eprintln(" meaning the block was moved by a resize and this access used the old address. A pointer or slice into a dynamic array was kept across an append; re-fetch it after growing.")
+ fmt.eprintln(
+ " meaning the block was moved by a resize and this access used the old address. A pointer or slice into a dynamic array was kept across an append; re-fetch it after growing.",
+ )
} else {
- fmt.eprintln(" meaning use after free. The accessor holds a dangling pointer to a block freed at the site above.")
+ fmt.eprintln(
+ " meaning use after free. The accessor holds a dangling pointer to a block freed at the site above.",
+ )
}
return
}
- fmt.eprintln(" not inside any block this allocator has handed out or is holding in quarantine")
+ fmt.eprintln(
+ " not inside any block this allocator has handed out or is holding in quarantine",
+ )
fmt.eprintfln(" phase=%s tick=%d issues so far=%d", da.phase, da.seq, len(da.issues))
}
@@ -737,7 +905,11 @@ describe_offset :: proc(a, user: uintptr, size: int) {
case a < user:
fmt.eprintfln(" %d byte(s) before the start of a %d-byte block", user - a, size)
case a >= user + uintptr(size):
- fmt.eprintfln(" %d byte(s) past the end of a %d-byte block", a - (user + uintptr(size)), size)
+ fmt.eprintfln(
+ " %d byte(s) past the end of a %d-byte block",
+ a - (user + uintptr(size)),
+ size,
+ )
case:
fmt.eprintfln(" %d byte(s) into a %d-byte block", a - user, size)
}
@@ -748,20 +920,34 @@ describe_offset :: proc(a, user: uintptr, size: int) {
@(private)
kind_name :: proc(k: Issue_Kind) -> string {
switch k {
- case .Overflow: return "OVERFLOW"
- case .Underflow: return "UNDERFLOW"
- case .Double_Free: return "DOUBLE_FREE"
- case .Write_After_Free: return "WRITE_AFTER_FREE"
- case .Bad_Free: return "BAD_FREE"
- case .Bad_Resize: return "BAD_RESIZE"
- case .Size_Mismatch: return "SIZE_MISMATCH"
+ case .Overflow:
+ return "OVERFLOW"
+ case .Underflow:
+ return "UNDERFLOW"
+ case .Double_Free:
+ return "DOUBLE_FREE"
+ case .Write_After_Free:
+ return "WRITE_AFTER_FREE"
+ case .Bad_Free:
+ return "BAD_FREE"
+ case .Bad_Resize:
+ return "BAD_RESIZE"
+ case .Size_Mismatch:
+ return "SIZE_MISMATCH"
}
return "UNKNOWN"
}
@(private)
print_issue :: proc(da: ^Allocator, iss: Issue) {
- fmt.eprintfln("!! ALLOCATION ISSUE #%d: %s ptr=%p size=%d b detected during %s", iss.id, kind_name(iss.kind), iss.ptr, iss.size, iss.stage)
+ fmt.eprintfln(
+ "!! ALLOCATION ISSUE #%d: %s ptr=%p size=%d b detected during %s",
+ iss.id,
+ kind_name(iss.kind),
+ iss.ptr,
+ iss.size,
+ iss.stage,
+ )
if iss.has_alloc {
print_site(da, "allocated ", iss.alloc)
}
@@ -783,29 +969,67 @@ print_issue :: proc(da: ^Allocator, iss: Issue) {
c := iss.corruption
switch iss.kind {
case .Overflow:
- fmt.eprintfln(" damage %d byte(s) written starting %d byte(s) past the end of the block: %s (guard bytes should read %02x)", c.count, c.offset - iss.size, hex(c), PATTERN_GUARD)
- fmt.eprintln(" meaning something wrote beyond the allocation's last byte between allocation and this check. Look for an off-by-one in a loop bound or a length computed from the wrong variable.")
+ fmt.eprintfln(
+ " damage %d byte(s) written starting %d byte(s) past the end of the block: %s (guard bytes should read %02x)",
+ c.count,
+ c.offset - iss.size,
+ hex(c),
+ PATTERN_GUARD,
+ )
+ fmt.eprintln(
+ " meaning something wrote beyond the allocation's last byte between allocation and this check. Look for an off-by-one in a loop bound or a length computed from the wrong variable.",
+ )
case .Underflow:
- fmt.eprintfln(" damage %d byte(s) written starting %d byte(s) before the block: %s (guard bytes should read %02x)", c.count, -c.offset, hex(c), PATTERN_GUARD)
- fmt.eprintln(" meaning something wrote before the allocation's first byte. Look for a negative index or pointer arithmetic that steps back past the start.")
+ fmt.eprintfln(
+ " damage %d byte(s) written starting %d byte(s) before the block: %s (guard bytes should read %02x)",
+ c.count,
+ -c.offset,
+ hex(c),
+ PATTERN_GUARD,
+ )
+ fmt.eprintln(
+ " meaning something wrote before the allocation's first byte. Look for a negative index or pointer arithmetic that steps back past the start.",
+ )
case .Double_Free:
if iss.by_resize {
- fmt.eprintln(" meaning this address was invalidated when the block was resized (see 'moved by' above); the caller kept the old address across an append or resize. Re-fetch the pointer after growing the container, or reserve capacity up front.")
+ fmt.eprintln(
+ " meaning this address was invalidated when the block was resized (see 'moved by' above); the caller kept the old address across an append or resize. Re-fetch the pointer after growing the container, or reserve capacity up front.",
+ )
} else {
- fmt.eprintln(" meaning the block was already freed. Remove one of the two frees, or set the pointer to nil after the first so a second free is a no-op.")
+ fmt.eprintln(
+ " meaning the block was already freed. Remove one of the two frees, or set the pointer to nil after the first so a second free is a no-op.",
+ )
}
case .Write_After_Free:
- fmt.eprintfln(" damage %d byte(s) written at offset %d after the block was freed: %s (freed memory should read %02x)", c.count, c.offset, hex(c), PATTERN_DEAD)
+ fmt.eprintfln(
+ " damage %d byte(s) written at offset %d after the block was freed: %s (freed memory should read %02x)",
+ c.count,
+ c.offset,
+ hex(c),
+ PATTERN_DEAD,
+ )
if iss.by_resize {
- fmt.eprintln(" meaning the block was moved by a resize (see 'moved by' above) and the old address was written afterwards. A pointer or slice into a dynamic array was kept across an append.")
+ fmt.eprintln(
+ " meaning the block was moved by a resize (see 'moved by' above) and the old address was written afterwards. A pointer or slice into a dynamic array was kept across an append.",
+ )
} else {
- fmt.eprintln(" meaning memory was written after it was freed. The writer holds a dangling pointer; find who still references this block after the free above.")
+ fmt.eprintln(
+ " meaning memory was written after it was freed. The writer holds a dangling pointer; find who still references this block after the free above.",
+ )
}
case .Bad_Free, .Bad_Resize:
- fmt.eprintln(" meaning this pointer was never returned by this allocator. Common causes: freeing memory from an arena or temp allocator with the context allocator, freeing a pointer into the middle of a block, or freeing an uninitialised pointer.")
+ fmt.eprintln(
+ " meaning this pointer was never returned by this allocator. Common causes: freeing memory from an arena or temp allocator with the context allocator, freeing a pointer into the middle of a block, or freeing an uninitialised pointer.",
+ )
case .Size_Mismatch:
- fmt.eprintfln(" damage freed with size %d b but %d b were allocated", iss.given_size, iss.size)
- fmt.eprintln(" meaning delete() was given a slice whose length differs from the allocation, usually a re-sliced or truncated slice. Delete the original slice, or keep the original length.")
+ fmt.eprintfln(
+ " damage freed with size %d b but %d b were allocated",
+ iss.given_size,
+ iss.size,
+ )
+ fmt.eprintln(
+ " meaning delete() was given a slice whose length differs from the allocation, usually a re-sliced or truncated slice. Delete the original slice, or keep the original length.",
+ )
}
}
@@ -826,7 +1050,15 @@ hex :: proc(c: Corruption) -> string {
@(private)
print_site :: proc(da: ^Allocator, label: string, s: Site) {
- fmt.eprintfln(" %s %s:%d proc=%s phase=%s tick=%d", label, display_path(da, s.loc.file_path), s.loc.line, s.loc.procedure, s.phase, s.seq)
+ fmt.eprintfln(
+ " %s %s:%d proc=%s phase=%s tick=%d",
+ label,
+ display_path(da, s.loc.file_path),
+ s.loc.line,
+ s.loc.procedure,
+ s.phase,
+ s.seq,
+ )
when BACKTRACES {
print_frames(da, s.bt, 8)
}
@@ -851,7 +1083,13 @@ Print the exit report to stderr. Safe to call via defer after swapping
context.allocator to this allocator. Verifies every block first, so corruption that
happened after the last free or phase change is still caught.
*/
-report :: proc(da: ^Allocator, max_groups := 20, max_sites := 15, max_frames := 8, loc := #caller_location) {
+report :: proc(
+ da: ^Allocator,
+ max_groups := 20,
+ max_sites := 15,
+ max_frames := 8,
+ loc := #caller_location,
+) {
context.allocator = da.internals
sync.guard(&da.mutex)
verify_all(da, "exit", loc)
@@ -861,9 +1099,23 @@ report :: proc(da: ^Allocator, max_groups := 20, max_sites := 15, max_frames :=
if len(da.issues) == 0 && leaks == 0 {
fmt.eprintln("verdict: clean (no issues, no leaks)")
} else {
- fmt.eprintfln("verdict: %d issue(s), %d leaked block(s) totalling %d b", len(da.issues), leaks, da.live_bytes)
- }
- fmt.eprintfln("allocs=%d frees=%d live=%d live_bytes=%d peak_bytes=%d quarantined=%d issues=%d", da.total_allocs, da.total_frees, leaks, da.live_bytes, da.peak_bytes, len(da.dead), len(da.issues))
+ fmt.eprintfln(
+ "verdict: %d issue(s), %d leaked block(s) totalling %d b",
+ len(da.issues),
+ leaks,
+ da.live_bytes,
+ )
+ }
+ fmt.eprintfln(
+ "allocs=%d frees=%d live=%d live_bytes=%d peak_bytes=%d quarantined=%d issues=%d",
+ da.total_allocs,
+ da.total_frees,
+ leaks,
+ da.live_bytes,
+ da.peak_bytes,
+ len(da.dead),
+ len(da.issues),
+ )
fmt.eprint("phases=")
for p, i in da.phases {
if i > 0 {
@@ -874,16 +1126,33 @@ report :: proc(da: ^Allocator, max_groups := 20, max_sites := 15, max_frames :=
fmt.eprintln()
if len(da.issues) > 0 {
- fmt.eprintfln("-- issues (%d, each printed in full where it was detected) --", len(da.issues))
+ fmt.eprintfln(
+ "-- issues (%d, each printed in full where it was detected) --",
+ len(da.issues),
+ )
for iss in da.issues {
fmt.eprintf("#%d %s ptr=%p", iss.id, kind_name(iss.kind), iss.ptr)
if iss.has_alloc {
- fmt.eprintf(" alloc %s:%d", display_path(da, iss.alloc.loc.file_path), iss.alloc.loc.line)
+ fmt.eprintf(
+ " alloc %s:%d",
+ display_path(da, iss.alloc.loc.file_path),
+ iss.alloc.loc.line,
+ )
}
if iss.has_first_free {
- fmt.eprintf(" %s %s:%d", "resize" if iss.by_resize else "free", display_path(da, iss.first_free.loc.file_path), iss.first_free.loc.line)
+ fmt.eprintf(
+ " %s %s:%d",
+ "resize" if iss.by_resize else "free",
+ display_path(da, iss.first_free.loc.file_path),
+ iss.first_free.loc.line,
+ )
}
- fmt.eprintfln(" detected %s:%d (%s)", display_path(da, iss.op.loc.file_path), iss.op.loc.line, iss.stage)
+ fmt.eprintfln(
+ " detected %s:%d (%s)",
+ display_path(da, iss.op.loc.file_path),
+ iss.op.loc.line,
+ iss.stage,
+ )
}
}
@@ -898,13 +1167,19 @@ print_leaks :: proc(da: ^Allocator, max_groups, max_frames: int) {
groups := make(map[Site_Key]Leak_Group, da.internals)
defer delete(groups)
for _, l in da.live {
- key := Site_Key{file = l.alloc.loc.file_path, line = l.alloc.loc.line}
+ key := Site_Key {
+ file = l.alloc.loc.file_path,
+ line = l.alloc.loc.line,
+ }
g, exists := &groups[key]
if !exists {
- groups[key] = Leak_Group{
- loc = l.alloc.loc, phase = l.alloc.phase,
- min_size = l.size, max_size = l.size,
- first_seq = l.alloc.seq, example = l.alloc.bt,
+ groups[key] = Leak_Group {
+ loc = l.alloc.loc,
+ phase = l.alloc.phase,
+ min_size = l.size,
+ max_size = l.size,
+ first_seq = l.alloc.seq,
+ example = l.alloc.bt,
}
g = &groups[key]
}
@@ -935,17 +1210,38 @@ print_leaks :: proc(da: ^Allocator, max_groups, max_frames: int) {
fmt.eprintfln("-- leaks (%d group(s), %d block(s)) --", len(list), len(da.live))
for g, i in list {
if i >= max_groups {
- fmt.eprintfln("... %d more leak groups omitted (raise max_groups to see them)", len(list) - i)
+ fmt.eprintfln(
+ "... %d more leak groups omitted (raise max_groups to see them)",
+ len(list) - i,
+ )
break
}
if g.min_size == g.max_size {
fmt.eprintf("LEAK %d x %d b = %d b", g.count, g.min_size, g.bytes)
} else {
- fmt.eprintf("LEAK %d block(s), %d b total (sizes %d..%d b)", g.count, g.bytes, g.min_size, g.max_size)
+ fmt.eprintf(
+ "LEAK %d block(s), %d b total (sizes %d..%d b)",
+ g.count,
+ g.bytes,
+ g.min_size,
+ g.max_size,
+ )
}
- fmt.eprintf(" phase=%s site=%s:%d proc=%s first_tick=%d", g.phase, display_path(da, g.loc.file_path), g.loc.line, g.loc.procedure, g.first_seq)
+ fmt.eprintf(
+ " phase=%s site=%s:%d proc=%s first_tick=%d",
+ g.phase,
+ display_path(da, g.loc.file_path),
+ g.loc.line,
+ g.loc.procedure,
+ g.first_seq,
+ )
if g.resizes > 0 {
- fmt.eprintf(" resized=%d (last at %s:%d)", g.resizes, display_path(da, g.last_resize.file_path), g.last_resize.line)
+ fmt.eprintf(
+ " resized=%d (last at %s:%d)",
+ g.resizes,
+ display_path(da, g.last_resize.file_path),
+ g.last_resize.line,
+ )
}
fmt.eprintln()
when BACKTRACES {
@@ -970,17 +1266,36 @@ print_sites :: proc(da: ^Allocator, max_sites: int) {
slice.sort_by(list[:], proc(a, b: Site_Stats) -> bool {
return a.bytes > b.bytes
})
- fmt.eprintfln("-- lifetimes by allocation site (top %d of %d by bytes; lifetime in allocation ticks) --", min(max_sites, len(list)), len(list))
+ fmt.eprintfln(
+ "-- lifetimes by allocation site (top %d of %d by bytes; lifetime in allocation ticks) --",
+ min(max_sites, len(list)),
+ len(list),
+ )
for st, i in list {
if i >= max_sites {
break
}
- fmt.eprintf("SITE %s:%d proc=%s allocs=%d frees=%d live=%d bytes=%d peak_live=%d", display_path(da, st.loc.file_path), st.loc.line, st.loc.procedure, st.allocs, st.frees, st.live, st.bytes, st.peak_live_bytes)
+ fmt.eprintf(
+ "SITE %s:%d proc=%s allocs=%d frees=%d live=%d bytes=%d peak_live=%d",
+ display_path(da, st.loc.file_path),
+ st.loc.line,
+ st.loc.procedure,
+ st.allocs,
+ st.frees,
+ st.live,
+ st.bytes,
+ st.peak_live_bytes,
+ )
if st.resizes > 0 {
fmt.eprintf(" resizes=%d", st.resizes)
}
if st.frees > 0 {
- fmt.eprintf(" lifetime=%d..%d avg %d", st.lifetime_min, st.lifetime_max, st.lifetime_sum / u64(st.frees))
+ fmt.eprintf(
+ " lifetime=%d..%d avg %d",
+ st.lifetime_min,
+ st.lifetime_max,
+ st.lifetime_sum / u64(st.frees),
+ )
if st.cross_phase > 0 {
fmt.eprintf(" cross_phase=%d (last died in %s)", st.cross_phase, st.last_death)
}
@@ -1006,7 +1321,10 @@ print_frames :: proc(da: ^Allocator, bt: trace.Capture_Const, max_frames: int) {
}
locs, err := trace.resolve(bt, da.internals, context.temp_allocator)
if err != nil {
- fmt.eprintfln(" (call chain unavailable: %s; build with -debug)", trace.resolve_err_string(err))
+ fmt.eprintfln(
+ " (call chain unavailable: %s; build with -debug)",
+ trace.resolve_err_string(err),
+ )
return
}
defer trace.locations_destroy(locs, da.internals)
@@ -1033,7 +1351,12 @@ print_frames :: proc(da: ^Allocator, bt: trace.Capture_Const, max_frames: int) {
fmt.eprintln(" <- ...")
break
}
- fmt.eprintfln(" <- %s %s:%d", short_proc(l.procedure), display_path(da, l.file_path), l.line)
+ fmt.eprintfln(
+ " <- %s %s:%d",
+ short_proc(l.procedure),
+ display_path(da, l.file_path),
+ l.line,
+ )
shown += 1
}
}
diff --git a/flow/flow.odin b/flow/flow.odin
@@ -29,19 +29,24 @@ allocator the work needs belongs in `State`.
An item should cost more than the thirty microseconds it takes to start a thread.
*/
-each :: proc(items: []$I, states: []$S, work: proc(item: I, state: ^S) -> bool, load := Load.Mixed) {
+each :: proc(
+ items: []$I,
+ states: []$S,
+ work: proc(item: I, state: ^S) -> bool,
+ load := Load.Mixed,
+) {
if len(items) == 0 || len(states) == 0 {
return
}
-
+
shared := Shared(I, S) {
items = items,
states = states,
work = work,
}
-
+
pool := min(len(states), width(len(items), load))
-
+
if pool == 1 {
claim_loop(&shared, 0)
return
@@ -49,7 +54,7 @@ each :: proc(items: []$I, states: []$S, work: proc(item: I, state: ^S) -> bool,
threads := make([]^thread.Thread, pool - 1, context.temp_allocator)
defer delete(threads, context.temp_allocator)
-
+
started := 0
for i in 0 ..< len(threads) {
t := thread.create_and_start_with_poly_data(Arg(I, S){&shared, i + 1}, worker_entry)
@@ -72,9 +77,9 @@ each :: proc(items: []$I, states: []$S, work: proc(item: I, state: ^S) -> bool,
// How a piece of work divides between waiting and computing, which is the part of
// the width decision that only the caller knows.
Load :: enum {
- Cpu, // computing throughout: more workers than cores only makes them compete
+ Cpu, // computing throughout: more workers than cores only makes them compete
Mixed, // alternates between the two, the common case
- Io, // mostly parked in a device call, using no core while it waits
+ Io, // mostly parked in a device call, using no core while it waits
}
/*
diff --git a/flow/flow_test.odin b/flow/flow_test.odin
@@ -66,15 +66,19 @@ test_each_shares_the_work_out :: proc(t: ^testing.T) {
// says nothing. That is a property of the work, not of the claiming.
tallies := make([]Tally, 4, context.temp_allocator)
items := make([]int, 32, context.temp_allocator)
- each(items, tallies, proc(item: int, tally: ^Tally) -> bool {
- acc := 0
- for i in 0 ..< 1_000_000 {
- acc += i ~ item
- }
- tally.handled += 1
- tally.sum += acc & 1 // consume acc so the loop cannot be optimised away
- return true
- })
+ each(
+ items,
+ tallies,
+ proc(item: int, tally: ^Tally) -> bool {
+ acc := 0
+ for i in 0 ..< 1_000_000 {
+ acc += i ~ item
+ }
+ tally.handled += 1
+ tally.sum += acc & 1 // consume acc so the loop cannot be optimised away
+ return true
+ },
+ )
testing.expect(t, busy(tallies) > 1, "work stayed on a single worker")
}
@@ -169,7 +173,11 @@ test_each_ignores_a_pathological_width :: proc(t: ^testing.T) {
handled, sum := totals(tallies)
testing.expect_value(t, handled, ITEMS)
testing.expect_value(t, sum, ITEMS * (ITEMS - 1) / 2)
- testing.expect(t, busy(tallies) <= width(ITEMS, .Mixed), "the pool grew past the default ceiling")
+ testing.expect(
+ t,
+ busy(tallies) <= width(ITEMS, .Mixed),
+ "the pool grew past the default ceiling",
+ )
}
@(test)
diff --git a/live.odin b/live.odin
@@ -44,7 +44,12 @@ run_live :: proc(volume: string, mount: string, opts: ntfs.Read_Options) -> int
out: scan.Publisher
started := time.tick_now()
- r := Reader{volume = volume, opts = opts, table = &m, scanning = true}
+ r := Reader {
+ volume = volume,
+ opts = opts,
+ table = &m,
+ scanning = true,
+ }
b := Builder {
table = &m,
tree = &t,
diff --git a/ntfs/bitmap.odin b/ntfs/bitmap.odin
@@ -23,7 +23,10 @@ read_bitmap_clusters :: proc(
boot: Boot_Sector,
scratch: []byte,
allocator: mem.Allocator,
-) -> (clusters: u64, err: Error) {
+) -> (
+ clusters: u64,
+ err: Error,
+) {
record_size := int(boot.record_size)
cluster := u64(boot.bytes_per_cluster)
if len(scratch) < record_size || u64(len(scratch)) < cluster {
@@ -82,7 +85,11 @@ read_bitmap_clusters :: proc(
// the prefix that maps to real clusters.
read_limit := min((limit + cluster - 1) / cluster * cluster, covered)
- br := Extent_Reader{v = mft.v, runs = runs, cluster = cluster}
+ br := Extent_Reader {
+ v = mft.v,
+ runs = runs,
+ cluster = cluster,
+ }
for offset: u64 = 0; offset < read_limit; {
n := int(min(u64(len(scratch)), read_limit - offset))
if read_err := read_logical(&br, offset, scratch[:n]); read_err != nil {
diff --git a/ntfs/boot.odin b/ntfs/boot.odin
@@ -54,7 +54,9 @@ parse_boot_sector :: proc(b: []byte) -> (bs: Boot_Sector, err: Error) {
// A record must be a whole number of sectors so it can be read directly from the
// volume, and at least two fixup blocks so the update sequence array has something
// to protect.
- if bs.record_size < 512 || !is_pow2(bs.record_size) || bs.record_size % bs.bytes_per_sector != 0 {
+ if bs.record_size < 512 ||
+ !is_pow2(bs.record_size) ||
+ bs.record_size % bs.bytes_per_sector != 0 {
return {}, .Bad_Boot_Sector
}
if bs.mft_lcn == 0 || bs.total_sectors == 0 {
diff --git a/ntfs/mft.odin b/ntfs/mft.odin
@@ -90,7 +90,13 @@ Mft :: struct {
ready: b32, // the table exists and may be read while it fills
}
-mft_init :: proc(m: ^Mft, record_count: int, bytes_per_cluster: u64, sinks := 1, allocator := context.allocator) -> Error {
+mft_init :: proc(
+ m: ^Mft,
+ record_count: int,
+ bytes_per_cluster: u64,
+ sinks := 1,
+ allocator := context.allocator,
+) -> Error {
m.allocator = allocator
m.bytes_per_cluster = bytes_per_cluster
entries, err := make([]Entry, record_count, allocator)
diff --git a/ntfs/ntfs.odin b/ntfs/ntfs.odin
@@ -22,18 +22,21 @@ Layout on disk, in the order this package consumes it:
*/
package ntfs
-#assert(ODIN_ENDIAN == .Little, "NTFS structures are little-endian; this package reads them in place")
+#assert(
+ ODIN_ENDIAN == .Little,
+ "NTFS structures are little-endian; this package reads them in place",
+)
Error :: enum {
None,
- Not_Ntfs, // boot sector OEM id is not "NTFS "
- Bad_Boot_Sector, // sizes in the boot sector are inconsistent
- Bad_Record, // FILE record magic or fixups do not match
- Bad_Runlist, // mapping pairs are malformed or a read fell outside them
- Mft_Data_Missing, // record 0 has no unnamed non-resident $DATA
+ Not_Ntfs, // boot sector OEM id is not "NTFS "
+ Bad_Boot_Sector, // sizes in the boot sector are inconsistent
+ Bad_Record, // FILE record magic or fixups do not match
+ Bad_Runlist, // mapping pairs are malformed or a read fell outside them
+ Mft_Data_Missing, // record 0 has no unnamed non-resident $DATA
Mft_Spans_Extension_Records, // $MFT's $DATA continues in extension records (attribute list); unsupported
Open_Failed,
- Access_Denied, // volume handles need administrator rights
+ Access_Denied, // volume handles need administrator rights
Read_Failed,
Short_Read,
Unsupported_Platform,
@@ -57,18 +60,18 @@ make_ref :: proc "contextless" (record: u64, sequence: u16) -> File_Ref {
}
// Well-known record numbers. Records below FIRST_USER_RECORD belong to the file system.
-RECORD_MFT :: 0
+RECORD_MFT :: 0
RECORD_MFT_MIRROR :: 1
-RECORD_LOG_FILE :: 2
-RECORD_VOLUME :: 3
-RECORD_ATTR_DEF :: 4
-RECORD_ROOT :: 5
-RECORD_BITMAP :: 6
-RECORD_BOOT :: 7
-RECORD_BAD_CLUS :: 8
-RECORD_SECURE :: 9
-RECORD_UPCASE :: 10
-RECORD_EXTEND :: 11
+RECORD_LOG_FILE :: 2
+RECORD_VOLUME :: 3
+RECORD_ATTR_DEF :: 4
+RECORD_ROOT :: 5
+RECORD_BITMAP :: 6
+RECORD_BOOT :: 7
+RECORD_BAD_CLUS :: 8
+RECORD_SECURE :: 9
+RECORD_UPCASE :: 10
+RECORD_EXTEND :: 11
FIRST_USER_RECORD :: 16
// Little-endian readers over byte slices. Slicing panics on out-of-range offsets, so
diff --git a/ntfs/ntfs_test.odin b/ntfs/ntfs_test.odin
@@ -107,7 +107,12 @@ nonresident :: proc(
}
@(private = "file")
-file_name_value :: proc(parent: File_Ref, name: string, ns: Name_Space, attrs: File_Attributes = {}) -> []byte {
+file_name_value :: proc(
+ parent: File_Ref,
+ name: string,
+ ns: Name_Space,
+ attrs: File_Attributes = {},
+) -> []byte {
name16 := utf16_of(name)
b := make([]byte, size_of(File_Name_Header) + 2 * len(name16), context.temp_allocator)
put64(b, 0, u64(parent))
@@ -176,7 +181,11 @@ build_record :: proc(
@(private = "file")
add :: proc(t: ^testing.T, m: ^Mft, rec: []byte) {
testing.expect_value(t, apply_fixups(rec), Error.None)
- testing.expect_value(t, mft_add_record(m, record_header(rec).record_number, rec, &m.sinks[0]), Error.None)
+ testing.expect_value(
+ t,
+ mft_add_record(m, record_header(rec).record_number, rec, &m.sinks[0]),
+ Error.None,
+ )
mft_merge_sinks(m)
}
@@ -322,7 +331,15 @@ test_nonresident_streams :: proc(t: ^testing.T) {
resident(.File_Name, file_name_value(make_ref(64, 3), "big.bin", .Win32_And_Dos)),
nonresident(.Data, 8192, 5000, two_clusters),
nonresident(.Data, 4096, 100, one_cluster, name = "Zone.Identifier"),
- nonresident(.Data, 1 << 20, 1 << 20, compressed, name = "packed", flags = {.Compressed}, compressed_size = 65536),
+ nonresident(
+ .Data,
+ 1 << 20,
+ 1 << 20,
+ compressed,
+ name = "packed",
+ flags = {.Compressed},
+ compressed_size = 65536,
+ ),
},
)
m: Mft
@@ -360,9 +377,18 @@ test_unallocated_runlist_counts_nothing :: proc(t: ^testing.T) {
// $BadClus:$Bad spans the whole volume with sparse runs and no sparse flag.
rec := build_record(
{
- resident(.File_Name, file_name_value(make_ref(RECORD_ROOT, 5), "$BadClus", .Win32_And_Dos)),
+ resident(
+ .File_Name,
+ file_name_value(make_ref(RECORD_ROOT, 5), "$BadClus", .Win32_And_Dos),
+ ),
nonresident(.Data, 0, 0, []byte{0x00}),
- nonresident(.Data, 1 << 40, 1 << 40, []byte{0x04, 0x00, 0x00, 0x00, 0x10, 0x00}, name = "$Bad"),
+ nonresident(
+ .Data,
+ 1 << 40,
+ 1 << 40,
+ []byte{0x04, 0x00, 0x00, 0x00, 0x10, 0x00},
+ name = "$Bad",
+ ),
},
record_number = RECORD_BAD_CLUS,
)
@@ -446,30 +472,46 @@ test_path :: proc(t: ^testing.T) {
defer mft_destroy(&m)
root := make_ref(RECORD_ROOT, 5)
- add(t, &m, build_record(
- {resident(.File_Name, file_name_value(root, ".", .Win32))},
- record_number = RECORD_ROOT,
- sequence = 5,
- flags = {.In_Use, .Directory},
- ))
- add(t, &m, build_record(
- {resident(.File_Name, file_name_value(root, "Windows", .Win32))},
- record_number = 64,
- sequence = 1,
- flags = {.In_Use, .Directory},
- ))
- add(t, &m, build_record(
- {resident(.File_Name, file_name_value(make_ref(64, 1), "explorer.exe", .Win32))},
- record_number = 65,
- sequence = 1,
- ))
+ add(
+ t,
+ &m,
+ build_record(
+ {resident(.File_Name, file_name_value(root, ".", .Win32))},
+ record_number = RECORD_ROOT,
+ sequence = 5,
+ flags = {.In_Use, .Directory},
+ ),
+ )
+ add(
+ t,
+ &m,
+ build_record(
+ {resident(.File_Name, file_name_value(root, "Windows", .Win32))},
+ record_number = 64,
+ sequence = 1,
+ flags = {.In_Use, .Directory},
+ ),
+ )
+ add(
+ t,
+ &m,
+ build_record(
+ {resident(.File_Name, file_name_value(make_ref(64, 1), "explorer.exe", .Win32))},
+ record_number = 65,
+ sequence = 1,
+ ),
+ )
// Parent sequence 99 does not match record 64's sequence 1: the directory this
// file was in has been deleted and its record reused.
- add(t, &m, build_record(
- {resident(.File_Name, file_name_value(make_ref(64, 99), "stale.tmp", .Win32))},
- record_number = 66,
- sequence = 1,
- ))
+ add(
+ t,
+ &m,
+ build_record(
+ {resident(.File_Name, file_name_value(make_ref(64, 99), "stale.tmp", .Win32))},
+ record_number = 66,
+ sequence = 1,
+ ),
+ )
testing.expect_value(t, mft_path(&m, 65, context.temp_allocator), `\Windows\explorer.exe`)
testing.expect_value(t, mft_path(&m, 64, context.temp_allocator), `\Windows`)
diff --git a/ntfs/plan.odin b/ntfs/plan.odin
@@ -32,7 +32,10 @@ plan_reads :: proc(
mft_bytes: u64,
record_size, cluster, min_gap: u64,
allocator: mem.Allocator,
-) -> (extents: []Read_Extent, err: Error) {
+) -> (
+ extents: []Read_Extent,
+ err: Error,
+) {
out := make([dynamic]Read_Extent, allocator)
total_clusters := (mft_bytes + cluster - 1) / cluster
records := mft_bytes / record_size
@@ -68,7 +71,10 @@ plan_reads :: proc(
}
@(private)
-cluster_has_live_record :: proc "contextless" (bitmap: []byte, c, records, record_size, cluster: u64) -> bool {
+cluster_has_live_record :: proc "contextless" (
+ bitmap: []byte,
+ c, records, record_size, cluster: u64,
+) -> bool {
first := c * cluster / record_size
last := ((c + 1) * cluster - 1) / record_size
for r := first; r <= last && r < records; r += 1 {
diff --git a/ntfs/reader.odin b/ntfs/reader.odin
@@ -71,7 +71,12 @@ Open `path` (drive letter or image file) and read its whole MFT into `m`.
The caller owns the table rather than receiving it at the end, so another thread can
watch it fill. `mft_ready` says when there is anything to watch.
*/
-read_mft :: proc(path: string, m: ^Mft, opts := Read_Options{}, allocator := context.allocator) -> Error {
+read_mft :: proc(
+ path: string,
+ m: ^Mft,
+ opts := Read_Options{},
+ allocator := context.allocator,
+) -> Error {
v, open_err := volume_open(path, opts.io_mode)
if open_err != nil {
return open_err
@@ -93,7 +98,12 @@ The MFT is addressed as a contiguous logical byte range and `read_logical` maps
chunk onto physical runs, so a record straddling two runs (possible when a cluster is
smaller than a record) is handled without special cases.
*/
-read_mft_from_volume :: proc(v: ^Volume, m: ^Mft, opts := Read_Options{}, allocator := context.allocator) -> Error {
+read_mft_from_volume :: proc(
+ v: ^Volume,
+ m: ^Mft,
+ opts := Read_Options{},
+ allocator := context.allocator,
+) -> Error {
// 1. Boot sector.
boot_buf, boot_alloc_err := mem.alloc_bytes(IO_ALIGN, IO_ALIGN, allocator)
if boot_alloc_err != nil {
@@ -198,7 +208,14 @@ read_mft_from_volume :: proc(v: ^Volume, m: ^Mft, opts := Read_Options{}, alloca
if opts.min_skip > 0 {
min_skip = u64(opts.min_skip)
}
- if planned, plan_err := plan_reads(slot_bitmap, mft_bytes, u64(record_size), cluster, min_skip, allocator); plan_err == nil {
+ if planned, plan_err := plan_reads(
+ slot_bitmap,
+ mft_bytes,
+ u64(record_size),
+ cluster,
+ min_skip,
+ allocator,
+ ); plan_err == nil {
extents = planned
}
}
@@ -223,7 +240,11 @@ read_mft_from_volume :: proc(v: ^Volume, m: ^Mft, opts := Read_Options{}, alloca
// 4. Size the pool. The limit is the measured one, not the machine's, because the
// drive saturates long before the cores do.
- worker_count := flow.width(len(chunks), .Io, limit = opts.workers if opts.workers > 0 else DEFAULT_WORKERS)
+ worker_count := flow.width(
+ len(chunks),
+ .Io,
+ limit = opts.workers if opts.workers > 0 else DEFAULT_WORKERS,
+ )
record_count := int(mft_bytes / u64(record_size))
if init_err := mft_init(m, record_count, cluster, worker_count, allocator); init_err != nil {
@@ -267,7 +288,11 @@ read_mft_from_volume :: proc(v: ^Volume, m: ^Mft, opts := Read_Options{}, alloca
w.volume = wv
w.owns_volume = true
w.buf = wbuf
- w.reader = Extent_Reader{v = &w.volume, runs = runs, cluster = cluster}
+ w.reader = Extent_Reader {
+ v = &w.volume,
+ runs = runs,
+ cluster = cluster,
+ }
live += 1
}
if live == 0 {
@@ -279,7 +304,11 @@ read_mft_from_volume :: proc(v: ^Volume, m: ^Mft, opts := Read_Options{}, alloca
}
w.volume = v^
w.buf = wbuf
- w.reader = Extent_Reader{v = &w.volume, runs = runs, cluster = cluster}
+ w.reader = Extent_Reader {
+ v = &w.volume,
+ runs = runs,
+ cluster = cluster,
+ }
live = 1
}
defer {
@@ -317,7 +346,8 @@ read_mft_from_volume :: proc(v: ^Volume, m: ^Mft, opts := Read_Options{}, alloca
// $Bitmap is the file system's own count of used clusters, which checks the sums
// built above. Failing to read it costs nothing else, so the table still stands.
bitmap_start := time.tick_now()
- if c, bitmap_err := read_bitmap_clusters(&workers[0].reader, boot, workers[0].buf, allocator); bitmap_err == nil {
+ if c, bitmap_err := read_bitmap_clusters(&workers[0].reader, boot, workers[0].buf, allocator);
+ bitmap_err == nil {
m.stats.allocated_clusters = c
}
m.stats.bitmap_ns = i64(time.tick_since(bitmap_start))
@@ -344,15 +374,15 @@ and are held in the sink rather than written across the divide.
*/
@(private)
Worker :: struct {
- mft: ^Mft,
- sink: ^Sink,
- volume: Volume,
- owns_volume: bool,
- reader: Extent_Reader,
- buf: []byte,
- record_size: int,
- cluster: u64,
- err: Error,
+ mft: ^Mft,
+ sink: ^Sink,
+ volume: Volume,
+ owns_volume: bool,
+ reader: Extent_Reader,
+ buf: []byte,
+ record_size: int,
+ cluster: u64,
+ err: Error,
}
@(private)
@@ -410,7 +440,15 @@ hold_extension_record :: proc(w: ^Worker, record: u32, rec: []byte) {
// Read a whole attribute into memory: a copy of the value when resident, or the
// clusters its run list names when not. The caller frees the result.
@(private)
-read_attribute :: proc(v: ^Volume, a: Attribute, cluster: u64, allocator: mem.Allocator) -> (data: []byte, err: Error) {
+read_attribute :: proc(
+ v: ^Volume,
+ a: Attribute,
+ cluster: u64,
+ allocator: mem.Allocator,
+) -> (
+ data: []byte,
+ err: Error,
+) {
if !a.non_resident {
if len(a.value) == 0 {
return nil, .Bad_Record
@@ -436,7 +474,11 @@ read_attribute :: proc(v: ^Volume, a: Attribute, cluster: u64, allocator: mem.Al
if alloc_err != nil {
return nil, .Out_Of_Memory
}
- r := Extent_Reader{v = v, runs = runs, cluster = cluster}
+ r := Extent_Reader {
+ v = v,
+ runs = runs,
+ cluster = cluster,
+ }
if read_err := read_logical(&r, 0, out); read_err != nil {
mem.free_bytes(out, allocator)
return nil, read_err
@@ -476,7 +518,8 @@ read_logical :: proc(r: ^Extent_Reader, offset: u64, buf: []byte) -> Error {
in_run := pos - run.vcn * r.cluster
avail := run.length * r.cluster - in_run
n := int(min(u64(len(buf) - done), avail))
- if read_err := volume_read_at(r.v, buf[done:done + n], run.lcn * r.cluster + in_run); read_err != nil {
+ if read_err := volume_read_at(r.v, buf[done:done + n], run.lcn * r.cluster + in_run);
+ read_err != nil {
return read_err
}
done += n
diff --git a/ntfs/record.odin b/ntfs/record.odin
@@ -2,19 +2,19 @@ package ntfs
import "core:mem"
-RECORD_MAGIC :: u32(0x454C4946) // "FILE"
+RECORD_MAGIC :: u32(0x454C4946) // "FILE"
RECORD_MAGIC_BAD :: u32(0x44414142) // "BAAD": chkdsk marked the record as damaged
// Fixups protect every 512 bytes regardless of the physical sector size.
FIXUP_BLOCK :: 512
-Record_Flag :: enum u16 {
+Record_Flag :: enum u16 {
In_Use = 0,
Directory = 1,
Extension = 2, // record lives in $Extend
View_Index = 3,
}
-Record_Flags :: bit_set[Record_Flag; u16]
+Record_Flags :: bit_set[Record_Flag;u16]
Record_Header :: struct #packed {
magic: u32,
@@ -53,12 +53,12 @@ Attr_Type :: enum u32 {
End = 0xFFFFFFFF,
}
-Attr_Flag :: enum u16 {
+Attr_Flag :: enum u16 {
Compressed = 0,
Encrypted = 14,
Sparse = 15,
}
-Attr_Flags :: bit_set[Attr_Flag; u16]
+Attr_Flags :: bit_set[Attr_Flag;u16]
Attr_Header :: struct #packed {
type: Attr_Type,
@@ -239,7 +239,8 @@ next_attribute :: proc(it: ^Attr_Iterator) -> (a: Attribute, ok: bool) {
a.data_size = n.data_size
a.initialized_size = n.initialized_size
a.compressed_size = n.allocated_size
- if (.Compressed in hdr.flags || .Sparse in hdr.flags) && length >= size_of(Nonresident_Header) + 8 {
+ if (.Compressed in hdr.flags || .Sparse in hdr.flags) &&
+ length >= size_of(Nonresident_Header) + 8 {
a.compressed_size = rd64(raw, size_of(Nonresident_Header))
}
ro := int(n.runlist_offset)
@@ -254,7 +255,7 @@ next_attribute :: proc(it: ^Attr_Iterator) -> (a: Attribute, ok: bool) {
}
// Windows FILE_ATTRIBUTE_* bits as stored in $STANDARD_INFORMATION and $FILE_NAME.
-File_Attribute :: enum u32 {
+File_Attribute :: enum u32 {
Read_Only = 0,
Hidden = 1,
System = 2,
@@ -279,7 +280,7 @@ File_Attribute :: enum u32 {
Directory_Index = 28, // NTFS-internal: record has an $I30 index
View_Index = 29,
}
-File_Attributes :: bit_set[File_Attribute; u32]
+File_Attributes :: bit_set[File_Attribute;u32]
Standard_Information :: struct #packed {
created: u64, // FILETIME
@@ -295,7 +296,9 @@ Standard_Information :: struct #packed {
#assert(size_of(Standard_Information) == 48)
standard_information :: proc(a: Attribute) -> (si: Standard_Information, ok: bool) {
- if a.type != .Standard_Information || a.non_resident || len(a.value) < size_of(Standard_Information) {
+ if a.type != .Standard_Information ||
+ a.non_resident ||
+ len(a.value) < size_of(Standard_Information) {
return
}
return (^Standard_Information)(raw_data(a.value))^, true
@@ -342,6 +345,9 @@ file_name :: proc(a: Attribute) -> (fn: File_Name, ok: bool) {
fn.parent = h.parent
fn.file_attributes = h.file_attributes
fn.namespace = h.namespace
- fn.name = mem.slice_data_cast([]u16, a.value[size_of(File_Name_Header):size_of(File_Name_Header) + n])
+ fn.name = mem.slice_data_cast(
+ []u16,
+ a.value[size_of(File_Name_Header):size_of(File_Name_Header) + n],
+ )
return fn, true
}
diff --git a/ntfs/runlist.odin b/ntfs/runlist.odin
@@ -19,7 +19,14 @@ sparse. A zero header byte terminates the list.
`first_vcn` is the attribute's lowest_vcn; it is 0 for the first (or only) extent.
*/
-decode_runlist :: proc(b: []byte, first_vcn: u64, allocator := context.allocator) -> (runs: []Run, err: Error) {
+decode_runlist :: proc(
+ b: []byte,
+ first_vcn: u64,
+ allocator := context.allocator,
+) -> (
+ runs: []Run,
+ err: Error,
+) {
out := make([dynamic]Run, allocator)
vcn := first_vcn
lcn: i64 = 0
@@ -42,7 +49,10 @@ decode_runlist :: proc(b: []byte, first_vcn: u64, allocator := context.allocator
delete(out)
return nil, .Bad_Runlist
}
- run := Run{vcn = vcn, length = length}
+ run := Run {
+ vcn = vcn,
+ length = length,
+ }
if ofs_size == 0 {
run.sparse = true
} else {
diff --git a/ntfs/volume_windows.odin b/ntfs/volume_windows.odin
@@ -30,7 +30,14 @@ and why every read must be a whole number of sectors at a sector-aligned offset.
FILE_SHARE_WRITE is required: the volume is mounted and in use, and opening it without
sharing writes would fail.
*/
-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,
+) {
name := path
if is_drive_spec(path) {
name = strings.concatenate({`\\.\`, path[:1], ":"}, context.temp_allocator)
diff --git a/scan/snapshot_test.odin b/scan/snapshot_test.odin
@@ -8,8 +8,15 @@ test_snapshot_round_trips :: proc(t: ^testing.T) {
_, ok := current(&p)
testing.expect(t, ok, "an untouched publisher should still hand out its zero value")
- s := Snapshot{count = 2, nodes = 99, complete = true}
- s.rows[0] = {node = 7, bytes = 4096}
+ s := Snapshot {
+ count = 2,
+ nodes = 99,
+ complete = true,
+ }
+ s.rows[0] = {
+ node = 7,
+ bytes = 4096,
+ }
publish(&p, s)
got, got_ok := current(&p)
diff --git a/scan/target_windows.odin b/scan/target_windows.odin
@@ -9,9 +9,9 @@ foreign import kernel32 "system:Kernel32.lib"
@(default_calling_convention = "system")
foreign kernel32 {
// Not bound by core:sys/windows, so declared here.
- GetVolumePathNameW :: proc(lpszFileName: win.LPCWSTR, lpszVolumePathName: win.LPWSTR, cchBufferLength: win.DWORD) -> win.BOOL ---
+ GetVolumePathNameW :: proc(lpszFileName: win.LPCWSTR, lpszVolumePathName: win.LPWSTR, cchBufferLength: win.DWORD) -> win.BOOL ---
GetVolumeInformationW :: proc(lpRootPathName: win.LPCWSTR, lpVolumeNameBuffer: win.LPWSTR, nVolumeNameSize: win.DWORD, lpVolumeSerialNumber: ^win.DWORD, lpMaximumComponentLength: ^win.DWORD, lpFileSystemFlags: ^win.DWORD, lpFileSystemNameBuffer: win.LPWSTR, nFileSystemNameSize: win.DWORD) -> win.BOOL ---
- GetCurrentProcess :: proc() -> win.HANDLE ---
+ GetCurrentProcess :: proc() -> win.HANDLE ---
}
/*
@@ -60,7 +60,16 @@ resolve :: proc(input: string, allocator := context.allocator) -> (t: Target, er
t.mount = mount
fs_buf: [win.MAX_PATH]u16
- if GetVolumeInformationW(win.wstring(&mount_buf[0]), nil, 0, nil, nil, nil, &fs_buf[0], len(fs_buf)) {
+ if GetVolumeInformationW(
+ win.wstring(&mount_buf[0]),
+ nil,
+ 0,
+ nil,
+ nil,
+ nil,
+ &fs_buf[0],
+ len(fs_buf),
+ ) {
name, name_err := win.wstring_to_utf8(win.wstring(&fs_buf[0]), -1, context.temp_allocator)
if name_err == nil {
t.fs = filesystem_from_name(name)
diff --git a/scan/tree.odin b/scan/tree.odin
@@ -16,7 +16,7 @@ Node :: struct {
flags: Node_Flags,
}
-Node_Flag :: enum u8 {
+Node_Flag :: enum u8 {
Used, // written by a reader; an untouched slot has this clear
// The parent link is final. A reader that learns of a child before its holding
// directory leaves this clear until the parent turns up, because until then the