ntfs.odin (3610B)
1 /* 2 Package ntfs reads the NTFS Master File Table ($MFT) and turns it into a flat table 3 of entries: one per file record, carrying parent, name, sizes, and flags. 4 5 Why read the MFT instead of walking directories: every file on an NTFS volume has a 6 1 KiB (sometimes 4 KiB) record in one contiguous-ish file, so the whole volume's 7 metadata is a few sequential reads. Directory enumeration is millions of small random 8 reads. For a "what is using my disk" tool the MFT route is one to two orders of 9 magnitude faster. 10 11 The parsing code is pure and operates on byte slices, so it is testable without a 12 volume and portable to other operating systems. Only `volume_windows.odin` touches 13 the OS. Reading a live volume requires administrator rights. 14 15 Layout on disk, in the order this package consumes it: 16 17 boot.odin boot sector: cluster size, record size, where the MFT starts 18 runlist.odin mapping pairs: which clusters a non-resident attribute occupies 19 record.odin FILE records: fixups, attribute headers, typed attribute views 20 mft.odin the entry table built from records, plus path reconstruction 21 reader.odin orchestration: boot -> record 0 -> $MFT runs -> stream records 22 */ 23 package ntfs 24 25 #assert( 26 ODIN_ENDIAN == .Little, 27 "NTFS structures are little-endian; this package reads them in place", 28 ) 29 30 Error :: enum { 31 None, 32 Not_Ntfs, // boot sector OEM id is not "NTFS " 33 Bad_Boot_Sector, // sizes in the boot sector are inconsistent 34 Bad_Record, // FILE record magic or fixups do not match 35 Bad_Runlist, // mapping pairs are malformed or a read fell outside them 36 Mft_Data_Missing, // record 0 has no unnamed non-resident $DATA 37 Mft_Spans_Extension_Records, // $MFT's $DATA continues in extension records (attribute list); unsupported 38 Open_Failed, 39 Access_Denied, // volume handles need administrator rights 40 Read_Failed, 41 Short_Read, 42 Unsupported_Platform, 43 Out_Of_Memory, 44 } 45 46 // A file reference packs a 48-bit record number with a 16-bit sequence number. 47 // The sequence number lets a reference detect that a record was deleted and reused. 48 File_Ref :: distinct u64 49 50 ref_record :: proc "contextless" (r: File_Ref) -> u64 { 51 return u64(r) & 0x0000_FFFF_FFFF_FFFF 52 } 53 54 ref_sequence :: proc "contextless" (r: File_Ref) -> u16 { 55 return u16(u64(r) >> 48) 56 } 57 58 make_ref :: proc "contextless" (record: u64, sequence: u16) -> File_Ref { 59 return File_Ref(record & 0x0000_FFFF_FFFF_FFFF | u64(sequence) << 48) 60 } 61 62 // Well-known record numbers. Records below FIRST_USER_RECORD belong to the file system. 63 RECORD_MFT :: 0 64 RECORD_MFT_MIRROR :: 1 65 RECORD_LOG_FILE :: 2 66 RECORD_VOLUME :: 3 67 RECORD_ATTR_DEF :: 4 68 RECORD_ROOT :: 5 69 RECORD_BITMAP :: 6 70 RECORD_BOOT :: 7 71 RECORD_BAD_CLUS :: 8 72 RECORD_SECURE :: 9 73 RECORD_UPCASE :: 10 74 RECORD_EXTEND :: 11 75 FIRST_USER_RECORD :: 16 76 77 // Little-endian readers over byte slices. Slicing panics on out-of-range offsets, so 78 // callers check lengths first; these only guard the final two-to-eight bytes. 79 @(private) 80 rd16 :: proc "contextless" (b: []byte, off: int) -> u16 { 81 if off + 2 > len(b) { 82 return 0 83 } 84 return u16(b[off]) | u16(b[off + 1]) << 8 85 } 86 87 @(private) 88 rd32 :: proc "contextless" (b: []byte, off: int) -> u32 { 89 if off + 4 > len(b) { 90 return 0 91 } 92 return u32(rd16(b, off)) | u32(rd16(b, off + 2)) << 16 93 } 94 95 @(private) 96 rd64 :: proc "contextless" (b: []byte, off: int) -> u64 { 97 if off + 8 > len(b) { 98 return 0 99 } 100 return u64(rd32(b, off)) | u64(rd32(b, off + 4)) << 32 101 } 102 103 @(private) 104 wr16 :: proc "contextless" (b: []byte, off: int, v: u16) { 105 if off + 2 > len(b) { 106 return 107 } 108 b[off] = byte(v) 109 b[off + 1] = byte(v >> 8) 110 }