sonar

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

target_windows.odin (4929B)


      1 #+build windows
      2 package scan
      3 
      4 import "core:strings"
      5 import win "core:sys/windows"
      6 
      7 foreign import kernel32 "system:Kernel32.lib"
      8 
      9 @(default_calling_convention = "system")
     10 foreign kernel32 {
     11 	// Not bound by core:sys/windows, so declared here.
     12 	GetVolumePathNameW    :: proc(lpszFileName: win.LPCWSTR, lpszVolumePathName: win.LPWSTR, cchBufferLength: win.DWORD) -> win.BOOL ---
     13 	GetVolumeInformationW :: proc(lpRootPathName: win.LPCWSTR, lpVolumeNameBuffer: win.LPWSTR, nVolumeNameSize: win.DWORD, lpVolumeSerialNumber: ^win.DWORD, lpMaximumComponentLength: ^win.DWORD, lpFileSystemFlags: ^win.DWORD, lpFileSystemNameBuffer: win.LPWSTR, nFileSystemNameSize: win.DWORD) -> win.BOOL ---
     14 }
     15 
     16 /*
     17 Resolve what was given into a target backends can dispatch on.
     18 
     19 A bare drive letter is accepted for convenience, an existing path is asked about, and
     20 anything else is taken to be an image file holding a volume. Failing here is better
     21 than failing inside a backend, because this is the layer that knows why.
     22 
     23 The reader comes back with the target. Choosing one needs to know whether the volume
     24 opens, which is found out here, so leaving the caller to ask separately would only
     25 mean opening it twice.
     26 */
     27 resolve :: proc(
     28 	input: string,
     29 	allocator := context.allocator,
     30 ) -> (
     31 	t: Target,
     32 	c: Choice,
     33 	err: Error,
     34 ) {
     35 	t.allocator = allocator
     36 	t.input = strings.clone(input, allocator)
     37 
     38 	path := input
     39 	if is_drive_spec(input) {
     40 		path = strings.concatenate({input[:1], `:\`}, context.temp_allocator)
     41 	}
     42 	wpath := win.utf8_to_wstring(path, context.temp_allocator)
     43 
     44 	attrs := win.GetFileAttributesW(wpath)
     45 	if attrs == win.INVALID_FILE_ATTRIBUTES {
     46 		target_destroy(&t)
     47 		return {}, {}, .Target_Not_Found
     48 	}
     49 
     50 	// A file that is not a directory is taken to be an image of a volume. Nothing
     51 	// here can say which filesystem is inside it, so a backend has to look.
     52 	if attrs & win.FILE_ATTRIBUTE_DIRECTORY == 0 {
     53 		t.image = true
     54 		t.volume = strings.clone(path, allocator)
     55 		return t, choose(t, can_open(t.volume)), .None
     56 	}
     57 
     58 	// The mount point tells us which volume the path sits on, which is the only way
     59 	// to be right about a directory mounted from another volume.
     60 	mount_buf: [win.MAX_PATH]u16
     61 	if !GetVolumePathNameW(wpath, &mount_buf[0], len(mount_buf)) {
     62 		target_destroy(&t)
     63 		return {}, {}, .Target_Not_Found
     64 	}
     65 	mount, mount_err := win.wstring_to_utf8(win.wstring(&mount_buf[0]), -1, allocator)
     66 	if mount_err != nil {
     67 		target_destroy(&t)
     68 		return {}, {}, .Out_Of_Memory
     69 	}
     70 	t.mount = mount
     71 
     72 	fs_buf: [win.MAX_PATH]u16
     73 	if GetVolumeInformationW(
     74 		win.wstring(&mount_buf[0]),
     75 		nil,
     76 		0,
     77 		nil,
     78 		nil,
     79 		nil,
     80 		&fs_buf[0],
     81 		len(fs_buf),
     82 	) {
     83 		name, name_err := win.wstring_to_utf8(win.wstring(&fs_buf[0]), -1, context.temp_allocator)
     84 		if name_err == nil {
     85 			t.fs = filesystem_from_name(name)
     86 		}
     87 	}
     88 
     89 	// A mount point of the form `C:\` opens as `\\.\C:`; anything else is a volume
     90 	// mounted into a directory, which needs its own device path and is left alone.
     91 	if len(t.mount) >= 2 && t.mount[1] == ':' {
     92 		t.volume = strings.concatenate({`\\.\`, t.mount[:1], ":"}, allocator)
     93 	}
     94 	if strings.has_prefix(t.mount, `\\`) {
     95 		t.fs = .Network
     96 	}
     97 
     98 	// Whatever of the path lies below the mount point is the subtree to report on.
     99 	if len(path) > len(t.mount) {
    100 		t.root = strings.clone(path[len(t.mount) - 1:], allocator)
    101 	}
    102 	// The mount point answered everything above; nothing has touched the volume
    103 	// itself. Whether that opens is what decides the reader.
    104 	return t, choose(t, can_open(t.volume)), .None
    105 }
    106 
    107 @(private)
    108 filesystem_from_name :: proc(name: string) -> Filesystem {
    109 	switch name {
    110 	case "NTFS":
    111 		return .Ntfs
    112 	case "ReFS":
    113 		return .Refs
    114 	case "exFAT":
    115 		return .Exfat
    116 	case "FAT32", "FAT":
    117 		return .Fat32
    118 	}
    119 	return .Unknown
    120 }
    121 
    122 // `C`, `C:` and `C:\` all mean the same volume.
    123 @(private)
    124 is_drive_spec :: proc(s: string) -> bool {
    125 	if len(s) == 0 || len(s) > 3 {
    126 		return false
    127 	}
    128 	c := s[0]
    129 	if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
    130 		return false
    131 	}
    132 	if len(s) >= 2 && s[1] != ':' {
    133 		return false
    134 	}
    135 	if len(s) == 3 && s[2] != '\\' && s[2] != '/' {
    136 		return false
    137 	}
    138 	return true
    139 }
    140 
    141 /*
    142 Whether a volume opens for raw reading.
    143 
    144 Elevation is the usual reason a raw handle is refused, but an elevated token still
    145 says nothing about a volume that is missing, or locked by something else. Opening it
    146 answers all of those at once and costs one handle.
    147 
    148 FILE_SHARE_WRITE is required: the volume is mounted and in use, so asking to exclude
    149 writers would fail for a reason that has nothing to do with permission.
    150 */
    151 @(private)
    152 can_open :: proc(path: string) -> bool {
    153 	if path == "" {
    154 		return false
    155 	}
    156 	h := win.CreateFileW(
    157 		win.utf8_to_wstring(path, context.temp_allocator),
    158 		win.GENERIC_READ,
    159 		win.FILE_SHARE_READ | win.FILE_SHARE_WRITE | win.FILE_SHARE_DELETE,
    160 		nil,
    161 		win.OPEN_EXISTING,
    162 		0,
    163 		nil,
    164 	)
    165 	if h == win.INVALID_HANDLE_VALUE {
    166 		return false
    167 	}
    168 	win.CloseHandle(h)
    169 	return true
    170 }