diff --git a/samples/entry_data_run_at_offset b/samples/entry_data_run_at_offset new file mode 100644 index 0000000..c5f48a5 Binary files /dev/null and b/samples/entry_data_run_at_offset differ diff --git a/samples/entry_multiple_index_root_entries b/samples/entry_multiple_index_root_entries new file mode 100644 index 0000000..4853444 Binary files /dev/null and b/samples/entry_multiple_index_root_entries differ diff --git a/samples/entry_single_file b/samples/entry_single_file new file mode 100644 index 0000000..8e70db3 Binary files /dev/null and b/samples/entry_single_file differ diff --git a/src/attribute/data_run.rs b/src/attribute/data_run.rs new file mode 100644 index 0000000..57c4fd2 --- /dev/null +++ b/src/attribute/data_run.rs @@ -0,0 +1,86 @@ +use serde::Serialize; + +// adapted from https://github.com/rkapl/ntfs-reclaim/blob/a68e87b21c12631311fc3f279f5b03bd8f23d57b/src/data_runs.rs +// original didn't support sparse clusters + +#[derive(Serialize, Debug, Copy, Clone, Eq, PartialEq)] +pub enum RunType { + Standard, + Sparse, +} + +#[derive(Serialize, Debug, Copy, Clone, Eq, PartialEq)] +pub struct DataRun { + pub lcn_offset: u64, + pub lcn_length: u64, + pub run_type: RunType +} + +fn decode_run_value>(it: &mut T, bytes: u8) -> Option { + let mut acc = 0u64; + for _ in 0..bytes { + let v = it.next()?; + acc = (acc >> 8) | ((v as u64) << 56); + } + acc >>= (8 - bytes) * 8; + Some(acc) +} + +fn decode_run_svalue>(it: &mut T, bytes: u8) -> Option { + let mut acc = decode_run_value(it, bytes)? as i64; + // sign extend + acc <<= (8 - bytes) * 8; + acc >>= (8 - bytes) * 8; + Some(acc) +} + +pub fn decode_data_runs(runs: &[u8]) -> Option> { + let mut it = runs.iter().copied(); + let mut out: Vec = Vec::new(); + + loop { + let h = it.next()?; + if h == 0 { + break; + } + let offset_size = (h & 0xF0) >> 4; + let length_size = h & 0x0F; + if offset_size > 8 || length_size > 8 { + return None + } + let length = decode_run_value(&mut it, length_size)?; + let abs_offset; + let run_type; + if offset_size != 0 { // offset_size of 0 == sparse cluster + if let Some(last) = out.last() { + let rel_offset = decode_run_svalue(&mut it, offset_size)?; + abs_offset = (last.lcn_offset as i64 + rel_offset) as u64; + } else { + abs_offset = decode_run_value(&mut it, offset_size)?; + } + run_type = RunType::Standard; + } + else { + abs_offset = 0; + run_type = RunType::Sparse; + } + out.push(DataRun { + lcn_offset: abs_offset, + lcn_length: length, + run_type + }); + } + Some(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_value_decode() { + assert_eq!(decode_run_value(&mut vec![0x34, 0x56].into_iter(), 2), Some(0x5634)); + assert_eq!(decode_run_svalue(&mut vec![0xE0].into_iter(), 1), Some(-0x20)); + assert_eq!(decode_run_svalue(&mut vec![0xE0].into_iter(), 2), None); + } +} diff --git a/src/attribute/header.rs b/src/attribute/header.rs index c3a3409..79a8caa 100644 --- a/src/attribute/header.rs +++ b/src/attribute/header.rs @@ -29,6 +29,8 @@ pub struct MftAttributeHeader { /// The unique instance for this attribute in the file record. pub instance: u16, pub name: String, + /// start of the attribute; used for calculating relative offsets + pub start_offset: u64 } #[derive(Serialize, Clone, Debug)] @@ -107,6 +109,7 @@ impl MftAttributeHeader { instance: id, name, residential_header, + start_offset: attribute_header_start_offset })) } } @@ -120,7 +123,6 @@ pub struct ResidentHeader { /// The offset to the value from the start of the attribute record, in bytes. pub data_offset: u16, pub index_flag: u8, - #[serde(skip_serializing)] pub padding: u8, } diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs index 08715f0..c00fb42 100644 --- a/src/attribute/mod.rs +++ b/src/attribute/mod.rs @@ -6,6 +6,8 @@ pub mod x30; pub mod x40; pub mod x80; pub mod x90; +pub mod non_resident_attr; +pub mod data_run; use crate::err::Result; use crate::impl_serialize_for_bitflags; @@ -19,10 +21,11 @@ use crate::attribute::x10::StandardInfoAttr; use crate::attribute::x20::AttributeListAttr; use crate::attribute::x30::FileNameAttr; -use crate::attribute::header::{MftAttributeHeader, ResidentHeader}; +use crate::attribute::header::{MftAttributeHeader, ResidentHeader, NonResidentHeader}; use crate::attribute::x40::ObjectIdAttr; use crate::attribute::x80::DataAttr; use crate::attribute::x90::IndexRootAttr; +use crate::attribute::non_resident_attr::NonResidentAttr; use serde::Serialize; #[derive(Serialize, Clone, Debug)] @@ -31,7 +34,17 @@ pub struct MftAttribute { pub data: MftAttributeContent, } -impl MftAttributeContent { +impl MftAttributeContent { + pub fn from_stream_non_resident( + stream: &mut S, + header: &MftAttributeHeader, + resident: &NonResidentHeader, + ) -> Result { + Ok(MftAttributeContent::DataRun( + NonResidentAttr::from_stream(stream, header, resident)?, + )) + } + pub fn from_stream_resident( stream: &mut S, header: &MftAttributeHeader, @@ -99,6 +112,7 @@ impl MftAttributeContent { _ => None, } } + /// Converts the given attributes into a `ObjectIdAttr`, consuming the object attribute object. pub fn into_object_id(self) -> Option { match self { @@ -113,6 +127,7 @@ impl MftAttributeContent { _ => None, } } + /// Converts the given attributes into a `DataAttr`, consuming the object attribute object. pub fn into_data(self) -> Option { match self { @@ -128,6 +143,14 @@ impl MftAttributeContent { _ => None, } } + + /// Converts the given attributes into a `NonResidentAttr`, consuming the object attribute object. + pub fn into_data_runs(self) -> Option { + match self { + MftAttributeContent::DataRun(content) => Some(content), + _ => None, + } + } } #[derive(Serialize, Clone, Debug)] @@ -140,6 +163,7 @@ pub enum MftAttributeContent { AttrX40(ObjectIdAttr), AttrX80(DataAttr), AttrX90(IndexRootAttr), + DataRun(NonResidentAttr), /// Empty - used when data is non resident. None, } diff --git a/src/attribute/non_resident_attr.rs b/src/attribute/non_resident_attr.rs new file mode 100644 index 0000000..9def676 --- /dev/null +++ b/src/attribute/non_resident_attr.rs @@ -0,0 +1,42 @@ +use crate::err::{Error, Result}; +use crate::attribute::header::{MftAttributeHeader, NonResidentHeader}; +use crate::attribute::data_run::{DataRun, decode_data_runs}; + +use std::io::{Read, Seek, SeekFrom}; +use serde::Serialize; + +#[derive(Serialize, Clone, Debug)] +pub struct NonResidentAttr { + pub data_runs: Vec +} + +impl NonResidentAttr { + pub fn from_stream( + stream: &mut S, + header: &MftAttributeHeader, + resident: &NonResidentHeader, + ) -> Result { + let data_run_bytes_count = (header.record_length - u32::from(resident.datarun_offset)) as usize; + let mut data_run_bytes = vec![0_u8; data_run_bytes_count]; + if resident.valid_data_length != 0 { + stream.seek(SeekFrom::Start(header.start_offset + u64::from(resident.datarun_offset)))?; + stream.read_exact(&mut data_run_bytes)?; + if let Some(data_runs) = decode_data_runs(&data_run_bytes) { + Ok(Self { + data_runs + }) + } + else { + Err(Error::FailedToDecodeDataRuns { + bad_data_runs: data_run_bytes, + }) + } + } + else { + let data_runs = Vec::new(); + Ok(Self { + data_runs + }) + } + } +} diff --git a/src/attribute/x20.rs b/src/attribute/x20.rs index 140153c..8de0e92 100644 --- a/src/attribute/x20.rs +++ b/src/attribute/x20.rs @@ -17,6 +17,7 @@ pub struct AttributeListAttr { /// A list of AttributeListEntry that make up this AttributeListAttr pub entries: Vec, } + impl AttributeListAttr { /// Read AttributeListAttr from stream. Stream should be the size of the attribute's data itself /// if no stream_size is passed in. diff --git a/src/attribute/x30.rs b/src/attribute/x30.rs index 184dede..fadd225 100644 --- a/src/attribute/x30.rs +++ b/src/attribute/x30.rs @@ -24,7 +24,7 @@ pub enum FileNamespace { Win32AndDos = 3, } -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize, Clone, Debug, PartialEq)] pub struct FileNameAttr { pub parent: MftReference, pub created: DateTime, diff --git a/src/attribute/x90.rs b/src/attribute/x90.rs index cefc7d8..2bef640 100644 --- a/src/attribute/x90.rs +++ b/src/attribute/x90.rs @@ -1,9 +1,17 @@ use std::io::{Read, Seek}; -use crate::err::Result; +use crate::impl_serialize_for_bitflags; +use crate::err::{Error, Result}; +use crate::attribute::x30::FileNameAttr; + use byteorder::{LittleEndian, ReadBytesExt}; +use bitflags::bitflags; use serde::Serialize; +use winstructs::ntfs::mft_reference::MftReference; +use std::io::SeekFrom; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; /// $IndexRoot Attribute #[derive(Serialize, Clone, Debug)] @@ -12,21 +20,149 @@ pub struct IndexRootAttr { pub attribute_type: u32, /// Collation rule used to sort the index entries. /// If type is $FILENAME, this must be COLLATION_FILENAME - pub collation_rule: u32, + pub collation_rule: IndexCollationRules, /// The index entry size pub index_entry_size: u32, /// The index entry number of cluster blocks - pub index_entry_number_of_cluster_blocks: u32, + pub index_entry_number_of_cluster_blocks: u32, // really 1 byte with 3 bytes padding + + pub relative_offset_to_index_node: u32, + pub index_node_length: u32, + pub index_node_allocation_length: u32, + pub index_root_flags: IndexRootFlags, // 0x00 = Small Index (fits in Index Root); 0x01 = Large index (Index Allocation needed) + pub index_entries: IndexEntries +} + +/// Enum sources: +/// https://opensource.apple.com/source/ntfs/ntfs-52/kext/ntfs_layout.h +/// https://docs.huihoo.com/doxygen/linux/kernel/3.7/layout_8h_source.html +/// +#[derive(Serialize, Debug, Copy, Clone, Eq, PartialEq)] +#[repr(u32)] +#[derive(FromPrimitive)] +pub enum IndexCollationRules { + CollationBinary = 0x00, + CollationFilename = 0x01, + CollationUnicodeString = 0x02, + CollationNtofsUlong = 0x10, + CollationNtofsSid = 0x11, + CollationNtofsSecurityHash = 0x12, + CollationNtofsUlongs = 0x13, +} + +bitflags! { + pub struct IndexRootFlags: u32 { + const SMALL_INDEX = 0x00; + const LARGE_INDEX = 0x01; + } } +impl_serialize_for_bitflags! {IndexRootFlags} impl IndexRootAttr { /// Data size should be either 16 or 64 pub fn from_stream(stream: &mut S) -> Result { + let attribute_type = stream.read_u32::()?; + let collation_rule_val = stream.read_u32::()?; + let collation_rule = IndexCollationRules::from_u32(collation_rule_val); + let collation_rule = match collation_rule { + None => return Err(Error::UnknownCollationType { + collation_type: collation_rule_val, + }), + Some(collation_rule) => collation_rule + }; + let index_entry_size = stream.read_u32::()?; + let index_entry_number_of_cluster_blocks = stream.read_u32::()?; + let index_node_start_pos = stream.stream_position().unwrap(); + let relative_offset_to_index_node = stream.read_u32::()?; + let index_node_length = stream.read_u32::()?; + let index_node_allocation_length = stream.read_u32::()?; + let index_root_flags = IndexRootFlags::from_bits_truncate(stream.read_u32::()?); + let index_entries = IndexEntries::from_stream(stream, index_node_length, index_node_start_pos)?; + Ok(IndexRootAttr { - attribute_type: stream.read_u32::()?, - collation_rule: stream.read_u32::()?, - index_entry_size: stream.read_u32::()?, - index_entry_number_of_cluster_blocks: stream.read_u32::()?, + attribute_type, + collation_rule, + index_entry_size, + index_entry_number_of_cluster_blocks, + relative_offset_to_index_node, + index_node_length, + index_node_allocation_length, + index_root_flags, + index_entries + }) + } +} + +#[derive(Serialize, Clone, Debug, PartialEq)] +pub struct IndexEntryHeader { + pub mft_reference: MftReference, + pub index_record_length: u16, + pub attr_fname_length: u16, + pub flags: IndexEntryFlags, + pub fname_info: FileNameAttr +} +bitflags! { + pub struct IndexEntryFlags: u32 { + const INDEX_ENTRY_NODE = 0x01; + const INDEX_ENTRY_END = 0x02; + } +} +impl_serialize_for_bitflags! {IndexEntryFlags} + +impl IndexEntryHeader { + pub fn from_stream(stream: &mut S) -> Result> { + let start_pos = stream.stream_position().unwrap(); + + let mft_reference = + MftReference::from_reader(stream).map_err(Error::failed_to_read_mft_reference)?; + if mft_reference.entry > 0 && mft_reference.sequence > 0 { + let index_record_length = stream.read_u16::()?; + let end_pos = start_pos + u64::from(index_record_length); + let attr_fname_length = stream.read_u16::()?; + let flags = IndexEntryFlags::from_bits_truncate(stream.read_u32::()?); + let fname_info = FileNameAttr::from_stream(stream)?; + + stream.seek(SeekFrom::Start(end_pos)).unwrap(); + + Ok(Some(IndexEntryHeader { + mft_reference, + index_record_length, + attr_fname_length, + flags, + fname_info + })) + } + else { + Ok(None) + } + } +} + +#[derive(Serialize, Clone, Debug)] +pub struct IndexEntries { + pub index_entries: Vec +} + +impl IndexEntries { + pub fn from_stream( + stream: &mut S, + index_node_length: u32, + index_node_start_pos: u64 + ) -> Result { + let end_pos = index_node_start_pos + u64::from(index_node_length); + + let mut index_entries: Vec = Vec::new(); + while stream.stream_position().unwrap() < end_pos + { + let index_entry = IndexEntryHeader::from_stream(stream)?; + match index_entry { + Some(inner) => index_entries.push(inner), + None => break, + } + } + + Ok(IndexEntries{ + index_entries }) } } diff --git a/src/entry.rs b/src/entry.rs index cc046d9..cfb4faf 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -84,10 +84,10 @@ pub struct EntryHeader { } bitflags! { pub struct EntryFlags: u16 { - const ALLOCATED = 0x01; - const INDEX_PRESENT = 0x02; - const UNKNOWN_1 = 0x04; - const UNKNOWN_2 = 0x08; + const ALLOCATED = 0x01; + const INDEX_PRESENT = 0x02; + const IS_EXTENSION = 0x04; //Record is an exension (Set for records in the $Extend directory) + const SPECIAL_INDEX_PRESENT = 0x08; //Special index present (Set for non-directory records containing an index: $Secure, $ObjID, $Quota, $Reparse) } } @@ -362,7 +362,16 @@ impl MftEntry { Err(e) => return Some(Err(e)), } } - ResidentialHeader::NonResident(_) => MftAttributeContent::None, + ResidentialHeader::NonResident(ref resident) => { + match MftAttributeContent::from_stream_non_resident( + &mut cursor, + &header, + resident, + ) { + Ok(content) => content, + Err(e) => return Some(Err(e)), + } + } }; return Some(Ok(MftAttribute { diff --git a/src/err.rs b/src/err.rs index 6073830..6eac3d6 100644 --- a/src/err.rs +++ b/src/err.rs @@ -24,6 +24,8 @@ pub enum Error { InvalidEntrySignature { bad_sig: Vec }, #[error("Unknown `AttributeType`: {:04X}", attribute_type)] UnknownAttributeType { attribute_type: u32 }, + #[error("Unknown collation type {}", collation_type)] + UnknownCollationType { collation_type: u32 }, #[error("Unknown filename namespace {}", namespace)] UnknownNamespace { namespace: u8 }, #[error("Unhandled resident flag: {} (offset: {})", flag, offset)] @@ -45,6 +47,8 @@ pub enum Error { FailedToReadWindowsTime { source: winstructs::err::Error }, #[error("Failed to read GUID")] FailedToReadGuid { source: winstructs::err::Error }, + #[error("Failed to decode data runs")] + FailedToDecodeDataRuns { bad_data_runs: Vec }, #[error("An unexpected error has occurred: {}", detail)] Any { detail: String }, } diff --git a/tests/fixtures.rs b/tests/fixtures.rs index bc831b3..20c5451 100644 --- a/tests/fixtures.rs +++ b/tests/fixtures.rs @@ -23,5 +23,9 @@ pub fn samples_dir() -> PathBuf { } pub fn mft_sample() -> PathBuf { - samples_dir().join("MFT") + mft_sample_name("MFT") +} + +pub fn mft_sample_name(filename: &str) -> PathBuf { + samples_dir().join(filename) } diff --git a/tests/test_data_run.rs b/tests/test_data_run.rs new file mode 100644 index 0000000..2b17f63 --- /dev/null +++ b/tests/test_data_run.rs @@ -0,0 +1,115 @@ +mod fixtures; + +use fixtures::*; +use mft::mft::MftParser; +use mft::attribute::{MftAttribute, MftAttributeType}; +use mft::attribute::data_run::{DataRun, RunType, decode_data_runs}; + +#[test] +fn test_runs() { + // Examples taken from the linux-ntfs guide + assert_eq!( + decode_data_runs(&[0x21, 0x18, 0x34, 0x56, 0x00]), + Some(vec![ + DataRun {lcn_length: 0x18, lcn_offset: 0x5634, run_type: RunType::Standard} + ]) + ); + + // this panics in the original github code + assert_eq!( + decode_data_runs(&[0x11, 0x30, 0x20, 0x01, 0x60, 0x11, 0x10, 0x30, 0x00]), + Some(vec![ + DataRun {lcn_length: 0x30, lcn_offset: 0x20, run_type: RunType::Standard}, + DataRun {lcn_length: 0x60, lcn_offset: 0, run_type: RunType::Sparse}, + DataRun {lcn_length: 0x10, lcn_offset: 0x30, run_type: RunType::Standard}, + ]) + ); + + assert_eq!( + decode_data_runs(&[0x31, 0x38, 0x73, 0x25, 0x34, 0x32, 0x14, 0x01, 0xE5, 0x11, 0x02, 0x31, 0x42, 0xAA, 0x00, 0x03, 0x00]), + Some(vec![ + DataRun {lcn_length: 0x38, lcn_offset: 0x342573, run_type: RunType::Standard}, + DataRun {lcn_length: 0x114, lcn_offset: 0x363758, run_type: RunType::Standard}, + DataRun {lcn_length: 0x42, lcn_offset: 0x393802, run_type: RunType::Standard}, + ]) + ); + + assert_eq!( + decode_data_runs(&[0x03, 0x80, 0xE4, 0x07, 0x31, 0x47, 0x62, 0x72, 0x3C, 0x31, 0x49, 0xC1, 0x9C, 0x02, 0x32, 0xA0, 0x00, 0x98, 0x80, 0xFA, 0x32, 0xA0, 0x00, 0xE4, 0xEC, 0x06, 0x31, 0x40, 0x0A, 0x93, 0xFD, 0x32, 0xA0, 0x00, 0x21, 0x12, 0x04, 0x32, 0xEB, 0x00, 0x7B, 0x16, 0xF4, 0x32, 0x3D, 0x01, 0x57, 0xCB, 0x0C, 0x21, 0x38, 0x18, 0x9D, 0x32, 0x48, 0x01, 0xFC, 0x40, 0x03, 0x21, 0x38, 0x54, 0x01, 0x32, 0x36, 0x01, 0x46, 0x46, 0x0B, 0x31, 0x68, 0x8E, 0xD5, 0xEC, 0x31, 0x70, 0x58, 0xE2, 0x07, 0x31, 0x72, 0xB9, 0x2E, 0xF8, 0x32, 0x80, 0x00, 0x37, 0x15, 0x08, 0x32, 0x81, 0x00, 0x08, 0xEA, 0xF7, 0x32, 0x81, 0x00, 0xD2, 0x13, 0x01, 0x22, 0x8A, 0x00, 0xD3, 0x3E, 0x31, 0x74, 0x33, 0x1E, 0x04, 0x32, 0x98, 0x00, 0xFC, 0x0D, 0x0A, 0x31, 0x68, 0xBF, 0xE2, 0xF1, 0x32, 0x80, 0x00, 0xD1, 0x0A, 0xFE, 0x32, 0x80, 0x00, 0x8D, 0x0F, 0x16, 0x32, 0x80, 0x00, 0xB8, 0xD3, 0xEC, 0x32, 0x80, 0x00, 0x69, 0xFB, 0x01, 0x32, 0xD8, 0x02, 0x86, 0xA2, 0x06, 0x31, 0x42, 0xB6, 0x5A, 0xF9, 0x32, 0xF3, 0x00, 0x9B, 0xFF, 0xF7, 0x21, 0x73, 0xFB, 0xE5, 0x32, 0x80, 0x00, 0xE1, 0xE6, 0x12, 0x32, 0x00, 0x01, 0x43, 0xFC, 0xEB, 0x22, 0x00, 0x01, 0x00, 0xFF, 0x32, 0xC0, 0x00, 0xA6, 0xA9, 0x17, 0x21, 0x43, 0x8F, 0xEC, 0x32, 0x00, 0x01, 0x89, 0x70, 0xE5, 0x32, 0x00, 0x01, 0x02, 0xD0, 0x1A, 0x32, 0x80, 0x00, 0x4B, 0x7A, 0xEE, 0x21, 0x7D, 0x8A, 0xFD, 0x22, 0x80, 0x00, 0xAE, 0x03, 0x22, 0x80, 0x00, 0x85, 0x9C, 0x32, 0x80, 0x00, 0xA0, 0x3B, 0x14, 0x32, 0xE4, 0x00, 0xFE, 0x40, 0xFD, 0x31, 0x24, 0x5E, 0x26, 0xF3, 0x12, 0xC1, 0x00, 0x25, 0x31, 0x37, 0x13, 0xD5, 0x0C, 0x12, 0x80, 0x00, 0x47, 0x22, 0x80, 0x00, 0x90, 0x00, 0x32, 0x86, 0x00, 0x9B, 0x3D, 0xE9, 0x32, 0x80, 0x00, 0x8A, 0xB3, 0x17, 0x32, 0xFA, 0x00, 0x49, 0x9B, 0xED, 0x32, 0x00, 0x01, 0xB7, 0x62, 0x12, 0x00, 0x00]), + Some(vec![ + DataRun {lcn_length: 517248, lcn_offset: 0, run_type: RunType::Sparse}, + DataRun {lcn_length: 71, lcn_offset: 3961442, run_type: RunType::Standard}, + DataRun {lcn_length: 73, lcn_offset: 4132643, run_type: RunType::Standard}, + DataRun {lcn_length: 160, lcn_offset: 3772347, run_type: RunType::Standard}, + DataRun {lcn_length: 160, lcn_offset: 4226207, run_type: RunType::Standard}, + DataRun {lcn_length: 64, lcn_offset: 4067241, run_type: RunType::Standard}, + DataRun {lcn_length: 160, lcn_offset: 4334026, run_type: RunType::Standard}, + DataRun {lcn_length: 235, lcn_offset: 3553349, run_type: RunType::Standard}, + DataRun {lcn_length: 317, lcn_offset: 4391836, run_type: RunType::Standard}, + DataRun {lcn_length: 56, lcn_offset: 4366516, run_type: RunType::Standard}, + DataRun {lcn_length: 328, lcn_offset: 4579760, run_type: RunType::Standard}, + DataRun {lcn_length: 56, lcn_offset: 4580100, run_type: RunType::Standard}, + DataRun {lcn_length: 310, lcn_offset: 5318986, run_type: RunType::Standard}, + DataRun {lcn_length: 104, lcn_offset: 4062936, run_type: RunType::Standard}, + DataRun {lcn_length: 112, lcn_offset: 4579632, run_type: RunType::Standard}, + DataRun {lcn_length: 114, lcn_offset: 4067305, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4597024, run_type: RunType::Standard}, + DataRun {lcn_length: 129, lcn_offset: 4067112, run_type: RunType::Standard}, + DataRun {lcn_length: 129, lcn_offset: 4137722, run_type: RunType::Standard}, + DataRun {lcn_length: 138, lcn_offset: 4153805, run_type: RunType::Standard}, + DataRun {lcn_length: 116, lcn_offset: 4423680, run_type: RunType::Standard}, + DataRun {lcn_length: 152, lcn_offset: 5082620, run_type: RunType::Standard}, + DataRun {lcn_length: 104, lcn_offset: 4157627, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4029324, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 5475097, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4218577, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4348474, run_type: RunType::Standard}, + DataRun {lcn_length: 728, lcn_offset: 4783296, run_type: RunType::Standard}, + DataRun {lcn_length: 66, lcn_offset: 4347766, run_type: RunType::Standard}, + DataRun {lcn_length: 243, lcn_offset: 3823377, run_type: RunType::Standard}, + DataRun {lcn_length: 115, lcn_offset: 3816716, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 5055469, run_type: RunType::Standard}, + DataRun {lcn_length: 256, lcn_offset: 3743792, run_type: RunType::Standard}, + DataRun {lcn_length: 256, lcn_offset: 3743536, run_type: RunType::Standard}, + DataRun {lcn_length: 192, lcn_offset: 5294294, run_type: RunType::Standard}, + DataRun {lcn_length: 67, lcn_offset: 5289317, run_type: RunType::Standard}, + DataRun {lcn_length: 256, lcn_offset: 3548654, run_type: RunType::Standard}, + DataRun {lcn_length: 256, lcn_offset: 5305840, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4157499, run_type: RunType::Standard}, + DataRun {lcn_length: 125, lcn_offset: 4156869, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4157811, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 4132344, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 5458328, run_type: RunType::Standard}, + DataRun {lcn_length: 228, lcn_offset: 5278358, run_type: RunType::Standard}, + DataRun {lcn_length: 36, lcn_offset: 4436212, run_type: RunType::Standard}, + DataRun {lcn_length: 193, lcn_offset: 4436249, run_type: RunType::Standard}, + DataRun {lcn_length: 55, lcn_offset: 5277228, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 5277299, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 5277443, run_type: RunType::Standard}, + DataRun {lcn_length: 134, lcn_offset: 3785886, run_type: RunType::Standard}, + DataRun {lcn_length: 128, lcn_offset: 5339176, run_type: RunType::Standard}, + DataRun {lcn_length: 250, lcn_offset: 4133745, run_type: RunType::Standard}, + DataRun {lcn_length: 256, lcn_offset: 5338664, run_type: RunType::Standard}, + ]) + ); +} + +#[test] +// if this test fails, most likely the datarun_offset is not being respected +fn test_data_runs_at_offset() { + let sample = mft_sample_name("entry_data_run_at_offset"); + let mut parser = MftParser::from_path(sample).unwrap(); + + for record in parser.iter_entries().take(1).filter_map(|a| a.ok()) { + let attributes: Vec = record.iter_attributes().filter_map(Result::ok).collect(); + for attribute in attributes { + if attribute.header.type_code == MftAttributeType::DATA { + let data_runs = attribute.data.into_data_runs().unwrap(); + assert_eq!(data_runs.data_runs.len(), 53); + assert_eq!(data_runs.data_runs[0].lcn_offset, 0); + assert_eq!(data_runs.data_runs[0].lcn_length, 517248); + assert_eq!(data_runs.data_runs[0].run_type, RunType::Sparse); + } + } + } +} diff --git a/tests/test_entry.rs b/tests/test_entry.rs index 68de3f2..8066019 100644 --- a/tests/test_entry.rs +++ b/tests/test_entry.rs @@ -1,4 +1,13 @@ +mod fixtures; + +use fixtures::*; use mft::entry::MftEntry; +use mft::mft::MftParser; +use mft::attribute::{MftAttribute, MftAttributeType, FileAttributeFlags}; +use mft::attribute::x90::{IndexEntryHeader, IndexEntryFlags, IndexCollationRules}; +use mft::attribute::x30::{FileNameAttr, FileNamespace}; +use winstructs::ntfs:: mft_reference::MftReference; +use winstructs::timestamp::WinTimestamp; use serde_json; #[test] @@ -16,3 +25,53 @@ fn test_entry_invalid_fixup_value() { serde_json::value::Value::from(false) ); } + +#[test] +fn test_entry_index_root() { + let sample = mft_sample_name("entry_multiple_index_root_entries"); + let mut parser = MftParser::from_path(sample).unwrap(); + + for record in parser.iter_entries().take(1).filter_map(|a| a.ok()) { + let attributes: Vec = record.iter_attributes().filter_map(Result::ok).collect(); + for attribute in attributes { + if attribute.header.type_code == MftAttributeType::IndexRoot { + let index_root = attribute.data.into_index_root().unwrap(); + assert_eq!(index_root.collation_rule, IndexCollationRules::CollationFilename); + let index_entries = index_root.index_entries.index_entries; + assert_eq!(index_entries.len(), 4); + + let created = WinTimestamp::new(&[0x00, 0x00, 0xC1, 0x03, 0xDB, 0x6A, 0xC6, 0x01]).unwrap().to_datetime(); + let mft_modified = WinTimestamp::new(&[0x76, 0x86, 0xF6, 0x8C, 0x04, 0x64, 0xCA, 0x01]).unwrap().to_datetime(); + + let index_entry_comp = IndexEntryHeader { + mft_reference: MftReference { + entry: 26399, + sequence: 1, + }, + index_record_length: 136, + attr_fname_length: 110, + flags: IndexEntryFlags::INDEX_ENTRY_NODE, + fname_info: FileNameAttr { + parent: MftReference { + entry: 26359, + sequence: 1, + }, + created: created, + modified: created, + mft_modified: mft_modified, + accessed: mft_modified, + logical_size: 4096, + physical_size: 1484, + flags: FileAttributeFlags::FILE_ATTRIBUTE_ARCHIVE, + reparse_value: 0, + name_length: 22, + namespace: FileNamespace::Win32, + name: "test_returnfuncptrs.py".to_string() + } + }; + let last_index_entry = &index_entries[3]; + assert_eq!(last_index_entry, &index_entry_comp); + } + } + } +}