From 777cbb028f22c73541f9340f6b94c5dd375f4384 Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Fri, 4 Jun 2021 12:22:33 +0200 Subject: Add `NtfsIndexAllocation`, iterators for Index Records and Index Entries This allows iterating through all nodes of an arbitrary index. Tested with a filesystem that has 512 directories in the root. Still lacks functions to traverse an index in-order or find an item efficiently. --- src/attribute.rs | 8 +- src/error.rs | 14 +- src/index_entry.rs | 217 ++++++++++++++++++++++++++++++ src/index_record.rs | 141 +++++++++++++++++++ src/lib.rs | 3 + src/ntfs_file.rs | 20 +-- src/record.rs | 21 +++ src/structured_values/index_allocation.rs | 111 +++++++++++++++ src/structured_values/index_root.rs | 77 ++++++++--- src/structured_values/mod.rs | 5 +- 10 files changed, 580 insertions(+), 37 deletions(-) create mode 100644 src/index_entry.rs create mode 100644 src/index_record.rs create mode 100644 src/record.rs create mode 100644 src/structured_values/index_allocation.rs diff --git a/src/attribute.rs b/src/attribute.rs index a13e740..2d2bdc5 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -7,8 +7,8 @@ use crate::ntfs::Ntfs; use crate::ntfs_file::NtfsFile; use crate::string::NtfsString; use crate::structured_values::{ - NewNtfsStructuredValue, NtfsFileName, NtfsIndexRoot, NtfsObjectId, NtfsStandardInformation, - NtfsStructuredValue, NtfsVolumeInformation, NtfsVolumeName, + NewNtfsStructuredValue, NtfsFileName, NtfsIndexAllocation, NtfsIndexRoot, NtfsObjectId, + NtfsStandardInformation, NtfsStructuredValue, NtfsVolumeInformation, NtfsVolumeName, }; use binread::io::{Read, Seek, SeekFrom}; use binread::{BinRead, BinReaderExt}; @@ -258,6 +258,10 @@ impl<'n> NtfsAttribute<'n> { let inner = NtfsIndexRoot::new(fs, value, length)?; Ok(NtfsStructuredValue::IndexRoot(inner)) } + NtfsAttributeType::IndexAllocation => { + let inner = NtfsIndexAllocation::new(fs, value, length)?; + Ok(NtfsStructuredValue::IndexAllocation(inner)) + } ty => Err(NtfsError::UnsupportedStructuredValue { position: self.position, ty, diff --git a/src/error.rs b/src/error.rs index 2d34537..99a4e61 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,12 +29,24 @@ pub enum NtfsError { InvalidLcnPositionInDataRunHeader { position: u64, lcn_position: i64 }, /// The requested NTFS file {n} is invalid InvalidNtfsFile { n: u64 }, - /// The NTFS file at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} + /// The NTFS file record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} InvalidNtfsFileSignature { position: u64, expected: &'static [u8], actual: [u8; 4], }, + /// The NTFS index record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} + InvalidNtfsIndexSignature { + position: u64, + expected: &'static [u8], + actual: [u8; 4], + }, + /// The NTFS index record at byte position {position:#010x} should have a maximum of {expected} bytes, but it indicates {actual} bytes. + InvalidNtfsIndexSize { + position: u64, + expected: u32, + actual: u32, + }, /// The given time can't be represented as an NtfsTime InvalidNtfsTime, /// A record size field in the BIOS Parameter Block denotes {size_info}, which is invalid considering the cluster size of {cluster_size} bytes diff --git a/src/index_entry.rs b/src/index_entry.rs new file mode 100644 index 0000000..65b24e3 --- /dev/null +++ b/src/index_entry.rs @@ -0,0 +1,217 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later + +use crate::attribute_value::NtfsAttributeValue; +use crate::error::Result; +use crate::structured_values::NewNtfsStructuredValue; +use crate::traits::NtfsReadSeek; +use binread::io::{Read, Seek, SeekFrom}; +use binread::{BinRead, BinReaderExt}; +use bitflags::bitflags; +use core::iter::FusedIterator; +use core::marker::PhantomData; +use core::mem; + +/// Size of all [`IndexEntryHeader`] fields plus some reserved bytes. +const INDEX_ENTRY_HEADER_SIZE: i64 = 16; + +#[derive(BinRead, Clone, Debug)] +struct IndexEntryHeader { + file_ref: u64, + index_entry_length: u16, + key_length: u16, + flags: u8, +} + +#[derive(Clone, Debug)] +pub struct NtfsIndexEntry<'n, K> +where + K: NewNtfsStructuredValue<'n>, +{ + header: IndexEntryHeader, + value: NtfsAttributeValue<'n>, + key_type: PhantomData, +} + +bitflags! { + pub struct NtfsIndexEntryFlags: u8 { + /// This index entry points to a sub-node. + const HAS_SUBNODE = 0x01; + /// This is the last index entry in the list. + const LAST_ENTRY = 0x02; + } +} + +impl<'n, K> NtfsIndexEntry<'n, K> +where + K: NewNtfsStructuredValue<'n>, +{ + pub(crate) fn new(fs: &mut T, value: NtfsAttributeValue<'n>) -> Result + where + T: Read + Seek, + { + let mut value_attached = value.clone().attach(fs); + let header = value_attached.read_le::()?; + let key_type = PhantomData; + + let entry = Self { + header, + value, + key_type, + }; + + Ok(entry) + } + + pub fn flags(&self) -> NtfsIndexEntryFlags { + NtfsIndexEntryFlags::from_bits_truncate(self.header.flags) + } + + pub fn index_entry_length(&self) -> u16 { + self.header.index_entry_length + } + + pub fn key_length(&self) -> u16 { + self.header.key_length + } + + /// Returns the structured value of the key of this Index Entry, + /// or `None` if this Index Entry has no key. + /// The last Index Entry never has a key. + pub fn key_structured_value(&self, fs: &mut T) -> Option> + where + T: Read + Seek, + { + if self.header.key_length == 0 || self.flags().contains(NtfsIndexEntryFlags::LAST_ENTRY) { + return None; + } + + let mut value = self.value.clone(); + iter_try!(value.seek(fs, SeekFrom::Current(INDEX_ENTRY_HEADER_SIZE))); + let length = self.header.key_length as u64; + + let structured_value = iter_try!(K::new(fs, value, length)); + Some(Ok(structured_value)) + } + + /// Returns the Virtual Cluster Number (VCN) of the subnode of this Index Entry, + /// or `None` if this Index Entry has no subnode. + pub fn subnode_vcn(&self, fs: &mut T) -> Option> + where + T: Read + Seek, + { + if !self.flags().contains(NtfsIndexEntryFlags::HAS_SUBNODE) { + return None; + } + + // Read the subnode VCN from the very end of the Index Entry. + let mut value_attached = self.value.clone().attach(fs); + iter_try!(value_attached.seek(SeekFrom::Current( + self.index_entry_length() as i64 - mem::size_of::() as i64 + ))); + let vcn = iter_try!(value_attached.read_le::()); + + Some(Ok(vcn)) + } +} + +#[derive(Clone, Debug)] +pub struct NtfsIndexEntries<'n, K> +where + K: NewNtfsStructuredValue<'n>, +{ + value: NtfsAttributeValue<'n>, + end: u64, + key_type: PhantomData, +} + +impl<'n, K> NtfsIndexEntries<'n, K> +where + K: NewNtfsStructuredValue<'n>, +{ + pub(crate) fn new(value: NtfsAttributeValue<'n>, end: u64) -> Self { + debug_assert!(end <= value.len()); + let key_type = PhantomData; + + Self { + value, + end, + key_type, + } + } + + pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsIndexEntriesAttached<'n, 'a, K, T> + where + T: Read + Seek, + { + NtfsIndexEntriesAttached::new(fs, self) + } + + pub fn next(&mut self, fs: &mut T) -> Option>> + where + T: Read + Seek, + { + if self.value.stream_position() >= self.end { + return None; + } + + // Get the current entry. + let entry = iter_try!(NtfsIndexEntry::new(fs, self.value.clone())); + + if entry.flags().contains(NtfsIndexEntryFlags::LAST_ENTRY) { + // This is the last entry. + // Ensure that we don't read any other entries by seeking to the end. + iter_try!(self.value.seek(fs, SeekFrom::Start(self.end))); + } else { + // This is not the last entry. + // Advance our iterator to the next entry. + iter_try!(self + .value + .seek(fs, SeekFrom::Current(entry.index_entry_length() as i64))); + } + + Some(Ok(entry)) + } +} + +pub struct NtfsIndexEntriesAttached<'n, 'a, K, T> +where + K: NewNtfsStructuredValue<'n>, + T: Read + Seek, +{ + fs: &'a mut T, + index_entries: NtfsIndexEntries<'n, K>, +} + +impl<'n, 'a, K, T> NtfsIndexEntriesAttached<'n, 'a, K, T> +where + K: NewNtfsStructuredValue<'n>, + T: Read + Seek, +{ + fn new(fs: &'a mut T, index_entries: NtfsIndexEntries<'n, K>) -> Self { + Self { fs, index_entries } + } + + pub fn detach(self) -> NtfsIndexEntries<'n, K> { + self.index_entries + } +} + +impl<'n, 'a, K, T> Iterator for NtfsIndexEntriesAttached<'n, 'a, K, T> +where + K: NewNtfsStructuredValue<'n>, + T: Read + Seek, +{ + type Item = Result>; + + fn next(&mut self) -> Option { + self.index_entries.next(self.fs) + } +} + +impl<'n, 'a, K, T> FusedIterator for NtfsIndexEntriesAttached<'n, 'a, K, T> +where + K: NewNtfsStructuredValue<'n>, + T: Read + Seek, +{ +} diff --git a/src/index_record.rs b/src/index_record.rs new file mode 100644 index 0000000..fec6e09 --- /dev/null +++ b/src/index_record.rs @@ -0,0 +1,141 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later + +use crate::attribute_value::NtfsAttributeValue; +use crate::error::{NtfsError, Result}; +use crate::index_entry::NtfsIndexEntries; +use crate::record::RecordHeader; +use crate::structured_values::NewNtfsStructuredValue; +use crate::traits::NtfsReadSeek; +use binread::io::{Read, Seek, SeekFrom}; +use binread::{BinRead, BinReaderExt}; + +/// Size of all [`IndexRecordHeader`] fields. +const INDEX_RECORD_HEADER_SIZE: u32 = 24; + +#[allow(unused)] +#[derive(BinRead, Clone, Debug)] +struct IndexRecordHeader { + record_header: RecordHeader, + vcn: u64, +} + +/// Size of all [`IndexNodeHeader`] fields plus some reserved bytes. +pub(crate) const INDEX_NODE_HEADER_SIZE: u64 = 16; + +#[derive(BinRead, Clone, Debug)] +pub(crate) struct IndexNodeHeader { + pub(crate) entries_offset: u32, + pub(crate) index_size: u32, + pub(crate) allocated_size: u32, + pub(crate) flags: u8, +} + +#[derive(Clone, Debug)] +pub struct NtfsIndexRecord<'n> { + value: NtfsAttributeValue<'n>, + index_record_header: IndexRecordHeader, + index_node_header: IndexNodeHeader, +} + +const HAS_SUBNODES_FLAG: u8 = 0x01; + +impl<'n> NtfsIndexRecord<'n> { + pub(crate) fn new( + fs: &mut T, + value: NtfsAttributeValue<'n>, + index_record_size: u32, + ) -> Result + where + T: Read + Seek, + { + let mut value_attached = value.clone().attach(fs); + let index_record_header = value_attached.read_le::()?; + let index_node_header = value_attached.read_le::()?; + + let index_record = Self { + value, + index_record_header, + index_node_header, + }; + index_record.validate_signature()?; + index_record.validate_sizes(index_record_size)?; + + Ok(index_record) + } + + pub fn entries(&self, fs: &mut T) -> Result> + where + K: NewNtfsStructuredValue<'n>, + T: Read + Seek, + { + let offset = self.value.stream_position() + INDEX_RECORD_HEADER_SIZE as u64; + let start = offset + self.index_node_header.entries_offset as u64; + let end = offset + self.index_used_size() as u64; + + let mut value = self.value.clone(); + value.seek(fs, SeekFrom::Start(start))?; + + Ok(NtfsIndexEntries::new(value, end)) + } + + /// Returns whether this index node has sub-nodes. + /// Otherwise, this index node is a leaf node. + pub fn has_subnodes(&self) -> bool { + (self.index_node_header.flags & HAS_SUBNODES_FLAG) != 0 + } + + pub fn index_allocated_size(&self) -> u32 { + self.index_node_header.allocated_size + } + + pub fn index_used_size(&self) -> u32 { + self.index_node_header.index_size + } + + fn validate_signature(&self) -> Result<()> { + let signature = &self.index_record_header.record_header.signature; + let expected = b"INDX"; + + if signature == expected { + Ok(()) + } else { + Err(NtfsError::InvalidNtfsIndexSignature { + position: self.value.data_position().unwrap(), + expected, + actual: *signature, + }) + } + } + + fn validate_sizes(&self, index_record_size: u32) -> Result<()> { + // The total size allocated for this index record must not be larger than + // the size defined for all index records of this index. + let total_allocated_size = INDEX_RECORD_HEADER_SIZE + self.index_allocated_size(); + if total_allocated_size > index_record_size { + return Err(NtfsError::InvalidNtfsIndexSize { + position: self.value.data_position().unwrap(), + expected: index_record_size, + actual: total_allocated_size, + }); + } + + // Furthermore, the total used size for this index record must not be + // larger than the total allocated size. + let total_used_size = INDEX_RECORD_HEADER_SIZE + + self + .index_record_header + .record_header + .update_sequence_array_size() + + self.index_used_size(); + if total_used_size > total_allocated_size { + return Err(NtfsError::InvalidNtfsIndexSize { + position: self.value.data_position().unwrap(), + expected: total_allocated_size, + actual: total_used_size, + }); + } + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index b6fed11..0cd1dbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,11 @@ mod attribute_value; mod boot_sector; mod error; mod guid; +mod index_entry; +mod index_record; mod ntfs; mod ntfs_file; +mod record; mod string; pub mod structured_values; mod time; diff --git a/src/ntfs_file.rs b/src/ntfs_file.rs index 4caea85..2d28e27 100644 --- a/src/ntfs_file.rs +++ b/src/ntfs_file.rs @@ -4,6 +4,7 @@ use crate::attribute::NtfsAttributes; use crate::error::{NtfsError, Result}; use crate::ntfs::Ntfs; +use crate::record::RecordHeader; use binread::io::{Read, Seek, SeekFrom}; use binread::{BinRead, BinReaderExt}; use bitflags::bitflags; @@ -25,18 +26,9 @@ pub enum KnownNtfsFile { } #[allow(unused)] -#[derive(BinRead)] -struct NtfsRecordHeader { - signature: [u8; 4], - update_sequence_array_offset: u16, - update_sequence_array_size: u16, - logfile_sequence_number: u64, -} - -#[allow(unused)] -#[derive(BinRead)] -pub(crate) struct NtfsFileRecordHeader { - record_header: NtfsRecordHeader, +#[derive(BinRead, Debug)] +struct FileRecordHeader { + record_header: RecordHeader, sequence_number: u16, hard_link_count: u16, first_attribute_offset: u16, @@ -58,7 +50,7 @@ bitflags! { pub struct NtfsFile<'n> { ntfs: &'n Ntfs, - header: NtfsFileRecordHeader, + header: FileRecordHeader, position: u64, } @@ -68,7 +60,7 @@ impl<'n> NtfsFile<'n> { T: Read + Seek, { fs.seek(SeekFrom::Start(position))?; - let header = fs.read_le::()?; + let header = fs.read_le::()?; let file = Self { ntfs, diff --git a/src/record.rs b/src/record.rs new file mode 100644 index 0000000..91db32e --- /dev/null +++ b/src/record.rs @@ -0,0 +1,21 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later + +use binread::BinRead; + +const UPDATE_SEQUENCE_ELEMENT_SIZE: u32 = 2; + +#[allow(unused)] +#[derive(BinRead, Clone, Debug)] +pub(crate) struct RecordHeader { + pub(crate) signature: [u8; 4], + update_sequence_array_offset: u16, + update_sequence_array_count: u16, + logfile_sequence_number: u64, +} + +impl RecordHeader { + pub(crate) fn update_sequence_array_size(&self) -> u32 { + self.update_sequence_array_count as u32 * UPDATE_SEQUENCE_ELEMENT_SIZE + } +} diff --git a/src/structured_values/index_allocation.rs b/src/structured_values/index_allocation.rs new file mode 100644 index 0000000..24d9e27 --- /dev/null +++ b/src/structured_values/index_allocation.rs @@ -0,0 +1,111 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later + +use crate::attribute_value::NtfsAttributeValue; +use crate::error::Result; +use crate::index_record::NtfsIndexRecord; +use crate::structured_values::index_root::NtfsIndexRoot; +use crate::structured_values::NewNtfsStructuredValue; +use crate::traits::NtfsReadSeek; +use binread::io::{Read, Seek, SeekFrom}; +use core::iter::FusedIterator; + +#[derive(Clone, Debug)] +pub struct NtfsIndexAllocation<'n> { + value: NtfsAttributeValue<'n>, +} + +impl<'n> NtfsIndexAllocation<'n> { + pub fn iter(&self, index_root: &NtfsIndexRoot) -> NtfsIndexRecords<'n> { + let index_record_size = index_root.index_record_size(); + NtfsIndexRecords::new(self.value.clone(), index_record_size) + } +} + +impl<'n> NewNtfsStructuredValue<'n> for NtfsIndexAllocation<'n> { + fn new(_fs: &mut T, value: NtfsAttributeValue<'n>, _length: u64) -> Result + where + T: Read + Seek, + { + Ok(Self { value }) + } +} + +#[derive(Clone, Debug)] +pub struct NtfsIndexRecords<'n> { + value: NtfsAttributeValue<'n>, + index_record_size: u32, +} + +impl<'n> NtfsIndexRecords<'n> { + fn new(value: NtfsAttributeValue<'n>, index_record_size: u32) -> Self { + Self { + value, + index_record_size, + } + } + + pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsIndexRecordsAttached<'n, 'a, T> + where + T: Read + Seek, + { + NtfsIndexRecordsAttached::new(fs, self) + } + + pub fn next(&mut self, fs: &mut T) -> Option>> + where + T: Read + Seek, + { + if self.value.stream_position() >= self.value.len() { + return None; + } + + // Get the current record. + let record = iter_try!(NtfsIndexRecord::new( + fs, + self.value.clone(), + self.index_record_size + )); + + // Advance our iterator to the next record. + iter_try!(self + .value + .seek(fs, SeekFrom::Current(self.index_record_size as i64))); + + Some(Ok(record)) + } +} + +pub struct NtfsIndexRecordsAttached<'n, 'a, T> +where + T: Read + Seek, +{ + fs: &'a mut T, + index_records: NtfsIndexRecords<'n>, +} + +impl<'n, 'a, T> NtfsIndexRecordsAttached<'n, 'a, T> +where + T: Read + Seek, +{ + fn new(fs: &'a mut T, index_records: NtfsIndexRecords<'n>) -> Self { + Self { fs, index_records } + } + + pub fn detach(self) -> NtfsIndexRecords<'n> { + self.index_records + } +} + +impl<'n, 'a, T> Iterator for NtfsIndexRecordsAttached<'n, 'a, T> +where + T: Read + Seek, +{ + type Item = Result>; + + fn next(&mut self) -> Option { + self.index_records.next(self.fs) + } +} + +impl<'n, 'a, T> FusedIterator for NtfsIndexRecordsAttached<'n, 'a, T> where T: Read + Seek {} diff --git a/src/structured_values/index_root.rs b/src/structured_values/index_root.rs index 26c7115..5a97cda 100644 --- a/src/structured_values/index_root.rs +++ b/src/structured_values/index_root.rs @@ -4,34 +4,67 @@ use crate::attribute::NtfsAttributeType; use crate::attribute_value::NtfsAttributeValue; use crate::error::{NtfsError, Result}; +use crate::index_entry::NtfsIndexEntries; +use crate::index_record::{IndexNodeHeader, INDEX_NODE_HEADER_SIZE}; use crate::structured_values::NewNtfsStructuredValue; -use binread::io::{Read, Seek}; +use crate::traits::NtfsReadSeek; +use binread::io::{Read, Seek, SeekFrom}; use binread::{BinRead, BinReaderExt}; /// Size of all [`IndexRootHeader`] fields plus some reserved bytes. -const INDEX_ROOT_HEADER_SIZE: u64 = 32; - -#[derive(BinRead, Clone, Debug)] -struct IndexHeader { - entries_offset: u32, - index_size: u32, - allocated_size: u32, - flags: u8, -} +const INDEX_ROOT_HEADER_SIZE: u64 = 16; #[derive(BinRead, Clone, Debug)] struct IndexRootHeader { ty: u32, collation_rule: u32, - index_block_size: u32, - clusters_per_index_block: i8, - reserved: [u8; 3], - index: IndexHeader, + index_record_size: u32, + clusters_per_index_record: i8, } #[derive(Clone, Debug)] -pub struct NtfsIndexRoot { - header: IndexRootHeader, +pub struct NtfsIndexRoot<'n> { + value: NtfsAttributeValue<'n>, + index_root_header: IndexRootHeader, + index_node_header: IndexNodeHeader, +} + +const LARGE_INDEX_FLAG: u8 = 0x01; + +impl<'n> NtfsIndexRoot<'n> { + pub fn index_allocated_size(&self) -> u32 { + self.index_node_header.allocated_size + } + + pub fn entries(&self, fs: &mut T) -> Result> + where + K: NewNtfsStructuredValue<'n>, + T: Read + Seek, + { + let offset = self.value.stream_position() + INDEX_ROOT_HEADER_SIZE as u64; + let start = offset + self.index_node_header.entries_offset as u64; + let end = offset + self.index_used_size() as u64; + + let mut value = self.value.clone(); + value.seek(fs, SeekFrom::Start(start))?; + + Ok(NtfsIndexEntries::new(value, end)) + } + + pub fn index_record_size(&self) -> u32 { + self.index_root_header.index_record_size + } + + pub fn index_used_size(&self) -> u32 { + self.index_node_header.index_size + } + + /// Returns whether the index belonging to this Index Root is large enough + /// to need an extra Index Allocation attribute. + /// Otherwise, the entire index information is stored in this Index Root. + pub fn is_large_index(&self) -> bool { + (self.index_node_header.flags & LARGE_INDEX_FLAG) != 0 + } } impl<'n> NewNtfsStructuredValue<'n> for NtfsIndexRoot<'n> { @@ -39,7 +72,7 @@ impl<'n> NewNtfsStructuredValue<'n> for NtfsIndexRoot<'n> { where T: Read + Seek, { - if value.len() < INDEX_ROOT_HEADER_SIZE { + if value.len() < INDEX_ROOT_HEADER_SIZE + INDEX_NODE_HEADER_SIZE { return Err(NtfsError::InvalidStructuredValueSize { position: value.data_position().unwrap(), ty: NtfsAttributeType::IndexRoot, @@ -49,8 +82,14 @@ impl<'n> NewNtfsStructuredValue<'n> for NtfsIndexRoot<'n> { } let mut value_attached = value.clone().attach(fs); - let header = value_attached.read_le::()?; + let index_root_header = value_attached.read_le::()?; + value_attached.seek(SeekFrom::Start(INDEX_ROOT_HEADER_SIZE))?; + let index_node_header = value_attached.read_le::()?; - Ok(Self { header }) + Ok(Self { + value, + index_root_header, + index_node_header, + }) } } diff --git a/src/structured_values/mod.rs b/src/structured_values/mod.rs index 9fb743d..b61f24b 100644 --- a/src/structured_values/mod.rs +++ b/src/structured_values/mod.rs @@ -3,6 +3,7 @@ mod attribute_list; mod file_name; +mod index_allocation; mod index_root; mod object_id; mod security_descriptor; @@ -12,6 +13,7 @@ mod volume_name; pub use attribute_list::*; pub use file_name::*; +pub use index_allocation::*; pub use index_root::*; pub use object_id::*; pub use security_descriptor::*; @@ -49,7 +51,8 @@ pub enum NtfsStructuredValue<'n> { ObjectId(NtfsObjectId), VolumeInformation(NtfsVolumeInformation), VolumeName(NtfsVolumeName<'n>), - IndexRoot(NtfsIndexRoot), + IndexRoot(NtfsIndexRoot<'n>), + IndexAllocation(NtfsIndexAllocation<'n>), } pub trait NewNtfsStructuredValue<'n>: Sized { -- cgit v1.2.3