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 }