Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/ntfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Finck <colin@reactos.org>2021-06-04 13:22:33 +0300
committerColin Finck <colin@reactos.org>2021-06-04 13:22:33 +0300
commit777cbb028f22c73541f9340f6b94c5dd375f4384 (patch)
tree06ff5f99ba9fabcb76096ecee255ddcf1e40ce92
parentc3a4f05cfe3bf69d683c0c27a3f50f7b9af5a273 (diff)
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.
-rw-r--r--src/attribute.rs8
-rw-r--r--src/error.rs14
-rw-r--r--src/index_entry.rs217
-rw-r--r--src/index_record.rs141
-rw-r--r--src/lib.rs3
-rw-r--r--src/ntfs_file.rs20
-rw-r--r--src/record.rs21
-rw-r--r--src/structured_values/index_allocation.rs111
-rw-r--r--src/structured_values/index_root.rs77
-rw-r--r--src/structured_values/mod.rs5
10 files changed, 580 insertions, 37 deletions
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 <colin@reactos.org>
+// 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<K>,
+}
+
+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<T>(fs: &mut T, value: NtfsAttributeValue<'n>) -> Result<Self>
+ where
+ T: Read + Seek,
+ {
+ let mut value_attached = value.clone().attach(fs);
+ let header = value_attached.read_le::<IndexEntryHeader>()?;
+ 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<T>(&self, fs: &mut T) -> Option<Result<K>>
+ 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<T>(&self, fs: &mut T) -> Option<Result<u64>>
+ 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::<u64>() as i64
+ )));
+ let vcn = iter_try!(value_attached.read_le::<u64>());
+
+ Some(Ok(vcn))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct NtfsIndexEntries<'n, K>
+where
+ K: NewNtfsStructuredValue<'n>,
+{
+ value: NtfsAttributeValue<'n>,
+ end: u64,
+ key_type: PhantomData<K>,
+}
+
+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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsIndexEntry<'n, K>>>
+ 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<NtfsIndexEntry<'n, K>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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 <colin@reactos.org>
+// 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<T>(
+ fs: &mut T,
+ value: NtfsAttributeValue<'n>,
+ index_record_size: u32,
+ ) -> Result<Self>
+ where
+ T: Read + Seek,
+ {
+ let mut value_attached = value.clone().attach(fs);
+ let index_record_header = value_attached.read_le::<IndexRecordHeader>()?;
+ let index_node_header = value_attached.read_le::<IndexNodeHeader>()?;
+
+ 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<K, T>(&self, fs: &mut T) -> Result<NtfsIndexEntries<'n, K>>
+ 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::<NtfsFileRecordHeader>()?;
+ let header = fs.read_le::<FileRecordHeader>()?;
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 <colin@reactos.org>
+// 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 <colin@reactos.org>
+// 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<T>(_fs: &mut T, value: NtfsAttributeValue<'n>, _length: u64) -> Result<Self>
+ 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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsIndexRecord<'n>>>
+ 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<NtfsIndexRecord<'n>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<K, T>(&self, fs: &mut T) -> Result<NtfsIndexEntries<'n, K>>
+ 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::<IndexRootHeader>()?;
+ let index_root_header = value_attached.read_le::<IndexRootHeader>()?;
+ value_attached.seek(SeekFrom::Start(INDEX_ROOT_HEADER_SIZE))?;
+ let index_node_header = value_attached.read_le::<IndexNodeHeader>()?;
- 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 {