sonar

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

commit ddd3faabc02a028b253014b1f0025e8410ba74f1
parent ac798c62c06ff9fc7044068c1731ec67c42d27bc
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Fri, 18 Sep 2026 10:34:36 -0400

ntfs: let a volume be reopened for concurrent reads

Windows serialises I/O on a synchronous handle, so threads sharing one would
queue behind each other rather than keeping the drive busy. A volume now
remembers the device path and mode it was opened with, and volume_clone hands
out an independent handle to the same place. Reopening is the only way to get
real concurrency: duplicating the handle would share one file object and
serialise just the same.

Diffstat:
Mntfs/volume_other.odin | 9+++++++--
Mntfs/volume_windows.odin | 33++++++++++++++++++++++++++++-----
2 files changed, 35 insertions(+), 7 deletions(-)

diff --git a/ntfs/volume_other.odin b/ntfs/volume_other.odin @@ -7,8 +7,13 @@ Volume :: struct { _: int, } -volume_open :: proc(path: string, mode := IO_Mode.Unbuffered) -> (v: Volume, err: Error) { - _, _ = path, mode +volume_open :: proc(path: string, mode := IO_Mode.Unbuffered, allocator := context.allocator) -> (v: Volume, err: Error) { + _, _, _ = path, mode, allocator + return {}, .Unsupported_Platform +} + +volume_clone :: proc(v: ^Volume, allocator := context.allocator) -> (Volume, Error) { + _, _ = v, allocator return {}, .Unsupported_Platform } diff --git a/ntfs/volume_windows.odin b/ntfs/volume_windows.odin @@ -1,12 +1,22 @@ #+build windows package ntfs +import "core:mem" import "core:strings" import win "core:sys/windows" -// Read-only handle to a raw volume (`\\.\C:`) or to an image file containing one. +/* +Read-only handle to a raw volume (`\\.\C:`) or to an image file containing one. + +The resolved name and mode are kept so `volume_clone` can open another handle to the +same place. Windows serialises I/O on a synchronous handle, so threads reading +concurrently need a handle each or they queue behind one another. +*/ Volume :: struct { - handle: win.HANDLE, + handle: win.HANDLE, + name: string, // device path, owned + mode: IO_Mode, + allocator: mem.Allocator, } @(private) @@ -20,7 +30,7 @@ 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) -> (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) @@ -44,14 +54,27 @@ volume_open :: proc(path: string, mode := IO_Mode.Unbuffered) -> (v: Volume, err } return {}, .Open_Failed } - return Volume{handle = h}, .None + owned, clone_err := strings.clone(name, allocator) + if clone_err != nil { + win.CloseHandle(h) + return {}, .Out_Of_Memory + } + return Volume{handle = h, name = owned, mode = mode, allocator = allocator}, .None +} + +// Another handle to the same volume, for a thread that needs to read independently. +volume_clone :: proc(v: ^Volume, allocator := context.allocator) -> (Volume, Error) { + return volume_open(v.name, v.mode, allocator) } volume_close :: proc(v: ^Volume) { if v.handle != nil && v.handle != win.INVALID_HANDLE_VALUE { win.CloseHandle(v.handle) } - v.handle = nil + if v.name != "" { + delete(v.name, v.allocator) + } + v^ = {} } // Fill `buf` from `offset`. Both must be sector-aligned for a raw volume.