reader.odin (15419B)
1 package ntfs 2 3 import "core:mem" 4 import "core:mem/virtual" 5 import "core:time" 6 7 import "jm:flow" 8 9 // Alignment for I/O buffers. Raw volume reads want sector alignment; 4 KiB covers 10 // every sector size in use. 11 @(private) 12 IO_ALIGN :: 4096 13 14 // Bytes per read. Smaller than a serial reader would want: queue depth comes from 15 // the number of workers, and smaller chunks divide more evenly between them. 16 DEFAULT_CHUNK_SIZE :: 4 * mem.Megabyte 17 18 /* 19 Readers to run concurrently. 20 21 Measured by sweeping this on a live NVMe volume: one reader takes 1807 ms, two take 22 1338, and four through eight all sit at about 1310. Past that it degrades, and 23 thirty-two takes 2339 ms, worse than reading serially. Aggregate read time grows in 24 step with the count throughout, which is what a device already at its limit looks 25 like: extra readers divide the same bandwidth rather than adding to it. 26 27 So this is not a function of the core count. Four sits in the flat part of the curve 28 with margin for a device that does want more depth. 29 */ 30 DEFAULT_WORKERS :: 4 31 32 /* 33 How volume reads reach memory. 34 35 Buffered reads pass through the Windows cache manager, which copies every byte into 36 the system cache and then again into our buffer, and evicts whatever the user had 37 cached to make room. Unbuffered reads land in our buffer directly. The MFT is read 38 once from front to back and never re-read, so the cache earns nothing here. 39 40 Unbuffered reads require the buffer address, the file offset, and the length to be 41 sector aligned, which the reader already satisfies by working in whole clusters. 42 */ 43 IO_Mode :: enum { 44 Unbuffered, // default 45 Buffered, 46 } 47 48 /* 49 Shortest run of dead MFT records worth skipping over. 50 51 Breaking the sequential stream costs about 140 microseconds per extra read, measured 52 by sweeping this value on a live volume, so a skip only pays when the dead run takes 53 longer than that to read. Below a quarter of a megabyte the extra requests cost more 54 than the bytes they save, and at four kilobytes the scan is twice as slow as reading 55 the whole table. Above a megabyte the curve flattens and then slowly worsens as real 56 savings are left on the table. A megabyte sat at the bottom of that curve and also 57 produces the fewest extents, which keeps the work easy to split across threads later. 58 */ 59 DEFAULT_MIN_SKIP :: mem.Megabyte 60 61 Read_Options :: struct { 62 chunk_size: int, // bytes per volume read; 0 selects DEFAULT_CHUNK_SIZE 63 io_mode: IO_Mode, 64 min_skip: int, // 0 selects DEFAULT_MIN_SKIP; negative reads the whole table 65 workers: int, // 0 selects DEFAULT_WORKERS 66 } 67 68 /* 69 Open `path` (drive letter or image file) and read its whole MFT into `m`. 70 71 The caller owns the table rather than receiving it at the end, so another thread can 72 watch it fill. `mft_ready` says when there is anything to watch. 73 */ 74 read_mft :: proc( 75 path: string, 76 m: ^Mft, 77 opts := Read_Options{}, 78 allocator := context.allocator, 79 ) -> Error { 80 v, open_err := volume_open(path, opts.io_mode) 81 if open_err != nil { 82 return open_err 83 } 84 defer volume_close(&v) 85 return read_mft_from_volume(&v, m, opts, allocator) 86 } 87 88 /* 89 Read the MFT in three steps: 90 91 1. Boot sector, for cluster size, record size, and the MFT's first cluster. 92 2. Record 0, which describes $MFT itself. Its $DATA run list says which clusters 93 hold the rest of the table, because the MFT is a file and can be fragmented. 94 3. Stream the table through the run list in large chunks, fixing up and folding each 95 record into the entry table as it goes. 96 97 The MFT is addressed as a contiguous logical byte range and `read_logical` maps each 98 chunk onto physical runs, so a record straddling two runs (possible when a cluster is 99 smaller than a record) is handled without special cases. 100 */ 101 read_mft_from_volume :: proc( 102 v: ^Volume, 103 m: ^Mft, 104 opts := Read_Options{}, 105 allocator := context.allocator, 106 ) -> Error { 107 // 1. Boot sector. 108 boot_buf, boot_alloc_err := mem.alloc_bytes(IO_ALIGN, IO_ALIGN, allocator) 109 if boot_alloc_err != nil { 110 return .Out_Of_Memory 111 } 112 defer mem.free_bytes(boot_buf, allocator) 113 if read_err := volume_read_at(v, boot_buf, 0); read_err != nil { 114 return read_err 115 } 116 boot, boot_err := parse_boot_sector(boot_buf) 117 if boot_err != nil { 118 return boot_err 119 } 120 cluster := u64(boot.bytes_per_cluster) 121 record_size := int(boot.record_size) 122 123 // 2. Record 0. 124 rec0_len := max(record_size, IO_ALIGN) 125 rec0_buf, rec0_alloc_err := mem.alloc_bytes(rec0_len, IO_ALIGN, allocator) 126 if rec0_alloc_err != nil { 127 return .Out_Of_Memory 128 } 129 defer mem.free_bytes(rec0_buf, allocator) 130 if read_err := volume_read_at(v, rec0_buf, boot.mft_lcn * cluster); read_err != nil { 131 return read_err 132 } 133 rec0 := rec0_buf[:record_size] 134 if fix_err := apply_fixups(rec0); fix_err != nil { 135 return fix_err 136 } 137 if .In_Use not_in record_header(rec0).flags { 138 return .Bad_Record 139 } 140 141 runs: []Run 142 defer delete(runs, allocator) 143 mft_bytes: u64 144 found := false 145 146 // $MFT's own $BITMAP marks which record slots are live, so the dead ones can be 147 // skipped rather than read and discarded. 148 slot_bitmap: []byte 149 defer mem.free_bytes(slot_bitmap, allocator) 150 151 it := attributes(rec0) 152 for { 153 a, ok := next_attribute(&it) 154 if !ok { 155 break 156 } 157 if len(a.name) != 0 || a.lowest_vcn != 0 { 158 continue 159 } 160 #partial switch a.type { 161 case .Data: 162 if !a.non_resident { 163 return .Mft_Data_Missing 164 } 165 if found { 166 continue 167 } 168 decoded, run_err := decode_runlist(a.runlist, 0, allocator) 169 if run_err != nil { 170 return run_err 171 } 172 runs = decoded 173 mft_bytes = a.data_size 174 // If the run list in record 0 stops short of the data size, the rest of the 175 // mapping lives in extension records reached via $ATTRIBUTE_LIST. 176 if runlist_clusters(runs) * cluster < mft_bytes { 177 return .Mft_Spans_Extension_Records 178 } 179 found = true 180 case .Bitmap: 181 if slot_bitmap != nil { 182 continue 183 } 184 // Losing the bitmap only costs the skipping, so a failure is not fatal. 185 if bm, bm_err := read_attribute(v, a, cluster, allocator); bm_err == nil { 186 slot_bitmap = bm 187 } 188 } 189 } 190 if !found { 191 return .Mft_Data_Missing 192 } 193 194 // 3. Plan what to read, and cut it into chunks small enough to hand out. 195 chunk := opts.chunk_size 196 if chunk <= 0 { 197 chunk = DEFAULT_CHUNK_SIZE 198 } 199 // A chunk must hold whole records and whole clusters so every read stays aligned. 200 unit := max(record_size, int(cluster)) 201 chunk = max(chunk / unit, 1) * unit 202 203 // Without the slot bitmap, or with skipping turned off, the plan is the whole table. 204 extents: []Read_Extent 205 defer delete(extents, allocator) 206 if slot_bitmap != nil && opts.min_skip >= 0 { 207 min_skip := u64(DEFAULT_MIN_SKIP) 208 if opts.min_skip > 0 { 209 min_skip = u64(opts.min_skip) 210 } 211 if planned, plan_err := plan_reads( 212 slot_bitmap, 213 mft_bytes, 214 u64(record_size), 215 cluster, 216 min_skip, 217 allocator, 218 ); plan_err == nil { 219 extents = planned 220 } 221 } 222 if extents == nil { 223 whole, extent_alloc_err := make([]Read_Extent, 1, allocator) 224 if extent_alloc_err != nil { 225 return .Out_Of_Memory 226 } 227 whole[0] = Read_Extent{0, mft_bytes} 228 extents = whole 229 } 230 231 chunks := make([dynamic]Chunk, allocator) 232 defer delete(chunks) 233 planned_bytes: u64 234 for e in extents { 235 planned_bytes += e.length 236 for offset := e.offset; offset < e.offset + e.length; offset += u64(chunk) { 237 append(&chunks, Chunk{offset, min(u64(chunk), e.offset + e.length - offset)}) 238 } 239 } 240 241 // 4. Size the pool. The limit is the measured one, not the machine's, because the 242 // drive saturates long before the cores do. 243 worker_count := flow.width( 244 len(chunks), 245 .Io, 246 limit = opts.workers if opts.workers > 0 else DEFAULT_WORKERS, 247 ) 248 249 record_count := int(mft_bytes / u64(record_size)) 250 if init_err := mft_init(m, record_count, cluster, worker_count, allocator); init_err != nil { 251 return init_err 252 } 253 m.boot = boot 254 mft_set_ready(m) 255 // From here on, failures must release the table. 256 ok := false 257 defer if !ok { 258 mft_destroy(m) 259 } 260 m.stats.planned_bytes = planned_bytes 261 m.stats.skipped_bytes = mft_bytes - planned_bytes 262 m.stats.extents = u64(len(extents)) 263 264 workers, workers_err := make([]Worker, worker_count, allocator) 265 if workers_err != nil { 266 return .Out_Of_Memory 267 } 268 defer delete(workers, allocator) 269 270 // Windows serialises I/O on one handle, so a worker that shares it would queue 271 // behind its neighbours instead of adding queue depth. Each takes its own. 272 live := 0 273 for i in 0 ..< worker_count { 274 w := &workers[i] 275 w.mft = m 276 w.sink = &m.sinks[i] 277 w.record_size = record_size 278 w.cluster = cluster 279 wv, clone_err := volume_clone(v, allocator) 280 if clone_err != nil { 281 break 282 } 283 wbuf, buf_err := mem.alloc_bytes(chunk, IO_ALIGN, allocator) 284 if buf_err != nil { 285 volume_close(&wv) 286 break 287 } 288 w.volume = wv 289 w.owns_volume = true 290 w.buf = wbuf 291 w.reader = Extent_Reader { 292 v = &w.volume, 293 runs = runs, 294 cluster = cluster, 295 } 296 live += 1 297 } 298 if live == 0 { 299 // No second handle to be had, so fall back to reading on the caller's. 300 w := &workers[0] 301 wbuf, buf_err := mem.alloc_bytes(chunk, IO_ALIGN, allocator) 302 if buf_err != nil { 303 return .Out_Of_Memory 304 } 305 w.volume = v^ 306 w.buf = wbuf 307 w.reader = Extent_Reader { 308 v = &w.volume, 309 runs = runs, 310 cluster = cluster, 311 } 312 live = 1 313 } 314 defer { 315 for i in 0 ..< live { 316 w := &workers[i] 317 if w.owns_volume { 318 volume_close(&w.volume) 319 } 320 mem.free_bytes(w.buf, allocator) 321 } 322 } 323 m.stats.workers = u64(live) 324 325 // 5. Read and fold, every worker on its own chunk. 326 // The width is already decided by how many workers were built; the load only has 327 // to match the one they were sized with. 328 flow.each(chunks[:], workers[:live], read_chunk, .Io) 329 for &w in workers[:live] { 330 if w.err != nil { 331 return w.err 332 } 333 } 334 335 // 6. Extension records were held back because they credit a base record another 336 // worker may own. Every base record is in place now, so they fold in serially. 337 for &w in workers[:live] { 338 for d in w.sink.deferred { 339 if add_err := mft_add_record(m, d.record, d.bytes, w.sink); add_err != nil { 340 w.sink.stats.records_bad += 1 341 } 342 } 343 clear(&w.sink.deferred) 344 } 345 346 // $Bitmap is the file system's own count of used clusters, which checks the sums 347 // built above. Failing to read it costs nothing else, so the table still stands. 348 bitmap_start := time.tick_now() 349 if c, bitmap_err := read_bitmap_clusters(&workers[0].reader, boot, workers[0].buf, allocator); 350 bitmap_err == nil { 351 m.stats.allocated_clusters = c 352 } 353 m.stats.bitmap_ns = i64(time.tick_since(bitmap_start)) 354 355 mft_merge_sinks(m) 356 mft_set_complete(m) 357 ok = true 358 return .None 359 } 360 361 // A slice of the MFT's logical address space small enough to be one read, and the 362 // unit of parallel work: one worker claims it, reads it, and folds it in. 363 @(private) 364 Chunk :: struct { 365 offset: u64, 366 length: u64, 367 } 368 369 /* 370 One worker's private world for a parallel scan. 371 372 Everything here belongs to a single worker except `mft`, and every write into that 373 lands at a record number no other worker claims. Extension records are the exception 374 and are held in the sink rather than written across the divide. 375 */ 376 @(private) 377 Worker :: struct { 378 mft: ^Mft, 379 sink: ^Sink, 380 volume: Volume, 381 owns_volume: bool, 382 reader: Extent_Reader, 383 buf: []byte, 384 record_size: int, 385 cluster: u64, 386 err: Error, 387 } 388 389 @(private) 390 read_chunk :: proc(c: Chunk, w: ^Worker) -> bool { 391 n := int(c.length) 392 // Unbuffered reads must cover whole sectors. The run list is cluster granular, so 393 // rounding up never reads past the attribute's allocation. 394 read_n := min(int((c.length + w.cluster - 1) / w.cluster * w.cluster), len(w.buf)) 395 io_start := time.tick_now() 396 if read_err := read_logical(&w.reader, c.offset, w.buf[:read_n]); read_err != nil { 397 w.err = read_err 398 return false 399 } 400 w.sink.stats.io_ns += i64(time.tick_since(io_start)) 401 402 parse_start := time.tick_now() 403 defer w.sink.stats.parse_ns += i64(time.tick_since(parse_start)) 404 for start := 0; start + w.record_size <= n; start += w.record_size { 405 rec := w.buf[start:start + w.record_size] 406 // Chunks skip forward, so the slot number comes from the offset. 407 record := u32((c.offset + u64(start)) / u64(w.record_size)) 408 // Slots never written are all zeros; skip them silently. 409 if rd32(rec, 0) != RECORD_MAGIC { 410 continue 411 } 412 w.sink.stats.records_read += 1 413 if apply_fixups(rec) != nil { 414 w.sink.stats.records_bad += 1 415 continue 416 } 417 if record_header(rec).base_record != 0 { 418 hold_extension_record(w, record, rec) 419 continue 420 } 421 if add_err := mft_add_record(w.mft, record, rec, w.sink); add_err != nil { 422 w.sink.stats.records_bad += 1 423 } 424 } 425 return true 426 } 427 428 // Copy an extension record into the sink's arena, since the buffer is about to be 429 // reused for the next chunk. 430 @(private) 431 hold_extension_record :: proc(w: ^Worker, record: u32, rec: []byte) { 432 bytes, err := make([]byte, len(rec), virtual.arena_allocator(&w.sink.names)) 433 if err != nil { 434 w.sink.stats.records_bad += 1 435 return 436 } 437 copy(bytes, rec) 438 append(&w.sink.deferred, Deferred{record = record, bytes = bytes}) 439 } 440 441 // Read a whole attribute into memory: a copy of the value when resident, or the 442 // clusters its run list names when not. The caller frees the result. 443 @(private) 444 read_attribute :: proc( 445 v: ^Volume, 446 a: Attribute, 447 cluster: u64, 448 allocator: mem.Allocator, 449 ) -> ( 450 data: []byte, 451 err: Error, 452 ) { 453 if !a.non_resident { 454 if len(a.value) == 0 { 455 return nil, .Bad_Record 456 } 457 out, alloc_err := mem.alloc_bytes(len(a.value), IO_ALIGN, allocator) 458 if alloc_err != nil { 459 return nil, .Out_Of_Memory 460 } 461 copy(out, a.value) 462 return out, .None 463 } 464 runs, run_err := decode_runlist(a.runlist, 0, allocator) 465 if run_err != nil { 466 return nil, run_err 467 } 468 defer delete(runs, allocator) 469 // Unbuffered reads cover whole clusters, so the buffer is rounded up to one. 470 n := min((a.data_size + cluster - 1) / cluster * cluster, runlist_clusters(runs) * cluster) 471 if n == 0 { 472 return nil, .Bad_Runlist 473 } 474 out, alloc_err := mem.alloc_bytes(int(n), IO_ALIGN, allocator) 475 if alloc_err != nil { 476 return nil, .Out_Of_Memory 477 } 478 r := Extent_Reader { 479 v = v, 480 runs = runs, 481 cluster = cluster, 482 } 483 if read_err := read_logical(&r, 0, out); read_err != nil { 484 mem.free_bytes(out, allocator) 485 return nil, read_err 486 } 487 return out, .None 488 } 489 490 @(private) 491 Extent_Reader :: struct { 492 v: ^Volume, 493 runs: []Run, 494 cluster: u64, 495 cursor: int, // runs are visited in ascending order, so remember where we were 496 } 497 498 // Read a byte range of the attribute's logical address space by mapping it onto runs. 499 @(private) 500 read_logical :: proc(r: ^Extent_Reader, offset: u64, buf: []byte) -> Error { 501 done := 0 502 for done < len(buf) { 503 pos := offset + u64(done) 504 vcn := pos / r.cluster 505 // The cursor only walks forward, so rewind it when a read goes backwards. 506 if r.cursor >= len(r.runs) || r.runs[r.cursor].vcn > vcn { 507 r.cursor = 0 508 } 509 for r.cursor < len(r.runs) && r.runs[r.cursor].vcn + r.runs[r.cursor].length <= vcn { 510 r.cursor += 1 511 } 512 if r.cursor >= len(r.runs) || r.runs[r.cursor].vcn > vcn { 513 return .Bad_Runlist 514 } 515 run := r.runs[r.cursor] 516 if run.sparse { 517 return .Bad_Runlist 518 } 519 in_run := pos - run.vcn * r.cluster 520 avail := run.length * r.cluster - in_run 521 n := int(min(u64(len(buf) - done), avail)) 522 if read_err := volume_read_at(r.v, buf[done:done + n], run.lcn * r.cluster + in_run); 523 read_err != nil { 524 return read_err 525 } 526 done += n 527 } 528 return .None 529 }