From c45b7cc2af981f77d9f043bd92e1017c1fe3bb2e Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Fri, 17 Sep 2021 19:13:14 +0200 Subject: Fix `Ntfs::file` to support files outside the first MFT data run. This change has notable negative performance implications, as the $MFT record is now read and fixed up on every file lookup. This needs to be fixed later via caching. --- src/file.rs | 21 +++++++++++++++++++-- src/ntfs.rs | 23 +++++++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/file.rs b/src/file.rs index 16f791c..f2dff53 100644 --- a/src/file.rs +++ b/src/file.rs @@ -56,10 +56,16 @@ bitflags! { #[derive(Debug)] pub struct NtfsFile<'n> { record: Record<'n>, + file_record_number: u64, } impl<'n> NtfsFile<'n> { - pub(crate) fn new(ntfs: &'n Ntfs, fs: &mut T, position: u64) -> Result + pub(crate) fn new( + ntfs: &'n Ntfs, + fs: &mut T, + position: u64, + file_record_number: u64, + ) -> Result where T: Read + Seek, { @@ -71,7 +77,10 @@ impl<'n> NtfsFile<'n> { Self::validate_signature(&record)?; record.fixup()?; - let file = Self { record }; + let file = Self { + record, + file_record_number, + }; file.validate_sizes()?; Ok(file) @@ -186,6 +195,14 @@ impl<'n> NtfsFile<'n> { NtfsIndex::::new(index_root, index_allocation) } + /// Returns the NTFS file record number of this file. + /// + /// This number uniquely identifies this file and can be used to recreate this [`NtfsFile`] + /// object via [`Ntfs::file`]. + pub fn file_record_number(&self) -> u64 { + self.file_record_number + } + pub(crate) fn first_attribute_offset(&self) -> u16 { let start = offset_of!(FileRecordHeader, first_attribute_offset); LittleEndian::read_u16(&self.record.data()[start..]) diff --git a/src/ntfs.rs b/src/ntfs.rs index deaa546..bb99e92 100644 --- a/src/ntfs.rs +++ b/src/ntfs.rs @@ -6,6 +6,7 @@ use crate::boot_sector::BootSector; use crate::error::{NtfsError, Result}; use crate::file::{KnownNtfsFileRecordNumber, NtfsFile}; use crate::structured_values::{NtfsVolumeInformation, NtfsVolumeName}; +use crate::traits::NtfsReadSeek; use crate::upcase_table::UpcaseTable; use binread::io::{Read, Seek, SeekFrom}; use binread::BinReaderExt; @@ -77,17 +78,31 @@ impl Ntfs { let offset = file_record_number .checked_mul(self.file_record_size as u64) .ok_or(NtfsError::InvalidFileRecordNumber { file_record_number })?; - let position = self - .mft_position - .checked_add(offset) + + let mft = NtfsFile::new(&self, fs, self.mft_position, 0)?; + let mft_data_attribute = mft.data("").ok_or(NtfsError::AttributeNotFound { + position: self.mft_position, + ty: NtfsAttributeType::Data, + })??; + let mut mft_data_value = mft_data_attribute.value()?; + + mft_data_value.seek(fs, SeekFrom::Start(offset))?; + let position = mft_data_value + .data_position() .ok_or(NtfsError::InvalidFileRecordNumber { file_record_number })?; - NtfsFile::new(&self, fs, position) + + NtfsFile::new(&self, fs, position, file_record_number) } pub fn file_record_size(&self) -> u32 { self.file_record_size } + /// Returns the absolute byte position of the Master File Table (MFT). + pub fn mft_position(&self) -> u64 { + self.mft_position + } + /// Reads the $UpCase file from the filesystem and stores it in this [`Ntfs`] object. /// /// This function only needs to be called if case-insensitive comparisons are later performed -- cgit v1.2.3