sonar

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

ntfs_test.odin (17106B)


      1 package ntfs
      2 
      3 import "core:testing"
      4 import "core:unicode/utf16"
      5 
      6 // ---- synthetic on-disk structures -------------------------------------------------
      7 //
      8 // Real MFT records are awkward to ship as fixtures (they carry the volume's file
      9 // names), so tests build records byte by byte with the same layout rules the parser
     10 // enforces. Everything is allocated from the temp allocator, which the test runner
     11 // resets between tests.
     12 
     13 @(private = "file")
     14 put16 :: proc(b: []byte, off: int, v: u16) {
     15 	b[off] = byte(v)
     16 	b[off + 1] = byte(v >> 8)
     17 }
     18 
     19 @(private = "file")
     20 put32 :: proc(b: []byte, off: int, v: u32) {
     21 	put16(b, off, u16(v))
     22 	put16(b, off + 2, u16(v >> 16))
     23 }
     24 
     25 @(private = "file")
     26 put64 :: proc(b: []byte, off: int, v: u64) {
     27 	put32(b, off, u32(v))
     28 	put32(b, off + 4, u32(v >> 32))
     29 }
     30 
     31 @(private = "file")
     32 utf16_of :: proc(s: string) -> []u16 {
     33 	buf := make([]u16, len(s) * 2 + 1, context.temp_allocator)
     34 	n := utf16.encode_string(buf, s)
     35 	return buf[:n]
     36 }
     37 
     38 @(private = "file")
     39 align8 :: proc(n: int) -> int {
     40 	return (n + 7) & ~int(7)
     41 }
     42 
     43 @(private = "file")
     44 resident :: proc(type: Attr_Type, value: []byte, name := "", flags: Attr_Flags = {}) -> []byte {
     45 	name16 := utf16_of(name)
     46 	name_ofs := size_of(Resident_Header)
     47 	value_ofs := align8(name_ofs + len(name16) * 2)
     48 	length := align8(value_ofs + len(value))
     49 	b := make([]byte, length, context.temp_allocator)
     50 	put32(b, 0, u32(type))
     51 	put32(b, 4, u32(length))
     52 	b[8] = 0
     53 	b[9] = u8(len(name16))
     54 	put16(b, 10, u16(name_ofs))
     55 	put16(b, 12, transmute(u16)flags)
     56 	put16(b, 14, 1)
     57 	put32(b, 16, u32(len(value)))
     58 	put16(b, 20, u16(value_ofs))
     59 	for c, i in name16 {
     60 		put16(b, name_ofs + 2 * i, c)
     61 	}
     62 	copy(b[value_ofs:], value)
     63 	return b
     64 }
     65 
     66 @(private = "file")
     67 nonresident :: proc(
     68 	type: Attr_Type,
     69 	allocated, data_size: u64,
     70 	runlist: []byte,
     71 	name := "",
     72 	flags: Attr_Flags = {},
     73 	compressed_size: u64 = 0,
     74 	lowest_vcn: u64 = 0,
     75 ) -> []byte {
     76 	name16 := utf16_of(name)
     77 	has_cs := .Compressed in flags || .Sparse in flags
     78 	hdr_len := size_of(Nonresident_Header) + (8 if has_cs else 0)
     79 	name_ofs := hdr_len
     80 	run_ofs := align8(name_ofs + len(name16) * 2)
     81 	length := align8(run_ofs + len(runlist))
     82 	b := make([]byte, length, context.temp_allocator)
     83 	put32(b, 0, u32(type))
     84 	put32(b, 4, u32(length))
     85 	b[8] = 1
     86 	b[9] = u8(len(name16))
     87 	put16(b, 10, u16(name_ofs))
     88 	put16(b, 12, transmute(u16)flags)
     89 	put16(b, 14, 2)
     90 	put64(b, 16, lowest_vcn)
     91 	clusters := allocated / 4096
     92 	if clusters > 0 {
     93 		put64(b, 24, lowest_vcn + clusters - 1)
     94 	}
     95 	put16(b, 32, u16(run_ofs))
     96 	put64(b, 40, allocated)
     97 	put64(b, 48, data_size)
     98 	put64(b, 56, data_size)
     99 	if has_cs {
    100 		put64(b, 64, compressed_size)
    101 	}
    102 	for c, i in name16 {
    103 		put16(b, name_ofs + 2 * i, c)
    104 	}
    105 	copy(b[run_ofs:], runlist)
    106 	return b
    107 }
    108 
    109 @(private = "file")
    110 file_name_value :: proc(
    111 	parent: File_Ref,
    112 	name: string,
    113 	ns: Name_Space,
    114 	attrs: File_Attributes = {},
    115 ) -> []byte {
    116 	name16 := utf16_of(name)
    117 	b := make([]byte, size_of(File_Name_Header) + 2 * len(name16), context.temp_allocator)
    118 	put64(b, 0, u64(parent))
    119 	put32(b, 0x38, transmute(u32)attrs)
    120 	b[0x40] = u8(len(name16))
    121 	b[0x41] = u8(ns)
    122 	for c, i in name16 {
    123 		put16(b, size_of(File_Name_Header) + 2 * i, c)
    124 	}
    125 	return b
    126 }
    127 
    128 @(private = "file")
    129 standard_info_value :: proc(attrs: File_Attributes) -> []byte {
    130 	b := make([]byte, 72, context.temp_allocator)
    131 	put32(b, 0x20, transmute(u32)attrs)
    132 	return b
    133 }
    134 
    135 // A 1024-byte record with fixups applied the way the file system writes them: the
    136 // last two bytes of each 512-byte block are replaced by the sequence number and the
    137 // displaced bytes are saved in the update sequence array.
    138 @(private = "file")
    139 build_record :: proc(
    140 	attrs: [][]byte,
    141 	record_number: u32 = 100,
    142 	sequence: u16 = 7,
    143 	flags: Record_Flags = {.In_Use},
    144 	base: File_Ref = 0,
    145 	link_count: u16 = 1,
    146 ) -> []byte {
    147 	rec := make([]byte, 1024, context.temp_allocator)
    148 	put32(rec, 0, RECORD_MAGIC)
    149 	put16(rec, 4, 0x30) // update sequence array offset
    150 	put16(rec, 6, 3) // sequence number + one entry per 512-byte block
    151 	put16(rec, 0x10, sequence)
    152 	put16(rec, 0x12, link_count)
    153 	put16(rec, 0x14, 0x38) // first attribute
    154 	put16(rec, 0x16, transmute(u16)flags)
    155 	put64(rec, 0x20, u64(base))
    156 	put32(rec, 0x2C, record_number)
    157 
    158 	off := 0x38
    159 	for a in attrs {
    160 		copy(rec[off:], a)
    161 		off += len(a)
    162 	}
    163 	put32(rec, off, u32(Attr_Type.End))
    164 	off += 8
    165 	put32(rec, 0x18, u32(off))
    166 	put32(rec, 0x1C, 1024)
    167 	assert(off <= 510, "test record too large: attributes overlap the first fixup")
    168 
    169 	// Give the protected bytes recognisable values so a test can see them restored.
    170 	put16(rec, 510, 0xABCD)
    171 	put16(rec, 1022, 0xEF01)
    172 	usn: u16 = 0x1234
    173 	put16(rec, 0x30, usn)
    174 	put16(rec, 0x32, rd16(rec, 510))
    175 	put16(rec, 0x34, rd16(rec, 1022))
    176 	put16(rec, 510, usn)
    177 	put16(rec, 1022, usn)
    178 	return rec
    179 }
    180 
    181 @(private = "file")
    182 add :: proc(t: ^testing.T, m: ^Mft, rec: []byte) {
    183 	testing.expect_value(t, apply_fixups(rec), Error.None)
    184 	testing.expect_value(
    185 		t,
    186 		mft_add_record(m, record_header(rec).record_number, rec, &m.sinks[0]),
    187 		Error.None,
    188 	)
    189 	mft_merge_sinks(m)
    190 }
    191 
    192 // ---- boot sector --------------------------------------------------------------------
    193 
    194 @(test)
    195 test_boot_sector :: proc(t: ^testing.T) {
    196 	b := make([]byte, 512, context.temp_allocator)
    197 	copy(b[3:], "NTFS    ")
    198 	put16(b, 0x0B, 512)
    199 	b[0x0D] = 8
    200 	put64(b, 0x28, 1_000_000)
    201 	put64(b, 0x30, 786_432)
    202 	put64(b, 0x38, 2)
    203 	b[0x40] = 0xF6 // 2^10 bytes per record
    204 	b[0x44] = 1 // one cluster per index block
    205 	put64(b, 0x48, 0xDEADBEEF)
    206 	b[0x1FE] = 0x55
    207 	b[0x1FF] = 0xAA
    208 
    209 	bs, err := parse_boot_sector(b)
    210 	testing.expect_value(t, err, Error.None)
    211 	testing.expect_value(t, bs.bytes_per_cluster, u32(4096))
    212 	testing.expect_value(t, bs.record_size, u32(1024))
    213 	testing.expect_value(t, bs.index_block_size, u32(4096))
    214 	testing.expect_value(t, bs.mft_lcn, u64(786_432))
    215 	testing.expect_value(t, bs.serial, u64(0xDEADBEEF))
    216 
    217 	// 0xF4 encodes 2^12 sectors per cluster: the 2 MiB clusters Windows 10 allows.
    218 	b[0x0D] = 0xF4
    219 	bs, err = parse_boot_sector(b)
    220 	testing.expect_value(t, err, Error.None)
    221 	testing.expect_value(t, bs.bytes_per_cluster, u32(2 * 1024 * 1024))
    222 
    223 	copy(b[3:], "MSDOS5.0")
    224 	_, err = parse_boot_sector(b)
    225 	testing.expect_value(t, err, Error.Not_Ntfs)
    226 }
    227 
    228 // ---- run lists ----------------------------------------------------------------------
    229 
    230 @(test)
    231 test_runlist :: proc(t: ^testing.T) {
    232 	// 0x21: 1-byte length, 2-byte delta. 24 clusters at LCN 0x5634.
    233 	// 0x11: 1-byte length, 1-byte delta of -16. 48 clusters at LCN 0x5624.
    234 	// 0x01: 1-byte length, no delta. 16 sparse clusters.
    235 	data := []byte{0x21, 0x18, 0x34, 0x56, 0x11, 0x30, 0xF0, 0x01, 0x10, 0x00}
    236 	runs, err := decode_runlist(data, 0, context.temp_allocator)
    237 	testing.expect_value(t, err, Error.None)
    238 	testing.expect_value(t, len(runs), 3)
    239 	testing.expect_value(t, runs[0], Run{vcn = 0, lcn = 0x5634, length = 0x18})
    240 	testing.expect_value(t, runs[1], Run{vcn = 0x18, lcn = 0x5624, length = 0x30})
    241 	testing.expect_value(t, runs[2], Run{vcn = 0x48, lcn = 0, length = 0x10, sparse = true})
    242 	testing.expect_value(t, runlist_clusters(runs), u64(0x58))
    243 
    244 	_, err = decode_runlist([]byte{0x21, 0x18}, 0, context.temp_allocator)
    245 	testing.expect_value(t, err, Error.Bad_Runlist)
    246 
    247 	// A delta that would take the LCN negative is corruption.
    248 	_, err = decode_runlist([]byte{0x11, 0x01, 0xFF, 0x00}, 0, context.temp_allocator)
    249 	testing.expect_value(t, err, Error.Bad_Runlist)
    250 }
    251 
    252 // ---- fixups -------------------------------------------------------------------------
    253 
    254 @(test)
    255 test_fixups :: proc(t: ^testing.T) {
    256 	rec := build_record({})
    257 	testing.expect_value(t, rd16(rec, 510), u16(0x1234))
    258 
    259 	torn := make([]byte, 1024, context.temp_allocator)
    260 	copy(torn, rec)
    261 	put16(torn, 1022, 0x9999)
    262 	testing.expect_value(t, apply_fixups(torn), Error.Bad_Record)
    263 
    264 	testing.expect_value(t, apply_fixups(rec), Error.None)
    265 	testing.expect_value(t, rd16(rec, 510), u16(0xABCD))
    266 	testing.expect_value(t, rd16(rec, 1022), u16(0xEF01))
    267 
    268 	put32(rec, 0, RECORD_MAGIC_BAD)
    269 	testing.expect_value(t, apply_fixups(rec), Error.Bad_Record)
    270 }
    271 
    272 // ---- records into entries -----------------------------------------------------------
    273 
    274 @(test)
    275 test_resident_file :: proc(t: ^testing.T) {
    276 	root := make_ref(RECORD_ROOT, 5)
    277 	rec := build_record(
    278 		{
    279 			resident(.Standard_Information, standard_info_value({.Archive})),
    280 			resident(.File_Name, file_name_value(root, "HELLOW~1.TXT", .Dos)),
    281 			resident(.File_Name, file_name_value(root, "hello world.txt", .Win32)),
    282 			resident(.Data, transmute([]byte)string("hello, world!")),
    283 		},
    284 	)
    285 	m: Mft
    286 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    287 	defer mft_destroy(&m)
    288 	add(t, &m, rec)
    289 
    290 	e := m.entries[100]
    291 	testing.expect_value(t, e.name, "hello world.txt")
    292 	testing.expect_value(t, e.namespace, Name_Space.Win32)
    293 	testing.expect_value(t, e.parent, u32(RECORD_ROOT))
    294 	testing.expect_value(t, e.parent_sequence, u16(5))
    295 	testing.expect_value(t, e.sequence, u16(7))
    296 	testing.expect_value(t, e.size, u64(13))
    297 	testing.expect_value(t, e.allocated, u64(0))
    298 	testing.expect(t, .In_Use in e.flags)
    299 	testing.expect(t, .Directory not_in e.flags)
    300 	testing.expect(t, .Archive in e.attributes)
    301 	testing.expect_value(t, len(m.links), 0)
    302 	testing.expect_value(t, m.stats.in_use, u64(1))
    303 }
    304 
    305 @(test)
    306 test_dos_name_first_is_replaced :: proc(t: ^testing.T) {
    307 	root := make_ref(RECORD_ROOT, 5)
    308 	rec := build_record(
    309 		{
    310 			resident(.File_Name, file_name_value(root, "LONGNA~1.TXT", .Dos)),
    311 			resident(.File_Name, file_name_value(root, "long name.txt", .Win32)),
    312 		},
    313 	)
    314 	m: Mft
    315 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    316 	defer mft_destroy(&m)
    317 	add(t, &m, rec)
    318 	testing.expect_value(t, m.entries[100].name, "long name.txt")
    319 	testing.expect_value(t, len(m.links), 0)
    320 }
    321 
    322 @(test)
    323 test_nonresident_streams :: proc(t: ^testing.T) {
    324 	two_clusters := []byte{0x11, 0x02, 0x10, 0x00}
    325 	one_cluster := []byte{0x11, 0x01, 0x20, 0x00}
    326 	// 16 allocated clusters followed by 240 sparse ones: a 1 MiB compressed stream
    327 	// that squeezed into 64 KiB.
    328 	compressed := []byte{0x11, 0x10, 0x30, 0x01, 0xF0, 0x00}
    329 	rec := build_record(
    330 		{
    331 			resident(.File_Name, file_name_value(make_ref(64, 3), "big.bin", .Win32_And_Dos)),
    332 			nonresident(.Data, 8192, 5000, two_clusters),
    333 			nonresident(.Data, 4096, 100, one_cluster, name = "Zone.Identifier"),
    334 			nonresident(
    335 				.Data,
    336 				1 << 20,
    337 				1 << 20,
    338 				compressed,
    339 				name = "packed",
    340 				flags = {.Compressed},
    341 				compressed_size = 65536,
    342 			),
    343 		},
    344 	)
    345 	m: Mft
    346 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    347 	defer mft_destroy(&m)
    348 	add(t, &m, rec)
    349 
    350 	e := m.entries[100]
    351 	testing.expect_value(t, e.size, u64(5000)) // unnamed stream only
    352 	testing.expect_value(t, e.allocated, u64(8192 + 4096 + 65536)) // every stream, measured from run lists
    353 	testing.expect_value(t, e.parent, u32(64))
    354 	testing.expect_value(t, e.parent_sequence, u16(3))
    355 }
    356 
    357 @(test)
    358 test_fragmented_extents_sum_their_own_clusters :: proc(t: ^testing.T) {
    359 	// Both extents repeat the header sizes (8192 allocated) but each maps one cluster.
    360 	rec := build_record(
    361 		{
    362 			resident(.File_Name, file_name_value(make_ref(RECORD_ROOT, 5), "frag.bin", .Win32)),
    363 			nonresident(.Data, 8192, 8000, []byte{0x11, 0x01, 0x10, 0x00}),
    364 			nonresident(.Data, 8192, 8000, []byte{0x11, 0x01, 0x40, 0x00}, lowest_vcn = 1),
    365 		},
    366 	)
    367 	m: Mft
    368 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    369 	defer mft_destroy(&m)
    370 	add(t, &m, rec)
    371 	testing.expect_value(t, m.entries[100].allocated, u64(8192))
    372 	testing.expect_value(t, m.entries[100].size, u64(8000))
    373 }
    374 
    375 @(test)
    376 test_unallocated_runlist_counts_nothing :: proc(t: ^testing.T) {
    377 	// $BadClus:$Bad spans the whole volume with sparse runs and no sparse flag.
    378 	rec := build_record(
    379 		{
    380 			resident(
    381 				.File_Name,
    382 				file_name_value(make_ref(RECORD_ROOT, 5), "$BadClus", .Win32_And_Dos),
    383 			),
    384 			nonresident(.Data, 0, 0, []byte{0x00}),
    385 			nonresident(
    386 				.Data,
    387 				1 << 40,
    388 				1 << 40,
    389 				[]byte{0x04, 0x00, 0x00, 0x00, 0x10, 0x00},
    390 				name = "$Bad",
    391 			),
    392 		},
    393 		record_number = RECORD_BAD_CLUS,
    394 	)
    395 	m: Mft
    396 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    397 	defer mft_destroy(&m)
    398 	add(t, &m, rec)
    399 	testing.expect_value(t, m.entries[RECORD_BAD_CLUS].allocated, u64(0))
    400 }
    401 
    402 @(test)
    403 test_runlist_allocated_clusters :: proc(t: ^testing.T) {
    404 	clusters, ok := runlist_allocated_clusters([]byte{0x11, 0x10, 0x30, 0x01, 0xF0, 0x00})
    405 	testing.expect(t, ok)
    406 	testing.expect_value(t, clusters, u64(16))
    407 	_, ok = runlist_allocated_clusters([]byte{0x21, 0x18})
    408 	testing.expect(t, !ok)
    409 }
    410 
    411 @(test)
    412 test_extension_record_and_hard_links :: proc(t: ^testing.T) {
    413 	base := build_record(
    414 		{resident(.File_Name, file_name_value(make_ref(RECORD_ROOT, 5), "a.txt", .Win32))},
    415 		record_number = 100,
    416 		sequence = 9,
    417 		link_count = 2,
    418 	)
    419 	ext := build_record(
    420 		{
    421 			resident(.File_Name, file_name_value(make_ref(70, 2), "b.txt", .Win32)),
    422 			nonresident(.Data, 4096, 10, []byte{0x11, 0x01, 0x20, 0x00}),
    423 		},
    424 		record_number = 101,
    425 		base = make_ref(100, 9),
    426 	)
    427 	m: Mft
    428 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    429 	defer mft_destroy(&m)
    430 
    431 	// The extension arrives before its base, as it can on disk.
    432 	add(t, &m, ext)
    433 	add(t, &m, base)
    434 
    435 	e := m.entries[100]
    436 	testing.expect_value(t, e.name, "b.txt") // first proper name seen wins
    437 	testing.expect_value(t, e.parent, u32(70))
    438 	testing.expect_value(t, e.allocated, u64(4096))
    439 	testing.expect_value(t, e.size, u64(10))
    440 	testing.expect_value(t, e.sequence, u16(9))
    441 	testing.expect_value(t, e.link_count, u16(2))
    442 	testing.expect_value(t, len(m.links), 1)
    443 	testing.expect_value(t, m.links[0].record, u32(100))
    444 	testing.expect_value(t, m.links[0].parent, u32(RECORD_ROOT))
    445 	testing.expect_value(t, m.links[0].name, "a.txt")
    446 
    447 	// The extension slot itself is not a file.
    448 	testing.expect_value(t, m.entries[101].name, "")
    449 	testing.expect_value(t, m.stats.in_use, u64(1))
    450 }
    451 
    452 @(test)
    453 test_free_record_is_ignored :: proc(t: ^testing.T) {
    454 	rec := build_record(
    455 		{resident(.File_Name, file_name_value(make_ref(RECORD_ROOT, 5), "deleted.txt", .Win32))},
    456 		flags = {},
    457 	)
    458 	m: Mft
    459 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    460 	defer mft_destroy(&m)
    461 	add(t, &m, rec)
    462 	testing.expect_value(t, m.entries[100].name, "")
    463 	testing.expect_value(t, m.stats.in_use, u64(0))
    464 }
    465 
    466 // ---- paths --------------------------------------------------------------------------
    467 
    468 @(test)
    469 test_path :: proc(t: ^testing.T) {
    470 	m: Mft
    471 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    472 	defer mft_destroy(&m)
    473 
    474 	root := make_ref(RECORD_ROOT, 5)
    475 	add(
    476 		t,
    477 		&m,
    478 		build_record(
    479 			{resident(.File_Name, file_name_value(root, ".", .Win32))},
    480 			record_number = RECORD_ROOT,
    481 			sequence = 5,
    482 			flags = {.In_Use, .Directory},
    483 		),
    484 	)
    485 	add(
    486 		t,
    487 		&m,
    488 		build_record(
    489 			{resident(.File_Name, file_name_value(root, "Windows", .Win32))},
    490 			record_number = 64,
    491 			sequence = 1,
    492 			flags = {.In_Use, .Directory},
    493 		),
    494 	)
    495 	add(
    496 		t,
    497 		&m,
    498 		build_record(
    499 			{resident(.File_Name, file_name_value(make_ref(64, 1), "explorer.exe", .Win32))},
    500 			record_number = 65,
    501 			sequence = 1,
    502 		),
    503 	)
    504 	// Parent sequence 99 does not match record 64's sequence 1: the directory this
    505 	// file was in has been deleted and its record reused.
    506 	add(
    507 		t,
    508 		&m,
    509 		build_record(
    510 			{resident(.File_Name, file_name_value(make_ref(64, 99), "stale.tmp", .Win32))},
    511 			record_number = 66,
    512 			sequence = 1,
    513 		),
    514 	)
    515 
    516 	testing.expect_value(t, mft_path(&m, 65, context.temp_allocator), `\Windows\explorer.exe`)
    517 	testing.expect_value(t, mft_path(&m, 64, context.temp_allocator), `\Windows`)
    518 	testing.expect_value(t, mft_path(&m, RECORD_ROOT, context.temp_allocator), `\`)
    519 	testing.expect_value(t, mft_path(&m, 66, context.temp_allocator), `<orphan>\stale.tmp`)
    520 }
    521 
    522 // ---- bitmap -------------------------------------------------------------------------
    523 
    524 @(test)
    525 test_count_set_bits :: proc(t: ^testing.T) {
    526 	testing.expect_value(t, count_set_bits(nil), u64(0))
    527 	testing.expect_value(t, count_set_bits([]byte{0xFF}), u64(8))
    528 
    529 	// Nine bytes exercises both the eight-byte chunk loop and the trailing bytes.
    530 	nine := []byte{0xFF, 0x00, 0x0F, 0xF0, 0x01, 0x80, 0xAA, 0x55, 0x03}
    531 	testing.expect_value(t, count_set_bits(nine), u64(28))
    532 
    533 	all := make([]byte, 64, context.temp_allocator)
    534 	for i in 0 ..< len(all) {
    535 		all[i] = 0xFF
    536 	}
    537 	testing.expect_value(t, count_set_bits(all), u64(512))
    538 }
    539 
    540 // ---- resident data ------------------------------------------------------------------
    541 
    542 @(test)
    543 test_resident_file_costs_no_clusters :: proc(t: ^testing.T) {
    544 	rec := build_record(
    545 		{
    546 			resident(.File_Name, file_name_value(make_ref(RECORD_ROOT, 5), "tiny.txt", .Win32)),
    547 			resident(.Data, transmute([]byte)string("hello")),
    548 		},
    549 	)
    550 	m: Mft
    551 	testing.expect_value(t, mft_init(&m, 128, 4096, 1), Error.None)
    552 	defer mft_destroy(&m)
    553 	add(t, &m, rec)
    554 
    555 	// The bytes live inside the MFT record, so the file itself allocates nothing and
    556 	// the stats record what the directory totals will therefore miss.
    557 	testing.expect_value(t, m.entries[100].size, u64(5))
    558 	testing.expect_value(t, m.entries[100].allocated, u64(0))
    559 	testing.expect_value(t, m.stats.resident_files, u64(1))
    560 	testing.expect_value(t, m.stats.resident_bytes, u64(5))
    561 }