sonar

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

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 }