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 }