mft.odin (11973B)
1 package ntfs 2 3 import "core:mem" 4 import "core:mem/virtual" 5 import "core:strings" 6 import "core:sync" 7 import "core:unicode/utf16" 8 import "core:unicode/utf8" 9 10 // One row per MFT record. Indexed by record number in Mft.entries. 11 Entry :: struct { 12 parent: u32, // record number of the directory holding `name` 13 parent_sequence: u16, // sequence the parent had when this name was written; mismatch means orphan 14 sequence: u16, 15 link_count: u16, 16 namespace: Name_Space, // of `name` 17 flags: Record_Flags, // .In_Use and .Directory are the ones that matter 18 attributes: File_Attributes, // from $STANDARD_INFORMATION; .Reparse_Point marks junctions and cloud placeholders 19 name: string, // UTF-8; empty when the record has no $FILE_NAME (free records, extension records) 20 size: u64, // logical length of the unnamed $DATA stream 21 allocated: u64, // bytes on disk across every non-resident attribute: data, alternate streams, indexes 22 } 23 24 // A file with several names. The first Win32 or POSIX name becomes Entry.name; the 25 // rest land here so they can be shown but never double-counted. 26 Hard_Link :: struct { 27 record: u32, 28 parent: u32, 29 name: string, 30 } 31 32 Mft_Stats :: struct { 33 records: u64, // record slots in $MFT 34 records_read: u64, // slots that held a FILE record 35 records_bad: u64, // FILE records whose fixups failed 36 in_use: u64, 37 directories: u64, 38 // Files whose unnamed $DATA fits inside their MFT record. They occupy no clusters 39 // of their own, so their bytes are charged to $MFT rather than to their directory. 40 resident_files: u64, 41 resident_bytes: u64, 42 // Clusters $Bitmap reports as allocated; 0 when it could not be read. This is the 43 // file system's own total, independent of the per-file sums. 44 allocated_clusters: u64, 45 // What the read plan decided, from $MFT's own $BITMAP of live record slots. 46 planned_bytes: u64, 47 skipped_bytes: u64, 48 extents: u64, 49 workers: u64, // threads the scan actually ran on 50 // Phase timings, nanoseconds. 51 io_ns: i64, 52 parse_ns: i64, 53 bitmap_ns: i64, 54 } 55 56 /* 57 A record that belongs to a base record elsewhere in the table. 58 59 Its attributes are credited to the base entry, which another worker may own, so it 60 cannot be folded in during a parallel pass. The bytes are copied because the read 61 buffer is reused for the next chunk. 62 */ 63 Deferred :: struct { 64 record: u32, 65 bytes: []byte, 66 } 67 68 /* 69 Where one worker puts what it produces. 70 71 Entries are written straight into the table at their own record numbers, which no 72 two workers share, so only these need merging afterwards. The arena outlives the run 73 because every `Entry.name` points into it. 74 */ 75 Sink :: struct { 76 names: virtual.Arena, 77 links: [dynamic]Hard_Link, 78 deferred: [dynamic]Deferred, 79 stats: Mft_Stats, 80 } 81 82 Mft :: struct { 83 entries: []Entry, 84 links: [dynamic]Hard_Link, 85 sinks: []Sink, // one per worker; kept alive for the names they hold 86 boot: Boot_Sector, 87 bytes_per_cluster: u64, 88 stats: Mft_Stats, 89 allocator: mem.Allocator, 90 ready: b32, // the table exists and may be read while it fills 91 complete: b32, // nothing further will be added to it 92 } 93 94 mft_init :: proc( 95 m: ^Mft, 96 record_count: int, 97 bytes_per_cluster: u64, 98 sinks := 1, 99 allocator := context.allocator, 100 ) -> Error { 101 m.allocator = allocator 102 m.bytes_per_cluster = bytes_per_cluster 103 entries, err := make([]Entry, record_count, allocator) 104 if err != nil { 105 return .Out_Of_Memory 106 } 107 m.entries = entries 108 m.links = make([dynamic]Hard_Link, allocator) 109 m.sinks = make([]Sink, max(sinks, 1), allocator) 110 for &s in m.sinks { 111 if virtual.arena_init_growing(&s.names) != nil { 112 mft_destroy(m) 113 return .Out_Of_Memory 114 } 115 s.links.allocator = allocator 116 s.deferred.allocator = allocator 117 } 118 m.stats.records = u64(record_count) 119 return .None 120 } 121 122 mft_destroy :: proc(m: ^Mft) { 123 delete(m.entries, m.allocator) 124 delete(m.links) 125 for &s in m.sinks { 126 delete(s.links) 127 delete(s.deferred) 128 virtual.arena_destroy(&s.names) 129 } 130 delete(m.sinks, m.allocator) 131 m^ = {} 132 } 133 134 // Say the table is sized and safe to watch. Until then there is nothing to read. 135 mft_set_ready :: proc(m: ^Mft) { 136 sync.atomic_store(&m.ready, true) 137 } 138 139 mft_ready :: proc(m: ^Mft) -> bool { 140 return bool(sync.atomic_load(&m.ready)) 141 } 142 143 // Say the table is finished. Hard links are merged in at the very end, so anything 144 // reading them concurrently has to wait for this. 145 mft_set_complete :: proc(m: ^Mft) { 146 sync.atomic_store(&m.complete, true) 147 } 148 149 mft_complete :: proc(m: ^Mft) -> bool { 150 return bool(sync.atomic_load(&m.complete)) 151 } 152 153 /* 154 Whether an entry is finished and safe for another thread to read. 155 156 Folding writes an entry's flags last, so a reader that sees them set is looking at a 157 record whose name and sizes are already in place. Without that order a watcher could 158 catch a half-written name, which is a pointer and a length written separately. 159 */ 160 entry_published :: proc(e: ^Entry) -> bool { 161 return .In_Use in transmute(Record_Flags)sync.atomic_load((^u16)(&e.flags)) 162 } 163 164 /* 165 An entry's parent, and whether that answer can still change. 166 167 A parent record that has not been published yet may turn out to hold this name, so 168 the link is not final until it appears. One that has been published and does not 169 match never will be, which is just as final as a valid answer and lets the node be 170 charged instead of waiting forever. 171 172 The returned index is the entry's own when it has no usable parent. 173 */ 174 entry_parent_settled :: proc(m: ^Mft, i: u32) -> (parent: u32, settled: bool) { 175 e := &m.entries[i] 176 if int(e.parent) >= len(m.entries) || e.parent == i { 177 return i, true 178 } 179 p := &m.entries[e.parent] 180 if !entry_published(p) { 181 return i, false 182 } 183 if .Directory in p.flags && p.sequence == e.parent_sequence { 184 return e.parent, true 185 } 186 return i, true 187 } 188 189 // Fold each worker's private results into the table. Only these needed merging; 190 // entries were written into slots no other worker could reach. 191 mft_merge_sinks :: proc(m: ^Mft) { 192 for &s in m.sinks { 193 m.stats.records_read += s.stats.records_read 194 m.stats.records_bad += s.stats.records_bad 195 m.stats.in_use += s.stats.in_use 196 m.stats.directories += s.stats.directories 197 m.stats.resident_files += s.stats.resident_files 198 m.stats.resident_bytes += s.stats.resident_bytes 199 m.stats.io_ns += s.stats.io_ns 200 m.stats.parse_ns += s.stats.parse_ns 201 for l in s.links { 202 append(&m.links, l) 203 } 204 clear(&s.links) 205 } 206 } 207 208 /* 209 Fold one fixed-up FILE record into the table. Records may arrive in any order. 210 211 An extension record carries attributes that did not fit in its base record (a heavily 212 fragmented file, or one with many hard links). Its header names the base record, so 213 its attributes are credited to the base entry. This makes the $ATTRIBUTE_LIST 214 attribute unnecessary for our purposes: by visiting every record we see every 215 attribute anyway. 216 217 Records not in use are skipped entirely. Their contents are stale and may point at 218 records that have since been reused. 219 */ 220 mft_add_record :: proc(m: ^Mft, record_number: u32, rec: []byte, sink: ^Sink) -> Error { 221 if len(rec) < size_of(Record_Header) { 222 return .Bad_Record 223 } 224 h := record_header(rec) 225 if h.magic != RECORD_MAGIC { 226 return .Bad_Record 227 } 228 if .In_Use not_in h.flags { 229 return .None 230 } 231 232 owner := record_number 233 if h.base_record != 0 { 234 owner = u32(ref_record(h.base_record)) 235 if int(owner) >= len(m.entries) { 236 return .None 237 } 238 } 239 if int(owner) >= len(m.entries) { 240 return .Bad_Record 241 } 242 e := &m.entries[owner] 243 publish := owner == record_number 244 if publish { 245 e.sequence = h.sequence 246 e.link_count = h.link_count 247 sink.stats.in_use += 1 248 if .Directory in h.flags { 249 sink.stats.directories += 1 250 } 251 } 252 253 it := attributes(rec) 254 for { 255 a, ok := next_attribute(&it) 256 if !ok { 257 break 258 } 259 #partial switch a.type { 260 case .Standard_Information: 261 if si, si_ok := standard_information(a); si_ok { 262 e.attributes = si.file_attributes 263 } 264 case .File_Name: 265 if fn, fn_ok := file_name(a); fn_ok { 266 add_name(m, owner, fn, sink) 267 } 268 case .Data: 269 if a.non_resident { 270 // Every extent contributes its own clusters, but only the first carries the 271 // logical size; later extents repeat it. 272 e.allocated += attr_disk_size(a, m.bytes_per_cluster) 273 if a.lowest_vcn == 0 && len(a.name) == 0 { 274 e.size = a.data_size 275 } 276 } else if len(a.name) == 0 { 277 e.size = u64(len(a.value)) 278 sink.stats.resident_files += 1 279 sink.stats.resident_bytes += u64(len(a.value)) 280 } 281 case: 282 // Directory indexes, bitmaps, reparse data, and EFS streams occupy clusters too. 283 e.allocated += attr_disk_size(a, m.bytes_per_cluster) 284 } 285 } 286 // Last, and atomically: this is what tells a watching thread the entry is whole. 287 if publish { 288 sync.atomic_store((^u16)(&e.flags), transmute(u16)h.flags) 289 } 290 return .None 291 } 292 293 /* 294 Choose which $FILE_NAME becomes the entry's name. 295 296 A file usually has one Win32 name and, when that name does not fit 8.3, a DOS alias 297 pointing at the same parent. The alias is not a separate link, so it is skipped 298 whenever a proper name exists. Any further Win32 or POSIX name is a real hard link. 299 */ 300 @(private) 301 add_name :: proc(m: ^Mft, owner: u32, fn: File_Name, sink: ^Sink) { 302 e := &m.entries[owner] 303 parent := u32(ref_record(fn.parent)) 304 parent_seq := ref_sequence(fn.parent) 305 306 if e.name == "" { 307 e.name = intern_utf16(sink, fn.name) 308 e.parent = parent 309 e.parent_sequence = parent_seq 310 e.namespace = fn.namespace 311 return 312 } 313 if fn.namespace == .Dos { 314 return 315 } 316 if e.namespace == .Dos { 317 // The alias arrived first; the real name replaces it. 318 e.name = intern_utf16(sink, fn.name) 319 e.parent = parent 320 e.parent_sequence = parent_seq 321 e.namespace = fn.namespace 322 return 323 } 324 name := intern_utf16(sink, fn.name) 325 if name == e.name && parent == e.parent { 326 return 327 } 328 append(&sink.links, Hard_Link{record = owner, parent = parent, name = name}) 329 } 330 331 // Copy a UTF-16LE name into the arena as UTF-8. Names are at most 255 code units. 332 @(private) 333 intern_utf16 :: proc(sink: ^Sink, name: []u16) -> string { 334 runes: [256]rune 335 n := utf16.decode(runes[:], name) 336 total := 0 337 for r in runes[:n] { 338 total += utf8.rune_size(r) 339 } 340 buf, err := make([]byte, total, virtual.arena_allocator(&sink.names)) 341 if err != nil { 342 return "" 343 } 344 off := 0 345 for r in runes[:n] { 346 enc, size := utf8.encode_rune(r) 347 copy(buf[off:], enc[:size]) 348 off += size 349 } 350 return string(buf) 351 } 352 353 // True when the entry's parent reference still points at the record it was written for. 354 entry_parent_valid :: proc(m: ^Mft, e: Entry) -> bool { 355 if int(e.parent) >= len(m.entries) { 356 return false 357 } 358 p := m.entries[e.parent] 359 return .In_Use in p.flags && .Directory in p.flags && p.sequence == e.parent_sequence 360 } 361 362 /* 363 Reconstruct the path of a record by following parent references up to the root. 364 The result starts with a backslash and omits the drive letter, e.g. 365 `\Windows\explorer.exe`. Records whose chain does not reach the root (files inside a 366 deleted directory, or a stale parent reference) yield a path rooted at the highest 367 reachable ancestor, prefixed with `<orphan>`. 368 */ 369 mft_path :: proc(m: ^Mft, record: u32, allocator := context.allocator) -> string { 370 chain: [64]u32 371 depth := 0 372 r := record 373 orphan := false 374 for depth < len(chain) { 375 if int(r) >= len(m.entries) || r == RECORD_ROOT { 376 break 377 } 378 e := m.entries[r] 379 chain[depth] = r 380 depth += 1 381 if !entry_parent_valid(m, e) || e.parent == r { 382 orphan = true 383 break 384 } 385 r = e.parent 386 } 387 if depth == len(chain) { 388 orphan = true 389 } 390 391 sb := strings.builder_make(allocator) 392 if orphan { 393 strings.write_string(&sb, "<orphan>") 394 } 395 if depth == 0 { 396 strings.write_string(&sb, `\`) 397 } 398 for i := depth - 1; i >= 0; i -= 1 { 399 strings.write_string(&sb, `\`) 400 strings.write_string(&sb, m.entries[chain[i]].name) 401 } 402 return strings.to_string(sb) 403 }