volume_other.odin (2533B)
1 #+build !windows 2 package ntfs 3 4 import "core:io" 5 import "core:mem" 6 import "core:os" 7 import "core:strings" 8 9 /* 10 Read-only handle to a volume: a block device such as /dev/nvme0n1p3, or a file 11 holding an image of one. 12 13 The path and mode are kept so `volume_clone` can open another handle to the same 14 place. Reads are positional and carry their own offset, so handles never contend over 15 one; a handle each still keeps a worker's lifetime and errors its own. 16 17 Reads pass through the page cache. Bypassing it would need O_DIRECT here and 18 F_NOCACHE on Darwin, and sweeping the Windows equivalent found the two 19 indistinguishable at about 1.9 GB/s, so the mode is carried for a clone to match 20 rather than acted on. 21 */ 22 Volume :: struct { 23 file: ^os.File, 24 name: string, // device or image path, owned 25 mode: IO_Mode, 26 allocator: mem.Allocator, 27 } 28 29 /* 30 Open a volume or an image by path. 31 32 Reading a block device is a matter of permissions on the device rather than of 33 privilege as it is on Windows: membership of the group owning it is what grants it, 34 and a group added to a live session only takes effect on the next login. 35 */ 36 volume_open :: proc( 37 path: string, 38 mode := IO_Mode.Unbuffered, 39 allocator := context.allocator, 40 ) -> ( 41 v: Volume, 42 err: Error, 43 ) { 44 f, open_err := os.open(path, {.Read}) 45 if open_err != nil { 46 if open_err == io.Error.Permission_Denied { 47 return {}, .Access_Denied 48 } 49 return {}, .Open_Failed 50 } 51 name, clone_err := strings.clone(path, allocator) 52 if clone_err != nil { 53 os.close(f) 54 return {}, .Out_Of_Memory 55 } 56 return Volume{file = f, name = name, mode = mode, allocator = allocator}, .None 57 } 58 59 // Another handle to the same volume, for a thread that reads independently. 60 volume_clone :: proc(v: ^Volume, allocator := context.allocator) -> (Volume, Error) { 61 return volume_open(v.name, v.mode, allocator) 62 } 63 64 volume_close :: proc(v: ^Volume) { 65 if v.file != nil { 66 os.close(v.file) 67 } 68 if v.name != "" { 69 delete(v.name, v.allocator) 70 } 71 v^ = {} 72 } 73 74 // Fill `buf` from `offset`. One positional read is capped by the kernel and stops 75 // short of a large buffer, so this repeats until the buffer is full or the volume 76 // ends under it. 77 volume_read_at :: proc(v: ^Volume, buf: []byte, offset: u64) -> Error { 78 done := 0 79 for done < len(buf) { 80 n, read_err := os.read_at(v.file, buf[done:], i64(offset) + i64(done)) 81 if read_err != nil { 82 if read_err == io.Error.EOF { 83 return .Short_Read 84 } 85 return .Read_Failed 86 } 87 if n == 0 { 88 return .Short_Read 89 } 90 done += n 91 } 92 return .None 93 }