sonar

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

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 }