sonar

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

target.odin (3474B)


      1 package scan
      2 
      3 import "core:mem"
      4 import "core:strings"
      5 
      6 /*
      7 What the user asked to scan, resolved once so no backend has to re-derive it.
      8 
      9 Deciding which volume a path lives on and what filesystem that volume runs is work
     10 for the OS, not string matching, and every backend would otherwise repeat it. Doing
     11 it here also puts the failures that are knowable up front, a path that does not
     12 exist or a share with no volume behind it, in the one place that can explain them.
     13 */
     14 Target :: struct {
     15 	input:     string, // exactly what was given
     16 	volume:    string, // device path to open, e.g. \\.\C:
     17 	mount:     string, // where that volume is mounted, e.g. C:\
     18 	root:      string, // subtree within the volume; "" means all of it
     19 	fs:        Filesystem,
     20 	image:     bool, // a file holding a volume rather than a mounted one
     21 	allocator: mem.Allocator,
     22 }
     23 
     24 Filesystem :: enum {
     25 	Unknown, // nothing could be learned about it
     26 	Ntfs,
     27 	Refs,
     28 	Exfat,
     29 	Fat32,
     30 	Network,
     31 	Other, // named, but no reader here knows it: walking is all that is left
     32 }
     33 
     34 /*
     35 The top of the target, named as a path: where the volume is mounted, or the volume
     36 itself when nothing has it mounted. No trailing separator, so a subtree joins on.
     37 */
     38 top :: proc(t: Target) -> string {
     39 	if t.mount == "" {
     40 		return t.volume
     41 	}
     42 	if len(t.mount) > 1 {
     43 		return strings.trim_right(t.mount, `\/`)
     44 	}
     45 	return t.mount
     46 }
     47 
     48 // The full path of what will be scanned: the top, and the subtree below it.
     49 location :: proc(t: Target, allocator := context.allocator) -> string {
     50 	if t.root == "" {
     51 		return strings.clone(top(t), allocator)
     52 	}
     53 	return strings.concatenate({top(t), t.root}, allocator)
     54 }
     55 
     56 target_destroy :: proc(t: ^Target) {
     57 	delete(t.input, t.allocator)
     58 	delete(t.volume, t.allocator)
     59 	delete(t.mount, t.allocator)
     60 	delete(t.root, t.allocator)
     61 	t^ = {}
     62 }
     63 
     64 // Which reader to use. Named here so choosing needs no knowledge of the backends
     65 // themselves, which keeps this package free of a dependency on any of them.
     66 Engine :: enum {
     67 	None, // nothing here can be scanned
     68 	Mft, // read the NTFS master file table whole
     69 	Walk, // enumerate directories
     70 }
     71 
     72 Choice :: struct {
     73 	engine:                Engine,
     74 	// The MFT reader is far faster but needs to open the volume raw. When this is
     75 	// set, Walk is what will run unless the caller obtains that and asks again.
     76 	permission_would_help: bool,
     77 }
     78 
     79 /*
     80 Pick a reader for a target.
     81 
     82 `readable` says the volume opened for raw reading, which is the only thing standing
     83 between an NTFS target and the MFT reader. `resolve` finds it out and calls this; it
     84 is a separate procedure so the whole decision table can be tested without a disk, not
     85 because anything else needs to choose. An image needs no special case: it is a file,
     86 so it opens.
     87 
     88 The MFT reader takes the whole volume at once, so a subtree costs no more than the
     89 root does and is far cheaper than walking it. That makes a subtree target a reason to
     90 filter the result, never a reason to reject the reader.
     91 */
     92 @(private)
     93 choose :: proc(t: Target, readable: bool) -> Choice {
     94 	if t.fs == .Ntfs {
     95 		if readable {
     96 			return {engine = .Mft}
     97 		}
     98 		return {engine = .Walk, permission_would_help = true}
     99 	}
    100 	// Nothing mounted means no directory tree to enumerate. A raw volume and an image
    101 	// are both bytes until a reader that knows the filesystem inside says otherwise,
    102 	// so an unrecognised one has nothing to offer rather than something to walk.
    103 	if t.mount == "" {
    104 		return {engine = .None}
    105 	}
    106 	return {engine = .Walk}
    107 }