sonar

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

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 }