sonar

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

volume_windows.odin (3253B)


      1 #+build windows
      2 package ntfs
      3 
      4 import "core:mem"
      5 import "core:strings"
      6 import win "core:sys/windows"
      7 
      8 /*
      9 Read-only handle to a raw volume (`\\.\C:`) or to an image file containing one.
     10 
     11 The resolved name and mode are kept so `volume_clone` can open another handle to the
     12 same place. Windows serialises I/O on a synchronous handle, so threads reading
     13 concurrently need a handle each or they queue behind one another.
     14 */
     15 Volume :: struct {
     16 	handle:    win.HANDLE,
     17 	name:      string, // device path, owned
     18 	mode:      IO_Mode,
     19 	allocator: mem.Allocator,
     20 }
     21 
     22 @(private)
     23 ERROR_ACCESS_DENIED :: 5
     24 
     25 /*
     26 Open a volume by drive letter (`C`, `C:`, `C:\`) or an NTFS image by file path.
     27 
     28 Volume handles bypass the file system, which is why they need administrator rights
     29 and why every read must be a whole number of sectors at a sector-aligned offset.
     30 FILE_SHARE_WRITE is required: the volume is mounted and in use, and opening it without
     31 sharing writes would fail.
     32 */
     33 volume_open :: proc(
     34 	path: string,
     35 	mode := IO_Mode.Unbuffered,
     36 	allocator := context.allocator,
     37 ) -> (
     38 	v: Volume,
     39 	err: Error,
     40 ) {
     41 	name := path
     42 	if is_drive_spec(path) {
     43 		name = strings.concatenate({`\\.\`, path[:1], ":"}, context.temp_allocator)
     44 	}
     45 	// FILE_FLAG_SEQUENTIAL_SCAN is a hint to the cache manager, so it means nothing
     46 	// once the cache is out of the path.
     47 	flags := win.FILE_FLAG_SEQUENTIAL_SCAN if mode == .Buffered else win.FILE_FLAG_NO_BUFFERING
     48 	wname := win.utf8_to_wstring(name, context.temp_allocator)
     49 	h := win.CreateFileW(
     50 		wname,
     51 		win.GENERIC_READ,
     52 		win.FILE_SHARE_READ | win.FILE_SHARE_WRITE | win.FILE_SHARE_DELETE,
     53 		nil,
     54 		win.OPEN_EXISTING,
     55 		flags,
     56 		nil,
     57 	)
     58 	if h == win.INVALID_HANDLE_VALUE {
     59 		if win.GetLastError() == ERROR_ACCESS_DENIED {
     60 			return {}, .Access_Denied
     61 		}
     62 		return {}, .Open_Failed
     63 	}
     64 	owned, clone_err := strings.clone(name, allocator)
     65 	if clone_err != nil {
     66 		win.CloseHandle(h)
     67 		return {}, .Out_Of_Memory
     68 	}
     69 	return Volume{handle = h, name = owned, mode = mode, allocator = allocator}, .None
     70 }
     71 
     72 // Another handle to the same volume, for a thread that needs to read independently.
     73 volume_clone :: proc(v: ^Volume, allocator := context.allocator) -> (Volume, Error) {
     74 	return volume_open(v.name, v.mode, allocator)
     75 }
     76 
     77 volume_close :: proc(v: ^Volume) {
     78 	if v.handle != nil && v.handle != win.INVALID_HANDLE_VALUE {
     79 		win.CloseHandle(v.handle)
     80 	}
     81 	if v.name != "" {
     82 		delete(v.name, v.allocator)
     83 	}
     84 	v^ = {}
     85 }
     86 
     87 // Fill `buf` from `offset`. Both must be sector-aligned for a raw volume.
     88 volume_read_at :: proc(v: ^Volume, buf: []byte, offset: u64) -> Error {
     89 	done := 0
     90 	for done < len(buf) {
     91 		ov: win.OVERLAPPED
     92 		ov.OffsetFull = offset + u64(done)
     93 		chunk := min(len(buf) - done, 1 << 30)
     94 		n: win.DWORD
     95 		if !win.ReadFile(v.handle, raw_data(buf[done:]), win.DWORD(chunk), &n, &ov) {
     96 			return .Read_Failed
     97 		}
     98 		if n == 0 {
     99 			return .Short_Read
    100 		}
    101 		done += int(n)
    102 	}
    103 	return .None
    104 }
    105 
    106 @(private)
    107 is_drive_spec :: proc(s: string) -> bool {
    108 	if len(s) == 0 || len(s) > 3 {
    109 		return false
    110 	}
    111 	c := s[0]
    112 	is_letter := (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
    113 	if !is_letter {
    114 		return false
    115 	}
    116 	if len(s) >= 2 && s[1] != ':' {
    117 		return false
    118 	}
    119 	if len(s) == 3 && s[2] != '\\' && s[2] != '/' {
    120 		return false
    121 	}
    122 	return true
    123 }