sonar

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

fill.odin (4363B)


      1 package main
      2 
      3 import "core:fmt"
      4 import "core:sync"
      5 import "core:thread"
      6 
      7 import "ntfs"
      8 import "scan"
      9 import "walk"
     10 
     11 /*
     12 Runs a reader into the tree.
     13 
     14 The one place that knows how each reader is driven. A watched scan and an unwatched
     15 one reach the same tree by the same route, rather than by two routes that happen to
     16 agree; the only difference between them is whether the tree is folded as it fills or
     17 once at the end.
     18 
     19 `scanning` is clear once nothing is filling the tree any more. A caller that draws
     20 while it runs watches that; one that does not simply waits for `fill_run` to return.
     21 */
     22 Fill :: struct {
     23 	// What to read, and how.
     24 	target:   scan.Target,
     25 	engine:   scan.Engine,
     26 	opts:     ntfs.Read_Options,
     27 	wcfg:     walk.Config,
     28 	// Whether anything is reading the tree while it fills. A reader that has to fold
     29 	// pays for doing so repeatedly, which is worth it only if someone is looking.
     30 	watched:  bool,
     31 	tree:     ^scan.Tree,
     32 	// The MFT reader's own table, which carries what only NTFS knows. It outlives the
     33 	// tree, whose names are borrowed from it.
     34 	table:    ntfs.Mft,
     35 	// What it left behind.
     36 	mft_err:  ntfs.Error,
     37 	walk_err: walk.Error,
     38 	tree_err: scan.Error,
     39 	reading:  b32, // the table is still being read
     40 	scanning: b32, // the tree is still being filled
     41 }
     42 
     43 fill_destroy :: proc(f: ^Fill) {
     44 	ntfs.mft_destroy(&f.table)
     45 }
     46 
     47 // Run the reader to completion. The one place a reader is chosen.
     48 fill_run :: proc(f: ^Fill) {
     49 	defer sync.atomic_store(&f.scanning, false)
     50 
     51 	switch f.engine {
     52 	case .Mft:
     53 		fill_mft(f)
     54 	case .Walk:
     55 		fill_walk(f)
     56 	case .None:
     57 	}
     58 }
     59 
     60 // Say what went wrong, if anything did. The MFT's failures get their own words
     61 // because they are the ones a reader of them can act on.
     62 fill_report :: proc(f: ^Fill) -> bool {
     63 	if f.mft_err != nil {
     64 		#partial switch f.mft_err {
     65 		case .Access_Denied:
     66 			fmt.eprintln(
     67 				"error: access denied. Reading a raw volume needs an administrator prompt.",
     68 			)
     69 		case .Not_Ntfs:
     70 			fmt.eprintln("error: not an NTFS volume")
     71 		case .Open_Failed:
     72 			fmt.eprintfln("error: could not open %s", f.target.volume)
     73 		case:
     74 			fmt.eprintfln("error: %v", f.mft_err)
     75 		}
     76 		return true
     77 	}
     78 	if f.walk_err != nil {
     79 		fmt.eprintfln(
     80 			"error: walking %s: %v",
     81 			scan.location(f.target, context.temp_allocator),
     82 			f.walk_err,
     83 		)
     84 		return true
     85 	}
     86 	if f.tree_err != nil {
     87 		fmt.eprintfln("error: %v", f.tree_err)
     88 		return true
     89 	}
     90 	return false
     91 }
     92 
     93 /*
     94 Read the table, folding it into the tree.
     95 
     96 read_mft blocks until the whole table is read, so a watched scan gives it a thread and
     97 folds beside it. Either way a record settled part way through misses whatever
     98 extension records added to it afterwards, so every record is folded again at the end
     99 and the tree restated, which is how a watcher learns its totals have to start over.
    100 
    101 The root is named inside the projection rather than afterwards: the table calls it
    102 ".", and a watcher has been known to catch the dot.
    103 */
    104 @(private)
    105 fill_mft :: proc(f: ^Fill) {
    106 	p: ntfs.Projection
    107 	p.root_name = scan.top(f.target)
    108 	defer ntfs.projection_destroy(&p)
    109 
    110 	if f.watched {
    111 		sync.atomic_store(&f.reading, true)
    112 		reader := thread.create_and_start_with_poly_data(f, fill_mft_read)
    113 		if reader == nil {
    114 			f.mft_err = .Open_Failed
    115 			return
    116 		}
    117 		defer thread.destroy(reader)
    118 
    119 		for {
    120 			// Read first, so a table that finishes mid-fold still gets one more pass.
    121 			reading := bool(sync.atomic_load(&f.reading))
    122 			if ntfs.mft_ready(&f.table) {
    123 				if err := ntfs.to_tree(&f.table, f.tree, &p); err != nil {
    124 					f.tree_err = err
    125 					break
    126 				}
    127 			}
    128 			if !reading {
    129 				break
    130 			}
    131 		}
    132 		thread.join(reader)
    133 	} else {
    134 		f.mft_err = ntfs.read_mft(f.target.volume, &f.table, f.opts)
    135 	}
    136 	if f.mft_err != nil || f.tree_err != nil {
    137 		return
    138 	}
    139 
    140 	ntfs.projection_reset(&p)
    141 	if err := ntfs.to_tree(&f.table, f.tree, &p); err != nil {
    142 		f.tree_err = err
    143 		return
    144 	}
    145 	scan.restate(f.tree)
    146 }
    147 
    148 @(private)
    149 fill_mft_read :: proc(f: ^Fill) {
    150 	f.mft_err = ntfs.read_mft(f.target.volume, &f.table, f.opts)
    151 	sync.atomic_store(&f.reading, false)
    152 }
    153 
    154 // Walk directories straight into the tree. Nothing is restated: a walk reaches a child
    155 // through its parent, so a node is final when it is written, watched or not.
    156 @(private)
    157 fill_walk :: proc(f: ^Fill) {
    158 	root := scan.location(f.target, context.allocator)
    159 	defer delete(root)
    160 	f.walk_err = walk.read(root, f.tree, f.wcfg)
    161 }