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
path: root/src
diff options
context:
space:
mode:
authorColin Finck <colin@reactos.org>2021-07-21 21:16:12 +0300
committerColin Finck <colin@reactos.org>2021-07-21 21:16:12 +0300
commitb659b7861b19dde96819620c71bee883f23d3e13 (patch)
tree9e3a3347868e9c7355fe6cca1a4f62cbede4ae47 /src
parent9fa9dda7eede3ca5689e67ad6e7ca76398443603 (diff)
Implement Update Sequence Array parsing and record fixups.
This is where things get dirty. As NTFS requires us to fix up records, we can't continue our previous design of avoiding dynamic allocations and reading everything on demand via `io::Read`. Instead, we now read an entire record (usually not larger than 4 KiB), fix it up, and store it in a `Vec`. This required changes almost everywhere. It should be noted that many non-resident attributes are not part of a record, which is why structured values are now implemented differently depending on the attribute type. On the plus side, many structures need less borrowing now, which makes them more comfortable to use. I have also added missing sanity checks with precise errors where appropriate.
Diffstat (limited to 'src')
-rw-r--r--src/attribute.rs456
-rw-r--r--src/attribute_value.rs436
-rw-r--r--src/error.rs87
-rw-r--r--src/guid.rs2
-rw-r--r--src/index.rs170
-rw-r--r--src/index_entry.rs235
-rw-r--r--src/index_record.rs119
-rw-r--r--src/ntfs.rs54
-rw-r--r--src/ntfs_file.rs82
-rw-r--r--src/record.rs143
-rw-r--r--src/string.rs17
-rw-r--r--src/structured_values/file_name.rs124
-rw-r--r--src/structured_values/index_allocation.rs82
-rw-r--r--src/structured_values/index_root.rs138
-rw-r--r--src/structured_values/mod.rs30
-rw-r--r--src/structured_values/object_id.rs44
-rw-r--r--src/structured_values/standard_information.rs74
-rw-r--r--src/structured_values/volume_information.rs44
-rw-r--r--src/structured_values/volume_name.rs65
-rw-r--r--src/time.rs6
20 files changed, 1318 insertions, 1090 deletions
diff --git a/src/attribute.rs b/src/attribute.rs
index bb28ce7..6655adb 100644
--- a/src/attribute.rs
+++ b/src/attribute.rs
@@ -1,27 +1,27 @@
// Copyright 2021 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use crate::attribute_value::{NtfsAttributeNonResidentValue, NtfsAttributeValue, NtfsDataRun};
+use crate::attribute_value::{
+ NtfsAttributeValue, NtfsNonResidentAttributeValue, NtfsResidentAttributeValue,
+};
use crate::error::{NtfsError, Result};
-use crate::ntfs::Ntfs;
use crate::ntfs_file::NtfsFile;
use crate::string::NtfsString;
use crate::structured_values::{
- NewNtfsStructuredValue, NtfsFileName, NtfsIndexAllocation, NtfsIndexRoot, NtfsObjectId,
- NtfsStandardInformation, NtfsStructuredValue, NtfsVolumeInformation, NtfsVolumeName,
+ NtfsStructuredValueFromData, NtfsStructuredValueFromNonResidentAttributeValue,
};
use crate::types::Vcn;
-use binread::io::{Read, Seek, SeekFrom};
-use binread::{BinRead, BinReaderExt};
+use binread::io::{Read, Seek};
use bitflags::bitflags;
+use byteorder::{ByteOrder, LittleEndian};
use core::iter::FusedIterator;
use core::mem;
use core::ops::Range;
use enumn::N;
+use memoffset::offset_of;
/// On-disk structure of the generic header of an NTFS attribute.
-#[allow(unused)]
-#[derive(BinRead, Debug)]
+#[repr(C, packed)]
struct NtfsAttributeHeader {
/// Type of the attribute, known types are in [`NtfsAttributeType`].
ty: u32,
@@ -39,12 +39,6 @@ struct NtfsAttributeHeader {
instance: u16,
}
-impl NtfsAttributeHeader {
- fn is_resident(&self) -> bool {
- self.is_non_resident == 0
- }
-}
-
bitflags! {
pub struct NtfsAttributeFlags: u16 {
/// The attribute value is compressed.
@@ -57,9 +51,9 @@ bitflags! {
}
/// On-disk structure of the extra header of an NTFS attribute that has a resident value.
-#[allow(unused)]
-#[derive(BinRead, Debug)]
-struct NtfsAttributeResidentHeader {
+#[repr(C, packed)]
+struct NtfsResidentAttributeHeader {
+ attribute_header: NtfsAttributeHeader,
/// Length of the value, in bytes.
value_length: u32,
/// Offset to the beginning of the value, in bytes from the beginning of the [`NtfsAttributeHeader`].
@@ -69,9 +63,9 @@ struct NtfsAttributeResidentHeader {
}
/// On-disk structure of the extra header of an NTFS attribute that has a non-resident value.
-#[allow(unused)]
-#[derive(BinRead, Debug)]
-struct NtfsAttributeNonResidentHeader {
+#[repr(C, packed)]
+struct NtfsNonResidentAttributeHeader {
+ attribute_header: NtfsAttributeHeader,
/// Lower boundary of Virtual Cluster Numbers (VCNs) referenced by this attribute.
/// This becomes relevant when file data is split over multiple attributes.
/// Otherwise, it's zero.
@@ -122,59 +116,14 @@ pub enum NtfsAttributeType {
}
#[derive(Debug)]
-enum NtfsAttributeExtraHeader {
- Resident(NtfsAttributeResidentHeader),
- NonResident(NtfsAttributeNonResidentHeader),
-}
-
-impl NtfsAttributeExtraHeader {
- fn new<T>(fs: &mut T, header: &NtfsAttributeHeader) -> Result<Self>
- where
- T: Read + Seek,
- {
- if header.is_resident() {
- // Read the resident header.
- let resident_header = fs.read_le::<NtfsAttributeResidentHeader>()?;
- Ok(Self::Resident(resident_header))
- } else {
- // Read the non-resident header.
- let non_resident_header = fs.read_le::<NtfsAttributeNonResidentHeader>()?;
- Ok(Self::NonResident(non_resident_header))
- }
- }
-}
-
-#[derive(Debug)]
-pub struct NtfsAttribute<'n> {
- ntfs: &'n Ntfs,
- position: u64,
- header: NtfsAttributeHeader,
- extra_header: NtfsAttributeExtraHeader,
+pub struct NtfsAttribute<'n, 'f> {
+ file: &'f NtfsFile<'n>,
+ offset: usize,
}
-impl<'n> NtfsAttribute<'n> {
- fn new<T>(ntfs: &'n Ntfs, fs: &mut T, position: u64) -> Result<Self>
- where
- T: Read + Seek,
- {
- // Read the common header for resident and non-resident attributes.
- fs.seek(SeekFrom::Start(position))?;
- let header = fs.read_le::<NtfsAttributeHeader>()?;
-
- // This must be a real attribute and not an end marker!
- // The caller must have already checked for potential end markers.
- debug_assert!(header.ty != NtfsAttributeType::End as u32);
-
- // Read the extra header specific to the attribute type.
- let extra_header = NtfsAttributeExtraHeader::new(fs, &header)?;
-
- let attribute = Self {
- ntfs,
- position,
- header,
- extra_header,
- };
- Ok(attribute)
+impl<'n, 'f> NtfsAttribute<'n, 'f> {
+ fn new(file: &'f NtfsFile<'n>, offset: usize) -> Self {
+ Self { file, offset }
}
/// Returns the length of this NTFS attribute, in bytes.
@@ -183,18 +132,47 @@ impl<'n> NtfsAttribute<'n> {
/// Apart from various headers, this structure also includes the name and,
/// for resident attributes, the actual value.
pub fn attribute_length(&self) -> u32 {
- self.header.length
+ let start = self.offset + offset_of!(NtfsAttributeHeader, length);
+ LittleEndian::read_u32(&self.file.record_data()[start..])
}
/// Returns flags set for this attribute as specified by [`NtfsAttributeFlags`].
pub fn flags(&self) -> NtfsAttributeFlags {
- NtfsAttributeFlags::from_bits_truncate(self.header.flags)
+ let start = self.offset + offset_of!(NtfsAttributeHeader, flags);
+ NtfsAttributeFlags::from_bits_truncate(LittleEndian::read_u16(
+ &self.file.record_data()[start..],
+ ))
}
/// Returns `true` if this is a resident attribute, i.e. one where its value
/// is part of the attribute structure.
pub fn is_resident(&self) -> bool {
- self.header.is_resident()
+ let start = self.offset + offset_of!(NtfsAttributeHeader, is_non_resident);
+ let is_non_resident = self.file.record_data()[start];
+ is_non_resident == 0
+ }
+
+ /// Gets the name of this NTFS attribute (if any) and returns it wrapped in an [`NtfsString`].
+ ///
+ /// Note that most NTFS attributes have no name and are distinguished by their types.
+ /// Use [`NtfsAttribute::ty`] to get the attribute type.
+ pub fn name(&self) -> Option<Result<NtfsString<'f>>> {
+ if self.name_offset() == 0 || self.name_length() == 0 {
+ return None;
+ }
+
+ iter_try!(self.validate_name_sizes());
+
+ let start = self.offset + self.name_offset() as usize;
+ let end = start + self.name_length();
+ let string = NtfsString(&self.file.record_data()[start..end]);
+
+ Some(Ok(string))
+ }
+
+ fn name_offset(&self) -> u16 {
+ let start = self.offset + offset_of!(NtfsAttributeHeader, name_offset);
+ LittleEndian::read_u16(&self.file.record_data()[start..])
}
/// Returns the length of the name of this NTFS attribute, in bytes.
@@ -203,213 +181,225 @@ impl<'n> NtfsAttribute<'n> {
/// It is always part of the attribute itself and hence also of the length
/// returned by [`NtfsAttribute::attribute_length`].
pub fn name_length(&self) -> usize {
- self.header.name_length as usize * mem::size_of::<u16>()
- }
-
- /// Returns the absolute position of this NTFS attribute within the filesystem, in bytes.
- pub fn position(&self) -> u64 {
- self.position
+ let start = self.offset + offset_of!(NtfsAttributeHeader, name_length);
+ let name_length_in_characters = self.file.record_data()[start];
+ name_length_in_characters as usize * mem::size_of::<u16>()
}
- /// Reads the name of this NTFS attribute into the given buffer, and returns an
- /// [`NtfsString`] wrapping that buffer.
- ///
- /// Note that most NTFS attributes have no name and are distinguished by their types.
- /// Use [`NtfsAttribute::ty`] to get the attribute type.
- pub fn read_name<'a, T>(&self, fs: &mut T, buf: &'a mut [u8]) -> Result<NtfsString<'a>>
+ pub fn non_resident_structured_value<T, S>(&self, fs: &mut T) -> Result<S>
where
T: Read + Seek,
+ S: NtfsStructuredValueFromNonResidentAttributeValue<'n, 'f>,
{
- let name_position = self.position + self.header.name_offset as u64;
- fs.seek(SeekFrom::Start(name_position))?;
- NtfsString::from_reader(fs, self.name_length(), buf)
+ let ty = self.ty()?;
+ if ty != S::TY {
+ return Err(NtfsError::StructuredValueOfDifferentType {
+ position: self.position(),
+ ty,
+ });
+ }
+
+ if self.is_resident() {
+ return Err(NtfsError::UnexpectedResidentAttribute {
+ position: self.position(),
+ });
+ }
+
+ S::from_non_resident_attribute_value(fs, self.non_resident_value()?)
+ }
+
+ fn non_resident_value(&self) -> Result<NtfsNonResidentAttributeValue<'n, 'f>> {
+ debug_assert!(!self.is_resident());
+ let start = self.offset + self.non_resident_value_data_runs_offset() as usize;
+ let end = start + self.attribute_length() as usize;
+ let data = &self.file.record_data()[start..end];
+ let position = self.file.position() + start as u64;
+
+ NtfsNonResidentAttributeValue::new(
+ self.file.ntfs(),
+ data,
+ position,
+ self.non_resident_value_data_size(),
+ )
+ }
+
+ fn non_resident_value_data_size(&self) -> u64 {
+ debug_assert!(!self.is_resident());
+ let start = self.offset + offset_of!(NtfsNonResidentAttributeHeader, data_size);
+ LittleEndian::read_u64(&self.file.record_data()[start..])
}
- pub fn structured_value<T>(&self, fs: &mut T) -> Result<NtfsStructuredValue<'n>>
+ fn non_resident_value_data_runs_offset(&self) -> u16 {
+ debug_assert!(!self.is_resident());
+ let start = self.offset + offset_of!(NtfsNonResidentAttributeHeader, data_runs_offset);
+ LittleEndian::read_u16(&self.file.record_data()[start..])
+ }
+
+ /// Returns the absolute position of this NTFS attribute within the filesystem, in bytes.
+ pub fn position(&self) -> u64 {
+ self.file.position() + self.offset as u64
+ }
+
+ pub fn resident_structured_value<S>(&self) -> Result<S>
where
- T: Read + Seek,
+ S: NtfsStructuredValueFromData<'f>,
{
- let value = self.value(fs)?;
- let length = value.len();
-
- match self.ty()? {
- NtfsAttributeType::StandardInformation => {
- let inner = NtfsStandardInformation::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::StandardInformation(inner))
- }
- NtfsAttributeType::AttributeList => panic!("TODO"),
- NtfsAttributeType::FileName => {
- let inner = NtfsFileName::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::FileName(inner))
- }
- NtfsAttributeType::ObjectId => {
- let inner = NtfsObjectId::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::ObjectId(inner))
- }
- NtfsAttributeType::SecurityDescriptor => panic!("TODO"),
- NtfsAttributeType::VolumeName => {
- let inner = NtfsVolumeName::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::VolumeName(inner))
- }
- NtfsAttributeType::VolumeInformation => {
- let inner = NtfsVolumeInformation::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::VolumeInformation(inner))
- }
- NtfsAttributeType::IndexRoot => {
- let inner = NtfsIndexRoot::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::IndexRoot(inner))
- }
- NtfsAttributeType::IndexAllocation => {
- let inner = NtfsIndexAllocation::new(self.ntfs, fs, value, length)?;
- Ok(NtfsStructuredValue::IndexAllocation(inner))
- }
- ty => Err(NtfsError::UnsupportedStructuredValue {
- position: self.position,
+ let ty = self.ty()?;
+ if ty != S::TY {
+ return Err(NtfsError::StructuredValueOfDifferentType {
+ position: self.position(),
ty,
- }),
+ });
}
+
+ if !self.is_resident() {
+ return Err(NtfsError::UnexpectedNonResidentAttribute {
+ position: self.position(),
+ });
+ }
+
+ let resident_value = self.resident_value()?;
+ S::from_data(resident_value.data(), self.position())
+ }
+
+ pub(crate) fn resident_value(&self) -> Result<NtfsResidentAttributeValue<'f>> {
+ debug_assert!(self.is_resident());
+ self.validate_resident_value_sizes()?;
+
+ let start = self.offset + self.resident_value_offset() as usize;
+ let end = start + self.resident_value_length() as usize;
+ let data = &self.file.record_data()[start..end];
+
+ Ok(NtfsResidentAttributeValue::new(data, self.position()))
+ }
+
+ fn resident_value_length(&self) -> u32 {
+ debug_assert!(self.is_resident());
+ let start = self.offset + offset_of!(NtfsResidentAttributeHeader, value_length);
+ LittleEndian::read_u32(&self.file.record_data()[start..])
+ }
+
+ fn resident_value_offset(&self) -> u16 {
+ debug_assert!(self.is_resident());
+ let start = self.offset + offset_of!(NtfsResidentAttributeHeader, value_offset);
+ LittleEndian::read_u16(&self.file.record_data()[start..])
}
/// Returns the type of this NTFS attribute, or [`NtfsError::UnsupportedNtfsAttributeType`]
/// if it's an unknown type.
pub fn ty(&self) -> Result<NtfsAttributeType> {
- NtfsAttributeType::n(self.header.ty).ok_or(NtfsError::UnsupportedNtfsAttributeType {
- position: self.position,
- actual: self.header.ty,
+ let start = self.offset + offset_of!(NtfsAttributeHeader, ty);
+ let ty = LittleEndian::read_u32(&self.file.record_data()[start..]);
+
+ NtfsAttributeType::n(ty).ok_or(NtfsError::UnsupportedNtfsAttributeType {
+ position: self.position(),
+ actual: ty,
})
}
+ fn validate_name_sizes(&self) -> Result<()> {
+ let start = self.name_offset();
+ if start as u32 >= self.attribute_length() {
+ return Err(NtfsError::InvalidNtfsAttributeNameOffset {
+ position: self.position(),
+ expected: start,
+ actual: self.attribute_length(),
+ });
+ }
+
+ let end = start as usize + self.name_length();
+ if end > self.attribute_length() as usize {
+ return Err(NtfsError::InvalidNtfsAttributeNameLength {
+ position: self.position(),
+ expected: end,
+ actual: self.attribute_length(),
+ });
+ }
+
+ Ok(())
+ }
+
+ fn validate_resident_value_sizes(&self) -> Result<()> {
+ debug_assert!(self.is_resident());
+
+ let start = self.resident_value_offset();
+ if start as u32 >= self.attribute_length() {
+ return Err(NtfsError::InvalidNtfsResidentAttributeValueOffset {
+ position: self.position(),
+ expected: start,
+ actual: self.attribute_length(),
+ });
+ }
+
+ let end = start as u32 + self.resident_value_length();
+ if end > self.attribute_length() {
+ return Err(NtfsError::InvalidNtfsResidentAttributeValueLength {
+ position: self.position(),
+ expected: end,
+ actual: self.attribute_length(),
+ });
+ }
+
+ Ok(())
+ }
+
/// Returns an [`NtfsAttributeValue`] structure to read the value of this NTFS attribute.
- pub fn value<T>(&self, fs: &mut T) -> Result<NtfsAttributeValue<'n>>
- where
- T: Read + Seek,
- {
- match &self.extra_header {
- NtfsAttributeExtraHeader::Resident(resident_header) => {
- let value_position = self.position + resident_header.value_offset as u64;
- let value_length = resident_header.value_length as u64;
- let value = NtfsDataRun::from_byte_info(value_position, value_length);
- Ok(NtfsAttributeValue::Resident(value))
- }
- NtfsAttributeExtraHeader::NonResident(non_resident_header) => {
- let start = self.position + non_resident_header.data_runs_offset as u64;
- let end = self.position + self.header.length as u64;
- let value = NtfsAttributeNonResidentValue::new(
- &self.ntfs,
- fs,
- start..end,
- non_resident_header.data_size,
- )?;
- Ok(NtfsAttributeValue::NonResident(value))
- }
+ pub fn value(&self) -> Result<NtfsAttributeValue<'n, 'f>> {
+ if self.is_resident() {
+ let resident_value = self.resident_value()?;
+ Ok(NtfsAttributeValue::Resident(resident_value))
+ } else {
+ let non_resident_value = self.non_resident_value()?;
+ Ok(NtfsAttributeValue::NonResident(non_resident_value))
}
}
/// Returns the length of the value of this NTFS attribute, in bytes.
pub fn value_length(&self) -> u64 {
- match &self.extra_header {
- NtfsAttributeExtraHeader::Resident(resident_header) => {
- resident_header.value_length as u64
- }
- NtfsAttributeExtraHeader::NonResident(non_resident_header) => {
- non_resident_header.data_size
- }
+ if self.is_resident() {
+ self.resident_value_length() as u64
+ } else {
+ self.non_resident_value_data_size()
}
}
}
-pub struct NtfsAttributes<'n> {
- ntfs: &'n Ntfs,
- items_range: Range<u64>,
+pub struct NtfsAttributes<'n, 'a> {
+ file: &'a NtfsFile<'n>,
+ items_range: Range<usize>,
}
-impl<'n> NtfsAttributes<'n> {
- pub(crate) fn new(ntfs: &'n Ntfs, file: &NtfsFile) -> Self {
- let start = file.position() + file.first_attribute_offset() as u64;
- let end = file.position() + file.used_size() as u64;
+impl<'n, 'a> NtfsAttributes<'n, 'a> {
+ pub(crate) fn new(file: &'a NtfsFile<'n>) -> Self {
+ let start = file.first_attribute_offset() as usize;
+ let end = file.used_size() as usize;
let items_range = start..end;
- Self { ntfs, items_range }
- }
-
- pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributesAttached<'n, 'a, T>
- where
- T: Read + Seek,
- {
- NtfsAttributesAttached::new(fs, self)
+ Self { file, items_range }
}
+}
- pub(crate) fn find_first_by_ty<T>(
- &mut self,
- fs: &mut T,
- ty: NtfsAttributeType,
- ) -> Option<Result<NtfsAttribute<'n>>>
- where
- T: Read + Seek,
- {
- while let Some(attribute) = self.next(fs) {
- let attribute = iter_try!(attribute);
- let attribute_ty = iter_try!(attribute.ty());
- if attribute_ty == ty {
- return Some(Ok(attribute));
- }
- }
-
- None
- }
+impl<'n, 'a> Iterator for NtfsAttributes<'n, 'a> {
+ type Item = NtfsAttribute<'n, 'a>;
- pub fn next<T>(&mut self, fs: &mut T) -> Option<Result<NtfsAttribute<'n>>>
- where
- T: Read + Seek,
- {
+ fn next(&mut self) -> Option<Self::Item> {
if self.items_range.is_empty() {
return None;
}
// This may be an entire attribute or just the 4-byte end marker.
// Check if this marks the end of the attribute list.
- let position = self.items_range.start;
- iter_try!(fs.seek(SeekFrom::Start(position)));
- let ty = iter_try!(fs.read_le::<u32>());
+ let ty = LittleEndian::read_u32(&self.file.record_data()[self.items_range.start..]);
if ty == NtfsAttributeType::End as u32 {
return None;
}
// It's a real attribute.
- let attribute = iter_try!(NtfsAttribute::new(self.ntfs, fs, position));
- self.items_range.start += attribute.attribute_length() as u64;
-
- Some(Ok(attribute))
- }
-}
-
-pub struct NtfsAttributesAttached<'n, 'a, T: Read + Seek> {
- fs: &'a mut T,
- attributes: NtfsAttributes<'n>,
-}
+ let attribute = NtfsAttribute::new(self.file, self.items_range.start);
+ self.items_range.start += attribute.attribute_length() as usize;
-impl<'n, 'a, T> NtfsAttributesAttached<'n, 'a, T>
-where
- T: Read + Seek,
-{
- fn new(fs: &'a mut T, attributes: NtfsAttributes<'n>) -> Self {
- Self { fs, attributes }
- }
-
- pub fn detach(self) -> NtfsAttributes<'n> {
- self.attributes
- }
-}
-
-impl<'n, 'a, T> Iterator for NtfsAttributesAttached<'n, 'a, T>
-where
- T: Read + Seek,
-{
- type Item = Result<NtfsAttribute<'n>>;
-
- fn next(&mut self) -> Option<Self::Item> {
- self.attributes.next(self.fs)
+ Some(attribute)
}
}
-impl<'n, 'a, T> FusedIterator for NtfsAttributesAttached<'n, 'a, T> where T: Read + Seek {}
+impl<'n, 'a> FusedIterator for NtfsAttributes<'n, 'a> {}
diff --git a/src/attribute_value.rs b/src/attribute_value.rs
index 2ae32dd..6f4f75f 100644
--- a/src/attribute_value.rs
+++ b/src/attribute_value.rs
@@ -4,23 +4,23 @@
use crate::error::{NtfsError, Result};
use crate::ntfs::Ntfs;
use crate::traits::NtfsReadSeek;
-use crate::types::Lcn;
+use crate::types::{Lcn, Vcn};
use binread::io;
+use binread::io::Cursor;
use binread::io::{Read, Seek, SeekFrom};
-use binread::BinReaderExt;
+use binread::BinRead;
use core::convert::TryFrom;
use core::iter::FusedIterator;
-use core::ops::Range;
use core::{cmp, mem};
#[derive(Clone, Debug)]
-pub enum NtfsAttributeValue<'n> {
- Resident(NtfsDataRun),
- NonResident(NtfsAttributeNonResidentValue<'n>),
+pub enum NtfsAttributeValue<'n, 'f> {
+ Resident(NtfsResidentAttributeValue<'f>),
+ NonResident(NtfsNonResidentAttributeValue<'n, 'f>),
}
-impl<'n> NtfsAttributeValue<'n> {
- pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributeValueAttached<'n, 'a, T>
+impl<'n, 'f> NtfsAttributeValue<'n, 'f> {
+ pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributeValueAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
@@ -29,7 +29,7 @@ impl<'n> NtfsAttributeValue<'n> {
pub fn data_position(&self) -> Option<u64> {
match self {
- Self::Resident(inner) => Some(inner.data_position()),
+ Self::Resident(inner) => inner.data_position(),
Self::NonResident(inner) => inner.data_position(),
}
}
@@ -42,7 +42,7 @@ impl<'n> NtfsAttributeValue<'n> {
}
}
-impl<'n> NtfsReadSeek for NtfsAttributeValue<'n> {
+impl<'n, 'f> NtfsReadSeek for NtfsAttributeValue<'n, 'f> {
fn read<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
where
T: Read + Seek,
@@ -71,16 +71,16 @@ impl<'n> NtfsReadSeek for NtfsAttributeValue<'n> {
}
}
-pub struct NtfsAttributeValueAttached<'n, 'a, T: Read + Seek> {
+pub struct NtfsAttributeValueAttached<'n, 'f, 'a, T: Read + Seek> {
fs: &'a mut T,
- value: NtfsAttributeValue<'n>,
+ value: NtfsAttributeValue<'n, 'f>,
}
-impl<'n, 'a, T> NtfsAttributeValueAttached<'n, 'a, T>
+impl<'n, 'f, 'a, T> NtfsAttributeValueAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
- fn new(fs: &'a mut T, value: NtfsAttributeValue<'n>) -> Self {
+ fn new(fs: &'a mut T, value: NtfsAttributeValue<'n, 'f>) -> Self {
Self { fs, value }
}
@@ -88,7 +88,7 @@ where
self.value.data_position()
}
- pub fn detach(self) -> NtfsAttributeValue<'n> {
+ pub fn detach(self) -> NtfsAttributeValue<'n, 'f> {
self.value
}
@@ -97,7 +97,7 @@ where
}
}
-impl<'n, 'a, T> Read for NtfsAttributeValueAttached<'n, 'a, T>
+impl<'n, 'f, 'a, T> Read for NtfsAttributeValueAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
@@ -106,7 +106,7 @@ where
}
}
-impl<'n, 'a, T> Seek for NtfsAttributeValueAttached<'n, 'a, T>
+impl<'n, 'f, 'a, T> Seek for NtfsAttributeValueAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
@@ -118,6 +118,7 @@ where
#[derive(Clone, Debug)]
pub struct NtfsDataRun {
/// Absolute position of the attribute's value within the filesystem, in bytes.
+ /// This may be zero if this is a "sparse" data run.
position: u64,
/// Total length of the attribute's value, in bytes.
length: u64,
@@ -126,25 +127,29 @@ pub struct NtfsDataRun {
}
impl NtfsDataRun {
- pub(crate) fn from_byte_info(position: u64, length: u64) -> Self {
- Self {
- position,
- length,
- stream_position: 0,
- }
- }
-
- pub(crate) fn from_lcn_info(ntfs: &Ntfs, lcn: Lcn, cluster_count: u64) -> Result<Self> {
+ pub(crate) fn new(ntfs: &Ntfs, lcn: Lcn, cluster_count: u64) -> Result<Self> {
let position = lcn.position(ntfs)?;
let length = cluster_count
.checked_mul(ntfs.cluster_size() as u64)
.ok_or(NtfsError::InvalidClusterCount { cluster_count })?;
- Ok(Self::from_byte_info(position, length))
+ Ok(Self {
+ position,
+ length,
+ stream_position: 0,
+ })
}
- pub fn data_position(&self) -> u64 {
- self.position + self.stream_position
+ /// Returns the absolute current data seek position within the filesystem, in bytes.
+ /// This may be `None` if:
+ /// * The current seek position is outside the valid range, or
+ /// * The data run is a "sparse" data run
+ pub fn data_position(&self) -> Option<u64> {
+ if self.position > 0 && self.stream_position < self.len() {
+ Some(self.position + self.stream_position)
+ } else {
+ None
+ }
}
pub fn len(&self) -> u64 {
@@ -152,7 +157,7 @@ impl NtfsDataRun {
}
fn remaining_len(&self) -> u64 {
- self.length.saturating_sub(self.stream_position)
+ self.len().saturating_sub(self.stream_position)
}
}
@@ -173,7 +178,8 @@ impl NtfsReadSeek for NtfsDataRun {
work_slice.fill(0);
} else {
// This data run contains "real" data.
- fs.seek(SeekFrom::Start(self.position + self.stream_position))?;
+ // We have already performed all necessary sanity checks above, so we can just unwrap here.
+ fs.seek(SeekFrom::Start(self.data_position().unwrap()))?;
fs.read(work_slice)?;
}
@@ -185,33 +191,8 @@ impl NtfsReadSeek for NtfsDataRun {
where
T: Read + Seek,
{
- // This implementation is taken from https://github.com/rust-lang/rust/blob/18c524fbae3ab1bf6ed9196168d8c68fc6aec61a/library/std/src/io/cursor.rs
- // It handles all signed/unsigned arithmetics properly and outputs the known `io` error message.
- let (base_pos, offset) = match pos {
- SeekFrom::Start(n) => {
- self.stream_position = n;
- return Ok(n);
- }
- SeekFrom::End(n) => (self.length, n),
- SeekFrom::Current(n) => (self.stream_position, n),
- };
-
- let new_pos = if offset >= 0 {
- base_pos.checked_add(offset as u64)
- } else {
- base_pos.checked_sub(offset.wrapping_neg() as u64)
- };
-
- match new_pos {
- Some(n) => {
- self.stream_position = n;
- Ok(self.stream_position)
- }
- None => Err(NtfsError::Io(io::Error::new(
- io::ErrorKind::InvalidInput,
- "invalid seek to a negative or overflowing position",
- ))),
- }
+ let length = self.len();
+ seek_contiguous(&mut self.stream_position, length, pos)
}
fn stream_position(&self) -> u64 {
@@ -220,102 +201,50 @@ impl NtfsReadSeek for NtfsDataRun {
}
#[derive(Clone, Debug)]
-pub struct NtfsDataRuns<'n> {
+pub struct NtfsDataRuns<'n, 'f> {
ntfs: &'n Ntfs,
- data_runs_range: Range<u64>,
+ data: &'f [u8],
+ position: u64,
previous_lcn: Lcn,
}
-impl<'n> NtfsDataRuns<'n> {
- fn new(ntfs: &'n Ntfs, data_runs_range: Range<u64>) -> Self {
+impl<'n, 'f> NtfsDataRuns<'n, 'f> {
+ fn new(ntfs: &'n Ntfs, data: &'f [u8], position: u64) -> Self {
Self {
ntfs,
- data_runs_range,
+ data,
+ position,
previous_lcn: Lcn::from(0),
}
}
- pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsDataRunsAttached<'n, 'a, T>
- where
- T: Read + Seek,
- {
- NtfsDataRunsAttached::new(fs, self)
- }
-
- pub fn next<T>(&mut self, fs: &mut T) -> Option<Result<NtfsDataRun>>
- where
- T: Read + Seek,
- {
- if self.data_runs_range.is_empty() {
- return None;
- }
-
- // Read the single header byte.
- iter_try!(fs.seek(SeekFrom::Start(self.data_runs_range.start)));
- let header = iter_try!(fs.read_le::<u8>());
- let mut size = 1u64;
-
- // A zero byte marks the end of the data runs.
- if header == 0 {
- // Ensure `self.data_runs_range.is_empty` returns true, so any further call uses the fast path above.
- self.data_runs_range.start = self.data_runs_range.end;
- return None;
- }
-
- // The lower nibble indicates the length of the following cluster count variable length integer.
- let cluster_count_byte_count = header & 0x0f;
- let cluster_count =
- iter_try!(self.read_variable_length_unsigned_integer(fs, cluster_count_byte_count));
- size += cluster_count_byte_count as u64;
-
- // The upper nibble indicates the length of the following VCN variable length integer.
- let vcn_byte_count = (header & 0xf0) >> 4;
- let vcn = iter_try!(self.read_variable_length_signed_integer(fs, vcn_byte_count)).into();
- size += vcn_byte_count as u64;
-
- // Turn the read VCN into an absolute LCN.
- let lcn = iter_try!(self.previous_lcn.checked_add(vcn).ok_or({
- NtfsError::InvalidVcnInDataRunHeader {
- position: self.data_runs_range.start,
- vcn,
- previous_lcn: self.previous_lcn,
- }
- }));
- self.previous_lcn = lcn;
-
- // Only increment `self.data_runs_range.start` after having checked for success.
- // In case of an error, a subsequent call shall output the same error again.
- self.data_runs_range.start += size;
-
- let data_run = iter_try!(NtfsDataRun::from_lcn_info(self.ntfs, lcn, cluster_count));
- Some(Ok(data_run))
- }
-
- fn read_variable_length_bytes<T>(&self, fs: &mut T, byte_count: u8) -> Result<[u8; 8]>
- where
- T: Read + Seek,
- {
+ fn read_variable_length_bytes(
+ &self,
+ cursor: &mut Cursor<&[u8]>,
+ byte_count: u8,
+ ) -> Result<[u8; 8]> {
const MAX_BYTE_COUNT: u8 = mem::size_of::<u64>() as u8;
if byte_count > MAX_BYTE_COUNT {
return Err(NtfsError::InvalidByteCountInDataRunHeader {
- position: self.data_runs_range.start,
- expected: MAX_BYTE_COUNT,
- actual: byte_count,
+ position: self.position,
+ expected: byte_count,
+ actual: MAX_BYTE_COUNT,
});
}
let mut buf = [0u8; MAX_BYTE_COUNT as usize];
- fs.read_exact(&mut buf[..byte_count as usize])?;
+ cursor.read_exact(&mut buf[..byte_count as usize])?;
Ok(buf)
}
- fn read_variable_length_signed_integer<T>(&self, fs: &mut T, byte_count: u8) -> Result<i64>
- where
- T: Read + Seek,
- {
- let buf = self.read_variable_length_bytes(fs, byte_count)?;
+ fn read_variable_length_signed_integer(
+ &self,
+ cursor: &mut Cursor<&[u8]>,
+ byte_count: u8,
+ ) -> Result<i64> {
+ let buf = self.read_variable_length_bytes(cursor, byte_count)?;
let mut integer = i64::from_le_bytes(buf);
// We have read `byte_count` bytes into a zeroed buffer and just interpreted that as an `i64`.
@@ -326,78 +255,100 @@ impl<'n> NtfsDataRuns<'n> {
Ok(integer)
}
- fn read_variable_length_unsigned_integer<T>(&self, fs: &mut T, byte_count: u8) -> Result<u64>
- where
- T: Read + Seek,
- {
- let buf = self.read_variable_length_bytes(fs, byte_count)?;
+ fn read_variable_length_unsigned_integer(
+ &self,
+ cursor: &mut Cursor<&[u8]>,
+ byte_count: u8,
+ ) -> Result<u64> {
+ let buf = self.read_variable_length_bytes(cursor, byte_count)?;
let integer = u64::from_le_bytes(buf);
Ok(integer)
}
}
-#[derive(Debug)]
-pub struct NtfsDataRunsAttached<'n, 'a, T: Read + Seek> {
- fs: &'a mut T,
- data_runs: NtfsDataRuns<'n>,
-}
+impl<'n, 'f> Iterator for NtfsDataRuns<'n, 'f> {
+ type Item = Result<NtfsDataRun>;
-impl<'n, 'a, T> NtfsDataRunsAttached<'n, 'a, T>
-where
- T: Read + Seek,
-{
- fn new(fs: &'a mut T, data_runs: NtfsDataRuns<'n>) -> Self {
- Self { fs, data_runs }
- }
+ fn next(&mut self) -> Option<Result<NtfsDataRun>> {
+ if self.data.is_empty() {
+ return None;
+ }
- pub fn detach(self) -> NtfsDataRuns<'n> {
- self.data_runs
- }
-}
+ // Read the single header byte.
+ let mut cursor = Cursor::new(self.data);
+ let header = iter_try!(u8::read(&mut cursor));
-impl<'n, 'a, T> Iterator for NtfsDataRunsAttached<'n, 'a, T>
-where
- T: Read + Seek,
-{
- type Item = Result<NtfsDataRun>;
+ // A zero byte marks the end of the data runs.
+ if header == 0 {
+ // Ensure `self.data.is_empty` returns true, so any further call uses the fast path above.
+ self.data = &[];
+ return None;
+ }
+
+ // The lower nibble indicates the length of the following cluster count variable length integer.
+ let cluster_count_byte_count = header & 0x0f;
+ let cluster_count = iter_try!(
+ self.read_variable_length_unsigned_integer(&mut cursor, cluster_count_byte_count)
+ );
+
+ // The upper nibble indicates the length of the following VCN variable length integer.
+ let vcn_byte_count = (header & 0xf0) >> 4;
+ let vcn = Vcn::from(iter_try!(
+ self.read_variable_length_signed_integer(&mut cursor, vcn_byte_count)
+ ));
- fn next(&mut self) -> Option<Self::Item> {
- self.data_runs.next(self.fs)
+ // Turn the read VCN into an absolute LCN.
+ let lcn = iter_try!(self.previous_lcn.checked_add(vcn).ok_or({
+ NtfsError::InvalidVcnInDataRunHeader {
+ position: self.position,
+ vcn,
+ previous_lcn: self.previous_lcn,
+ }
+ }));
+ self.previous_lcn = lcn;
+
+ // Only advance after having checked for success.
+ // In case of an error, a subsequent call shall output the same error again.
+ let bytes_to_advance = cursor.stream_position().unwrap();
+ self.data = &self.data[bytes_to_advance as usize..];
+ self.position += bytes_to_advance;
+
+ let data_run = iter_try!(NtfsDataRun::new(self.ntfs, lcn, cluster_count));
+ Some(Ok(data_run))
}
}
-impl<'n, 'a, T> FusedIterator for NtfsDataRunsAttached<'n, 'a, T> where T: Read + Seek {}
+impl<'n, 'f> FusedIterator for NtfsDataRuns<'n, 'f> {}
#[derive(Clone, Debug)]
-pub struct NtfsAttributeNonResidentValue<'n> {
+pub struct NtfsNonResidentAttributeValue<'n, 'f> {
/// Reference to the base `Ntfs` object of this filesystem.
ntfs: &'n Ntfs,
- /// Byte range where the data run information of this non-resident value is stored on the filesystem.
- data_runs_range: Range<u64>,
+ /// Attribute bytes where the data run information of this non-resident value is stored on the filesystem.
+ data: &'f [u8],
+ /// Absolute position of the data run information within the filesystem, in bytes.
+ position: u64,
/// Total size of the data spread among all data runs, in bytes.
data_size: u64,
/// Iterator of data runs used for reading/seeking.
- stream_data_runs: NtfsDataRuns<'n>,
+ stream_data_runs: NtfsDataRuns<'n, 'f>,
/// Current data run we are reading from.
stream_data_run: Option<NtfsDataRun>,
/// Total stream position, in bytes.
stream_position: u64,
}
-impl<'n> NtfsAttributeNonResidentValue<'n> {
- pub(crate) fn new<T>(
+impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> {
+ pub(crate) fn new(
ntfs: &'n Ntfs,
- fs: &mut T,
- data_runs_range: Range<u64>,
+ data: &'f [u8],
+ position: u64,
data_size: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- let mut stream_data_runs = NtfsDataRuns::new(ntfs, data_runs_range.clone());
+ ) -> Result<Self> {
+ let mut stream_data_runs = NtfsDataRuns::new(ntfs, data, position);
// Get the first data run already here to let `data_position` return something meaningful.
- let stream_data_run = match stream_data_runs.next(fs) {
+ let stream_data_run = match stream_data_runs.next() {
Some(Ok(data_run)) => Some(data_run),
Some(Err(e)) => return Err(e),
None => None,
@@ -405,7 +356,8 @@ impl<'n> NtfsAttributeNonResidentValue<'n> {
Ok(Self {
ntfs,
- data_runs_range,
+ data,
+ position,
data_size,
stream_data_runs,
stream_data_run,
@@ -413,20 +365,32 @@ impl<'n> NtfsAttributeNonResidentValue<'n> {
})
}
+ /// Returns the absolute current data seek position within the filesystem, in bytes.
+ /// This may be `None` if:
+ /// * The current seek position is outside the valid range, or
+ /// * The current data run is a "sparse" data run
pub fn data_position(&self) -> Option<u64> {
- self.stream_data_run
- .as_ref()
- .map(|data_run| data_run.data_position())
+ let stream_data_run = self.stream_data_run.as_ref()?;
+ stream_data_run.data_position()
}
- pub fn data_runs(&self) -> NtfsDataRuns<'n> {
- NtfsDataRuns::new(self.ntfs, self.data_runs_range.clone())
+ pub fn data_runs(&self) -> NtfsDataRuns<'n, 'f> {
+ NtfsDataRuns::new(self.ntfs, self.data, self.position)
}
pub fn len(&self) -> u64 {
self.data_size
}
+ pub fn ntfs(&self) -> &'n Ntfs {
+ self.ntfs
+ }
+
+ /// Returns the absolute position of the data run information within the filesystem, in bytes.
+ pub fn position(&self) -> u64 {
+ self.position
+ }
+
fn do_seek<T>(&mut self, fs: &mut T, mut bytes_to_seek: SeekFrom) -> Result<u64>
where
T: Read + Seek,
@@ -445,7 +409,7 @@ impl<'n> NtfsAttributeNonResidentValue<'n> {
let mut bytes_left_to_seek = match bytes_to_seek {
SeekFrom::Start(n) => {
// Reset `stream_data_runs` and `stream_data_run` to read from the very beginning.
- self.stream_data_runs = NtfsDataRuns::new(self.ntfs, self.data_runs_range.clone());
+ self.stream_data_runs = NtfsDataRuns::new(self.ntfs, self.data, self.position);
self.stream_data_run = None;
n
}
@@ -480,7 +444,7 @@ impl<'n> NtfsAttributeNonResidentValue<'n> {
}
}
- match self.stream_data_runs.next(fs) {
+ match self.stream_data_runs.next() {
Some(Ok(data_run)) => self.stream_data_run = Some(data_run),
Some(Err(e)) => return Err(e),
None => break,
@@ -497,7 +461,7 @@ impl<'n> NtfsAttributeNonResidentValue<'n> {
}
}
-impl<'n> NtfsReadSeek for NtfsAttributeNonResidentValue<'n> {
+impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> {
fn read<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
where
T: Read + Seek,
@@ -516,7 +480,7 @@ impl<'n> NtfsReadSeek for NtfsAttributeNonResidentValue<'n> {
// We still have bytes to read, but no data run or the previous data run has been read to its end.
// Get the next data run and try again.
- match self.stream_data_runs.next(fs) {
+ match self.stream_data_runs.next() {
Some(Ok(data_run)) => self.stream_data_run = Some(data_run),
Some(Err(e)) => return Err(e),
None => break,
@@ -578,3 +542,105 @@ impl<'n> NtfsReadSeek for NtfsAttributeNonResidentValue<'n> {
self.stream_position
}
}
+
+#[derive(Clone, Debug)]
+pub struct NtfsResidentAttributeValue<'f> {
+ data: &'f [u8],
+ position: u64,
+ stream_position: u64,
+}
+
+impl<'f> NtfsResidentAttributeValue<'f> {
+ pub(crate) fn new(data: &'f [u8], position: u64) -> Self {
+ Self {
+ data,
+ position,
+ stream_position: 0,
+ }
+ }
+
+ pub fn data(&self) -> &'f [u8] {
+ self.data
+ }
+
+ /// Returns the absolute current data seek position within the filesystem, in bytes.
+ /// This may be `None` if the current seek position is outside the valid range.
+ pub fn data_position(&self) -> Option<u64> {
+ if self.stream_position < self.len() {
+ Some(self.position + self.stream_position)
+ } else {
+ None
+ }
+ }
+
+ pub fn len(&self) -> u64 {
+ self.data.len() as u64
+ }
+
+ fn remaining_len(&self) -> u64 {
+ self.len().saturating_sub(self.stream_position)
+ }
+}
+
+impl<'f> NtfsReadSeek for NtfsResidentAttributeValue<'f> {
+ fn read<T>(&mut self, _fs: &mut T, buf: &mut [u8]) -> Result<usize>
+ where
+ T: Read + Seek,
+ {
+ if self.remaining_len() == 0 {
+ return Ok(0);
+ }
+
+ let bytes_to_read = cmp::min(buf.len(), self.remaining_len() as usize);
+ let work_slice = &mut buf[..bytes_to_read];
+
+ let start = self.stream_position as usize;
+ let end = start + bytes_to_read;
+ work_slice.copy_from_slice(&self.data[start..end]);
+
+ self.stream_position += bytes_to_read as u64;
+ Ok(bytes_to_read)
+ }
+
+ fn seek<T>(&mut self, _fs: &mut T, pos: SeekFrom) -> Result<u64>
+ where
+ T: Read + Seek,
+ {
+ let length = self.len();
+ seek_contiguous(&mut self.stream_position, length, pos)
+ }
+
+ fn stream_position(&self) -> u64 {
+ self.stream_position
+ }
+}
+
+fn seek_contiguous(stream_position: &mut u64, length: u64, pos: SeekFrom) -> Result<u64> {
+ // This implementation is taken from https://github.com/rust-lang/rust/blob/18c524fbae3ab1bf6ed9196168d8c68fc6aec61a/library/std/src/io/cursor.rs
+ // It handles all signed/unsigned arithmetics properly and outputs the known `io` error message.
+ let (base_pos, offset) = match pos {
+ SeekFrom::Start(n) => {
+ *stream_position = n;
+ return Ok(n);
+ }
+ SeekFrom::End(n) => (length, n),
+ SeekFrom::Current(n) => (*stream_position, n),
+ };
+
+ let new_pos = if offset >= 0 {
+ base_pos.checked_add(offset as u64)
+ } else {
+ base_pos.checked_sub(offset.wrapping_neg() as u64)
+ };
+
+ match new_pos {
+ Some(n) => {
+ *stream_position = n;
+ Ok(*stream_position)
+ }
+ None => Err(NtfsError::Io(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "invalid seek to a negative or overflowing position",
+ ))),
+ }
+}
diff --git a/src/error.rs b/src/error.rs
index 0d205e7..fa62d8e 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -18,7 +18,7 @@ pub enum NtfsError {
},
/// The given buffer should have at least {expected} bytes, but it only has {actual} bytes
BufferTooSmall { expected: usize, actual: usize },
- /// The header of an NTFS data run should indicate a maximum byte count of {expected}, but the header at byte position {position:#010x} indicates a byte count of {actual}
+ /// The NTFS data run header at byte position {position:#010x} indicates a maximum byte count of {expected}, but {actual} is the limit
InvalidByteCountInDataRunHeader {
position: u64,
expected: u8,
@@ -26,6 +26,42 @@ pub enum NtfsError {
},
/// The cluster count {cluster_count} is too big
InvalidClusterCount { cluster_count: u64 },
+ /// The NTFS attribute at byte position {position:#010x} indicates that its name starts at offset {expected}, but the attribute only has a size of {actual} bytes
+ InvalidNtfsAttributeNameOffset {
+ position: u64,
+ expected: u16,
+ actual: u32,
+ },
+ /// The NTFS attribute at byte position {position:#010x} indicates a name length up to offset {expected}, but the attribute only has a size of {actual} bytes
+ InvalidNtfsAttributeNameLength {
+ position: u64,
+ expected: usize,
+ actual: u32,
+ },
+ /// The NTFS file record at byte position {position:#010x} indicates an allocated size of {expected} bytes, but the record only has a size of {actual} bytes
+ InvalidNtfsFileAllocatedSize {
+ position: u64,
+ expected: u32,
+ actual: u32,
+ },
+ /// The NTFS file record at byte position {position:#010x} indicates a used size of {expected} bytes, but only {actual} bytes are allocated
+ InvalidNtfsFileUsedSize {
+ position: u64,
+ expected: u32,
+ actual: u32,
+ },
+ /// The resident NTFS attribute at byte position {position:#010x} indicates that its value starts at offset {expected}, but the attribute only has a size of {actual} bytes
+ InvalidNtfsResidentAttributeValueOffset {
+ position: u64,
+ expected: u16,
+ actual: u32,
+ },
+ /// The resident NTFS attribute at byte position {position:#010x} indicates a value length up to offset {expected}, but the attribute only has a size of {actual} bytes
+ InvalidNtfsResidentAttributeValueLength {
+ position: u64,
+ expected: u32,
+ actual: u32,
+ },
/// The requested NTFS file {n} is invalid
InvalidNtfsFile { n: u64 },
/// The NTFS file record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?}
@@ -46,6 +82,18 @@ pub enum NtfsError {
expected: u32,
actual: u32,
},
+ /// The NTFS index root at byte position {position:#010x} indicates that its entries start at offset {expected}, but the index root only has a size of {actual} bytes
+ InvalidNtfsIndexRootEntriesOffset {
+ position: u64,
+ expected: usize,
+ actual: usize,
+ },
+ /// The NTFS index root at byte position {position:#010x} indicates a used size up to offset {expected}, but the index root only has a size of {actual} bytes
+ InvalidNtfsIndexRootUsedSize {
+ position: u64,
+ expected: usize,
+ actual: usize,
+ },
/// 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
@@ -54,8 +102,8 @@ pub enum NtfsError {
InvalidStructuredValueSize {
position: u64,
ty: NtfsAttributeType,
- expected: u64,
- actual: u64,
+ expected: usize,
+ actual: usize,
},
/// The 2-byte signature field at byte position {position:#010x} should contain {expected:?}, but it contains {actual:?}
InvalidTwoByteSignature {
@@ -75,19 +123,42 @@ pub enum NtfsError {
LcnTooBig { lcn: Lcn },
/// The index root at byte position {position:#010x} is a large index, but no matching index allocation attribute was provided
MissingIndexAllocation { position: u64 },
+ /// The NTFS attribute at byte position {position:#010x} has type {ty:?}, but a different type has been requested
+ StructuredValueOfDifferentType {
+ position: u64,
+ ty: NtfsAttributeType,
+ },
+ /// The NTFS attribute at byte position {position:#010x} should be resident, but it is non-resident
+ UnexpectedNonResidentAttribute { position: u64 },
+ /// The NTFS attribute at byte position {position:#010x} should be non-resident, but it is resident
+ UnexpectedResidentAttribute { position: u64 },
/// The cluster size is {actual} bytes, but the maximum supported one is {expected}
UnsupportedClusterSize { expected: u32, actual: u32 },
/// The type of the NTFS attribute at byte position {position:#010x} is {actual:#010x}, which is not supported
UnsupportedNtfsAttributeType { position: u64, actual: u32 },
/// The namespace of the NTFS file name starting at byte position {position:#010x} is {actual}, which is not supported
UnsupportedNtfsFileNamespace { position: u64, actual: u8 },
- /// The NTFS attribute at byte position {position:#010x} has type {ty:?}, which cannot be read as a structured value
- UnsupportedStructuredValue {
+ /// The Update Sequence Array (USA) of the record at byte position {position:#010x} has entries for {array_count} sectors of {sector_size} bytes, but the record is only {record_size} bytes long
+ UpdateSequenceArrayExceedsRecordSize {
position: u64,
- ty: NtfsAttributeType,
+ array_count: u16,
+ sector_size: u16,
+ record_size: usize,
+ },
+ /// Sector corruption: The 2 bytes at byte position {position:#010x} should match the Update Sequence Number (USN) {expected:?}, but they are {actual:?}
+ UpdateSequenceNumberMismatch {
+ position: u64,
+ expected: [u8; 2],
+ actual: [u8; 2],
+ },
+ /// The index allocation at byte position {position:#010x} references a Virtual Cluster Number (VCN) {expected}, but a record with VCN {actual} is found at that offset
+ VcnMismatchInIndexAllocation {
+ position: u64,
+ expected: Vcn,
+ actual: Vcn,
},
- /// The requested Virtual Cluster Number (VCN) {requested_vcn} leads to a record with VCN {record_vcn}
- VcnMismatch { requested_vcn: Vcn, record_vcn: Vcn },
+ /// The index allocation at byte position {position:#010x} references a Virtual Cluster Number (VCN) {vcn}, but this VCN exceeds the boundaries of the filesystem.
+ VcnOutOfBoundsInIndexAllocation { position: u64, vcn: Vcn },
/// The Virtual Cluster Number (VCN) {vcn} is too big to be processed
VcnTooBig { vcn: Vcn },
}
diff --git a/src/guid.rs b/src/guid.rs
index 4deee30..66ba657 100644
--- a/src/guid.rs
+++ b/src/guid.rs
@@ -5,7 +5,7 @@ use binread::BinRead;
use core::fmt;
/// Size of a single GUID on disk (= size of all GUID fields).
-pub(crate) const GUID_SIZE: u64 = 16;
+pub(crate) const GUID_SIZE: usize = 16;
#[derive(BinRead, Clone, Debug, Eq, PartialEq)]
pub struct NtfsGuid {
diff --git a/src/index.rs b/src/index.rs
index 03c5830..0af1a8b 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -2,28 +2,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::error::{NtfsError, Result};
-use crate::index_entry::{NtfsIndexEntry, NtfsIndexNodeEntries};
-use crate::structured_values::{NewNtfsStructuredValue, NtfsIndexAllocation, NtfsIndexRoot};
+use crate::index_entry::{IndexEntryRange, IndexNodeEntryRanges, NtfsIndexEntry};
+use crate::structured_values::{NtfsIndexAllocation, NtfsIndexRoot};
use alloc::vec::Vec;
use binread::io::{Read, Seek};
-use core::marker::PhantomData;
-pub struct NtfsIndex<'n, 'a, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- index_root: &'a NtfsIndexRoot<'n>,
- index_allocation: Option<&'a NtfsIndexAllocation<'n>>,
- key_type: PhantomData<K>,
+pub struct NtfsIndex<'n, 'f> {
+ index_root: NtfsIndexRoot<'f>,
+ index_allocation: Option<NtfsIndexAllocation<'n, 'f>>,
}
-impl<'n, 'a, K> NtfsIndex<'n, 'a, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
+impl<'n, 'f> NtfsIndex<'n, 'f> {
pub fn new(
- index_root: &'a NtfsIndexRoot<'n>,
- index_allocation: Option<&'a NtfsIndexAllocation<'n>>,
+ index_root: NtfsIndexRoot<'f>,
+ index_allocation: Option<NtfsIndexAllocation<'n, 'f>>,
) -> Result<Self> {
if index_root.is_large_index() && index_allocation.is_none() {
return Err(NtfsError::MissingIndexAllocation {
@@ -31,73 +23,42 @@ where
});
}
- let key_type = PhantomData;
-
Ok(Self {
index_root,
index_allocation,
- key_type,
})
}
- pub fn iter<T>(&self, fs: &mut T) -> Result<NtfsIndexEntries<'n, 'a, K>>
- where
- K: NewNtfsStructuredValue<'n>,
- T: Read + Seek,
- {
- NtfsIndexEntries::new(fs, self.index_root, self.index_allocation)
+ pub fn iter<'i>(&'i self) -> Result<NtfsIndexEntries<'n, 'f, 'i>> {
+ NtfsIndexEntries::new(self)
}
}
-enum StackEntry<'n, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- EntryToExplore(NtfsIndexEntry<'n, K>),
- EntryToReturn(NtfsIndexEntry<'n, K>),
- Iter(NtfsIndexNodeEntries<'n, K>),
-}
-
/// Iterator over
/// all index entries of an index,
/// sorted ascending by the index key,
/// returning an [`NtfsIndexEntry`] for each entry.
///
/// See [`NtfsIndexEntriesAttached`] for an iterator that implements [`Iterator`] and [`FusedIterator`].
-pub struct NtfsIndexEntries<'n, 'a, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- index_root: &'a NtfsIndexRoot<'n>,
- index_allocation: Option<&'a NtfsIndexAllocation<'n>>,
- stack: Vec<StackEntry<'n, K>>,
+pub struct NtfsIndexEntries<'n, 'f, 'i> {
+ index: &'i NtfsIndex<'n, 'f>,
+ inner_iterators: Vec<IndexNodeEntryRanges>,
+ following_entries: Vec<IndexEntryRange>,
}
-impl<'n, 'a, K> NtfsIndexEntries<'n, 'a, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- fn new<T>(
- fs: &mut T,
- index_root: &'a NtfsIndexRoot<'n>,
- index_allocation: Option<&'a NtfsIndexAllocation<'n>>,
- ) -> Result<Self>
- where
- K: NewNtfsStructuredValue<'n>,
- T: Read + Seek,
- {
- // Start with the entries of the top-most node of the B-tree.
- // This is given by the `NtfsIndexNodeEntries` iterator over the Index Root entries.
- let stack = vec![StackEntry::Iter(index_root.entries(fs)?)];
+impl<'n, 'f, 'i> NtfsIndexEntries<'n, 'f, 'i> {
+ fn new(index: &'i NtfsIndex<'n, 'f>) -> Result<Self> {
+ let inner_iterators = vec![index.index_root.entry_ranges()];
+ let following_entries = Vec::new();
Ok(Self {
- index_root,
- index_allocation,
- stack,
+ index,
+ inner_iterators,
+ following_entries,
})
}
- pub fn next<T>(&mut self, fs: &mut T) -> Option<Result<NtfsIndexEntry<'n, K>>>
+ pub fn next<'a, T>(&'a mut self, fs: &mut T) -> Option<Result<NtfsIndexEntry<'a>>>
where
T: Read + Seek,
{
@@ -119,50 +80,57 @@ where
// INDEX ALLOCATION SUBNODE: | 2 |
// -----
//
- loop {
- match self.stack.pop()? {
- StackEntry::EntryToExplore(entry) => {
- // We got an `NtfsIndexEntry` from a previous iteration, which we haven't explored yet.
- // Check if it has a subnode that needs to be returned first. In that case, push us on the
- // stack to be returned later and push the `NtfsIndexNodeEntries` iterator from the subnode
- // to iterate it first.
- // If this entry has no subnode, just return and forget about it.
- if let Some(subnode_vcn) = entry.subnode_vcn(fs) {
- let subnode_vcn = iter_try!(subnode_vcn);
- let index_allocation = iter_try!(self.index_allocation.ok_or_else(|| {
+ let entry_range = loop {
+ // Get the iterator from the current node level, if any.
+ let iter = self.inner_iterators.last_mut()?;
+
+ // Get the next `IndexEntryRange` from it.
+ if let Some(entry_range) = iter.next() {
+ // Convert that `IndexEntryRange` to a (lifetime-bound) `NtfsIndexEntry`.
+ let entry = entry_range.to_entry(iter.data());
+
+ // Does this entry have a subnode that needs to be iterated first?
+ if let Some(subnode_vcn) = entry.subnode_vcn() {
+ // Read the subnode from the filesystem and get an iterator for it.
+ let index_allocation =
+ iter_try!(self.index.index_allocation.as_ref().ok_or_else(|| {
NtfsError::MissingIndexAllocation {
- position: self.index_root.position(),
+ position: self.index.index_root.position(),
}
}));
- let subnode = iter_try!(index_allocation.record_from_vcn(
- fs,
- &self.index_root,
- subnode_vcn
- ));
- let iter = iter_try!(subnode.entries(fs));
- self.stack.push(StackEntry::EntryToReturn(entry));
- self.stack.push(StackEntry::Iter(iter));
- } else {
- return Some(Ok(entry));
- }
- }
- StackEntry::EntryToReturn(entry) => {
- // We got an `NtfsIndexEntry` that we have already explored, hence all elements before it
- // have already been returned.
- // Now it's our turn.
- return Some(Ok(entry));
- }
- StackEntry::Iter(mut iter) => {
- // We got an `NtfsIndexNodeEntries` iterator over the entries of a node.
- // Get the next entry from it, and push the updated iterator and the entry back on the stack.
- // If this iterator yields no more entries, we are done with this node and can just forget about it.
- if let Some(entry) = iter.next(fs) {
- let entry = iter_try!(entry);
- self.stack.push(StackEntry::Iter(iter));
- self.stack.push(StackEntry::EntryToExplore(entry));
- }
+ let subnode = iter_try!(index_allocation.record_from_vcn(
+ fs,
+ &self.index.index_root,
+ subnode_vcn
+ ));
+ let subnode_iter = subnode.into_entry_ranges();
+
+ // Save this subnode's iterator and the entry range.
+ // We'll pick up the iterator through `self.inner_iterators.last_mut()` in the
+ // next loop iteration, and we will return that entry as soon as the subnode iterator
+ // has been fully iterated.
+ self.inner_iterators.push(subnode_iter);
+ self.following_entries.push(entry_range);
+ } else {
+ // There is no subnode, so our `entry` is next lexicographically.
+ break entry_range;
}
+ } else {
+ // The iterator for this subnode level has been fully iterated.
+ // Drop it.
+ self.inner_iterators.pop();
+
+ // Return the entry, whose subnode we just iterated and which we saved in `following_entries` above.
+ // If we just finished iterating the top-level node, `following_entries` is empty and we are done.
+ // Otherwise, we can be sure that `inner_iterators` contains the matching iterator for converting
+ // `IndexEntryRange` to a (lifetime-bound) `NtfsIndexEntry`.
+ let entry_range = self.following_entries.pop()?;
+ break entry_range;
}
- }
+ };
+
+ let iter = self.inner_iterators.last().unwrap();
+ let entry = entry_range.to_entry(iter.data());
+ Some(Ok(entry))
}
}
diff --git a/src/index_entry.rs b/src/index_entry.rs
index c3825b9..0fb4503 100644
--- a/src/index_entry.rs
+++ b/src/index_entry.rs
@@ -1,23 +1,20 @@
// 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::ntfs::Ntfs;
-use crate::structured_values::NewNtfsStructuredValue;
-use crate::traits::NtfsReadSeek;
+use crate::structured_values::NtfsStructuredValueFromData;
use crate::types::Vcn;
-use binread::io::{Read, Seek, SeekFrom};
-use binread::{BinRead, BinReaderExt};
use bitflags::bitflags;
+use byteorder::{ByteOrder, LittleEndian};
use core::iter::FusedIterator;
-use core::marker::PhantomData;
use core::mem;
+use core::ops::Range;
+use memoffset::offset_of;
/// Size of all [`IndexEntryHeader`] fields plus some reserved bytes.
const INDEX_ENTRY_HEADER_SIZE: i64 = 16;
-#[derive(BinRead, Clone, Debug)]
+#[repr(C, packed)]
struct IndexEntryHeader {
file_ref: u64,
index_entry_length: u16,
@@ -25,17 +22,6 @@ struct IndexEntryHeader {
flags: u8,
}
-#[derive(Clone, Debug)]
-pub struct NtfsIndexEntry<'n, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- ntfs: &'n Ntfs,
- header: IndexEntryHeader,
- value: NtfsAttributeValue<'n>,
- key_type: PhantomData<K>,
-}
-
bitflags! {
pub struct NtfsIndexEntryFlags: u8 {
/// This index entry points to a sub-node.
@@ -45,179 +31,172 @@ bitflags! {
}
}
-impl<'n, K> NtfsIndexEntry<'n, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- pub(crate) fn new<T>(ntfs: &'n Ntfs, 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 {
- ntfs,
- header,
- value,
- key_type,
- };
-
- Ok(entry)
+pub(crate) struct IndexEntryRange {
+ range: Range<usize>,
+ position: u64,
+}
+
+impl IndexEntryRange {
+ pub(crate) const fn new(range: Range<usize>, position: u64) -> Self {
+ Self { range, position }
+ }
+
+ pub(crate) fn to_entry<'d>(&self, data: &'d [u8]) -> NtfsIndexEntry<'d> {
+ NtfsIndexEntry::new(&data[self.range.clone()], self.position)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct NtfsIndexEntry<'d> {
+ data: &'d [u8],
+ position: u64,
+}
+
+impl<'d> NtfsIndexEntry<'d> {
+ pub(crate) const fn new(data: &'d [u8], position: u64) -> Self {
+ Self { data, position }
}
pub fn flags(&self) -> NtfsIndexEntryFlags {
- NtfsIndexEntryFlags::from_bits_truncate(self.header.flags)
+ let flags = self.data[offset_of!(IndexEntryHeader, flags)];
+ NtfsIndexEntryFlags::from_bits_truncate(flags)
}
pub fn index_entry_length(&self) -> u16 {
- self.header.index_entry_length
+ let start = offset_of!(IndexEntryHeader, index_entry_length);
+ LittleEndian::read_u16(&self.data[start..])
}
pub fn key_length(&self) -> u16 {
- self.header.key_length
+ let start = offset_of!(IndexEntryHeader, key_length);
+ LittleEndian::read_u16(&self.data[start..])
}
/// 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>>
+ pub fn key_structured_value<K>(&self) -> Option<Result<K>>
where
- T: Read + Seek,
+ K: NtfsStructuredValueFromData<'d>,
{
- if self.header.key_length == 0 || self.flags().contains(NtfsIndexEntryFlags::LAST_ENTRY) {
+ // The key/stream is only set when the last entry flag is not set.
+ // https://flatcap.org/linux-ntfs/ntfs/concepts/index_entry.html
+ if self.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 start = INDEX_ENTRY_HEADER_SIZE as usize;
+ let end = start + self.key_length() as usize;
+ let position = self.position + start as u64;
- let structured_value = iter_try!(K::new(self.ntfs, fs, value, length));
+ let structured_value = iter_try!(K::from_data(&self.data[start..end], position));
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<Vcn>>
- where
- T: Read + Seek,
- {
+ pub fn subnode_vcn(&self) -> Option<Vcn> {
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::<Vcn>() as i64
- )));
- let vcn = iter_try!(value_attached.read_le::<Vcn>());
+ // Get the subnode VCN from the very end of the Index Entry.
+ let start = self.index_entry_length() as usize - mem::size_of::<Vcn>();
+ let vcn = Vcn::from(LittleEndian::read_i64(&self.data[start..]));
- Some(Ok(vcn))
+ Some(vcn)
}
}
-#[derive(Clone, Debug)]
-pub struct NtfsIndexNodeEntries<'n, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- ntfs: &'n Ntfs,
- value: NtfsAttributeValue<'n>,
- end: u64,
- key_type: PhantomData<K>,
+pub(crate) struct IndexNodeEntryRanges {
+ data: Vec<u8>,
+ range: Range<usize>,
+ position: u64,
}
-impl<'n, K> NtfsIndexNodeEntries<'n, K>
-where
- K: NewNtfsStructuredValue<'n>,
-{
- pub(crate) fn new(ntfs: &'n Ntfs, value: NtfsAttributeValue<'n>, end: u64) -> Self {
- debug_assert!(end <= value.len());
- let key_type = PhantomData;
+impl IndexNodeEntryRanges {
+ pub(crate) fn new(data: Vec<u8>, range: Range<usize>, position: u64) -> Self {
+ debug_assert!(range.end <= data.len());
Self {
- ntfs,
- value,
- end,
- key_type,
+ data,
+ range,
+ position,
}
}
- pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsIndexNodeEntriesAttached<'n, 'a, K, T>
- where
- T: Read + Seek,
- {
- NtfsIndexNodeEntriesAttached::new(fs, self)
+ pub(crate) fn data<'d>(&'d self) -> &'d [u8] {
+ &self.data
}
+}
- 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 {
+impl Iterator for IndexNodeEntryRanges {
+ type Item = IndexEntryRange;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.range.is_empty() {
return None;
}
// Get the current entry.
- let entry = iter_try!(NtfsIndexEntry::new(self.ntfs, fs, self.value.clone()));
+ let start = self.range.start;
+ let position = self.position + self.range.start as u64;
+ let entry = NtfsIndexEntry::new(&self.data[start..], position);
+ let end = start + entry.index_entry_length() as usize;
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)));
+ // Ensure that we don't read any other entries by advancing `self.range.start` to the end.
+ self.range.start = self.data.len();
} 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)));
+ self.range.start = end;
}
- Some(Ok(entry))
+ Some(IndexEntryRange::new(start..end, position))
}
}
-pub struct NtfsIndexNodeEntriesAttached<'n, 'a, K, T>
-where
- K: NewNtfsStructuredValue<'n>,
- T: Read + Seek,
-{
- fs: &'a mut T,
- index_entries: NtfsIndexNodeEntries<'n, K>,
-}
+impl FusedIterator for IndexNodeEntryRanges {}
-impl<'n, 'a, K, T> NtfsIndexNodeEntriesAttached<'n, 'a, K, T>
-where
- K: NewNtfsStructuredValue<'n>,
- T: Read + Seek,
-{
- fn new(fs: &'a mut T, index_entries: NtfsIndexNodeEntries<'n, K>) -> Self {
- Self { fs, index_entries }
- }
+#[derive(Clone, Debug)]
+pub struct NtfsIndexNodeEntries<'d> {
+ data: &'d [u8],
+ position: u64,
+}
- pub fn detach(self) -> NtfsIndexNodeEntries<'n, K> {
- self.index_entries
+impl<'d> NtfsIndexNodeEntries<'d> {
+ pub(crate) const fn new(data: &'d [u8], position: u64) -> Self {
+ Self { data, position }
}
}
-impl<'n, 'a, K, T> Iterator for NtfsIndexNodeEntriesAttached<'n, 'a, K, T>
-where
- K: NewNtfsStructuredValue<'n>,
- T: Read + Seek,
-{
- type Item = Result<NtfsIndexEntry<'n, K>>;
+impl<'d> Iterator for NtfsIndexNodeEntries<'d> {
+ type Item = NtfsIndexEntry<'d>;
fn next(&mut self) -> Option<Self::Item> {
- self.index_entries.next(self.fs)
+ if self.data.is_empty() {
+ return None;
+ }
+
+ // Get the current entry.
+ let entry = NtfsIndexEntry::new(self.data, self.position);
+
+ if entry.flags().contains(NtfsIndexEntryFlags::LAST_ENTRY) {
+ // This is the last entry.
+ // Ensure that we don't read any other entries by emptying the slice.
+ self.data = &[];
+ } else {
+ // This is not the last entry.
+ // Advance our iterator to the next entry.
+ let bytes_to_advance = entry.index_entry_length() as usize;
+ self.data = &self.data[bytes_to_advance..];
+ self.position += bytes_to_advance as u64;
+ }
+
+ Some(entry)
}
}
-impl<'n, 'a, K, T> FusedIterator for NtfsIndexNodeEntriesAttached<'n, 'a, K, T>
-where
- K: NewNtfsStructuredValue<'n>,
- T: Read + Seek,
-{
-}
+impl<'d> FusedIterator for NtfsIndexNodeEntries<'d> {}
diff --git a/src/index_record.rs b/src/index_record.rs
index a313982..5b47aa1 100644
--- a/src/index_record.rs
+++ b/src/index_record.rs
@@ -1,31 +1,31 @@
// Copyright 2021 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use crate::attribute_value::NtfsAttributeValue;
+use crate::attribute_value::NtfsNonResidentAttributeValue;
use crate::error::{NtfsError, Result};
-use crate::index_entry::NtfsIndexNodeEntries;
-use crate::ntfs::Ntfs;
+use crate::index_entry::{IndexNodeEntryRanges, NtfsIndexNodeEntries};
+use crate::record::Record;
use crate::record::RecordHeader;
-use crate::structured_values::NewNtfsStructuredValue;
use crate::traits::NtfsReadSeek;
use crate::types::Vcn;
-use binread::io::{Read, Seek, SeekFrom};
-use binread::{BinRead, BinReaderExt};
+use binread::io::{Read, Seek};
+use byteorder::{ByteOrder, LittleEndian};
+use core::ops::Range;
+use memoffset::offset_of;
/// Size of all [`IndexRecordHeader`] fields.
const INDEX_RECORD_HEADER_SIZE: u32 = 24;
-#[allow(unused)]
-#[derive(BinRead, Clone, Debug)]
+#[repr(C, packed)]
struct IndexRecordHeader {
record_header: RecordHeader,
- vcn: Vcn,
+ vcn: i64,
}
/// Size of all [`IndexNodeHeader`] fields plus some reserved bytes.
-pub(crate) const INDEX_NODE_HEADER_SIZE: u64 = 16;
+pub(crate) const INDEX_NODE_HEADER_SIZE: usize = 16;
-#[derive(BinRead, Clone, Debug)]
+#[repr(C, packed)]
pub(crate) struct IndexNodeHeader {
pub(crate) entries_offset: u32,
pub(crate) index_size: u32,
@@ -33,93 +33,106 @@ pub(crate) struct IndexNodeHeader {
pub(crate) flags: u8,
}
-#[derive(Clone, Debug)]
+#[derive(Debug)]
pub struct NtfsIndexRecord<'n> {
- ntfs: &'n Ntfs,
- value: NtfsAttributeValue<'n>,
- index_record_header: IndexRecordHeader,
- index_node_header: IndexNodeHeader,
+ record: Record<'n>,
}
const HAS_SUBNODES_FLAG: u8 = 0x01;
impl<'n> NtfsIndexRecord<'n> {
pub(crate) fn new<T>(
- ntfs: &'n Ntfs,
fs: &mut T,
- value: NtfsAttributeValue<'n>,
+ mut value: NtfsNonResidentAttributeValue<'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 {
- ntfs,
- value,
- index_record_header,
- index_node_header,
- };
+ // The caller must have checked that value.stream_position() < value.len(),
+ // so that value.data_position() returns a value.
+ let data_position = value.data_position().unwrap();
+
+ let mut data = vec![0; index_record_size as usize];
+ value.read_exact(fs, &mut data)?;
+
+ let mut record = Record::new(value.ntfs(), data, data_position);
+ record.fixup()?;
+
+ let index_record = Self { record };
index_record.validate_signature()?;
- index_record.validate_sizes(index_record_size)?;
+ index_record.validate_sizes()?;
Ok(index_record)
}
- pub fn entries<K, T>(&self, fs: &mut T) -> Result<NtfsIndexNodeEntries<'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;
+ pub fn entries<'r>(&'r self) -> Result<NtfsIndexNodeEntries<'r>> {
+ let (entries_range, position) = self.entries_range_and_position();
+ let data = &self.record.data()[entries_range];
- let mut value = self.value.clone();
- value.seek(fs, SeekFrom::Start(start))?;
+ Ok(NtfsIndexNodeEntries::new(data, position))
+ }
- Ok(NtfsIndexNodeEntries::new(self.ntfs, value, end))
+ fn entries_range_and_position(&self) -> (Range<usize>, u64) {
+ let start = INDEX_RECORD_HEADER_SIZE as usize + self.index_entries_offset() as usize;
+ let end = INDEX_RECORD_HEADER_SIZE as usize + self.index_used_size() as usize;
+ let position = self.record.position() + start as u64;
+
+ (start..end, position)
}
/// 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
+ let start = INDEX_RECORD_HEADER_SIZE as usize + offset_of!(IndexNodeHeader, flags);
+ let flags = self.record.data()[start];
+ (flags & HAS_SUBNODES_FLAG) != 0
}
pub fn index_allocated_size(&self) -> u32 {
- self.index_node_header.allocated_size
+ let start = INDEX_RECORD_HEADER_SIZE as usize + offset_of!(IndexNodeHeader, allocated_size);
+ LittleEndian::read_u32(&self.record.data()[start..])
+ }
+
+ pub(crate) fn index_entries_offset(&self) -> u32 {
+ let start = INDEX_RECORD_HEADER_SIZE as usize + offset_of!(IndexNodeHeader, entries_offset);
+ LittleEndian::read_u32(&self.record.data()[start..])
}
pub fn index_used_size(&self) -> u32 {
- self.index_node_header.index_size
+ let start = INDEX_RECORD_HEADER_SIZE as usize + offset_of!(IndexNodeHeader, index_size);
+ LittleEndian::read_u32(&self.record.data()[start..])
+ }
+
+ pub(crate) fn into_entry_ranges(self) -> IndexNodeEntryRanges {
+ let (entries_range, position) = self.entries_range_and_position();
+ IndexNodeEntryRanges::new(self.record.into_data(), entries_range, position)
}
fn validate_signature(&self) -> Result<()> {
- let signature = &self.index_record_header.record_header.signature;
+ let signature = &self.record.signature();
let expected = b"INDX";
if signature == expected {
Ok(())
} else {
Err(NtfsError::InvalidNtfsIndexSignature {
- position: self.value.data_position().unwrap(),
+ position: self.record.position(),
expected,
actual: *signature,
})
}
}
- fn validate_sizes(&self, index_record_size: u32) -> Result<()> {
+ fn validate_sizes(&self) -> Result<()> {
+ let index_record_size = self.record.len() as u32;
+
// 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(),
+ position: self.record.position(),
expected: index_record_size,
actual: total_allocated_size,
});
@@ -127,15 +140,10 @@ impl<'n> NtfsIndexRecord<'n> {
// 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();
+ let total_used_size = INDEX_RECORD_HEADER_SIZE + self.index_used_size();
if total_used_size > total_allocated_size {
return Err(NtfsError::InvalidNtfsIndexSize {
- position: self.value.data_position().unwrap(),
+ position: self.record.position(),
expected: total_allocated_size,
actual: total_used_size,
});
@@ -145,6 +153,7 @@ impl<'n> NtfsIndexRecord<'n> {
}
pub fn vcn(&self) -> Vcn {
- self.index_record_header.vcn
+ let start = offset_of!(IndexRecordHeader, vcn);
+ Vcn::from(LittleEndian::read_i64(&self.record.data()[start..]))
}
}
diff --git a/src/ntfs.rs b/src/ntfs.rs
index ccd6ae9..bfe9621 100644
--- a/src/ntfs.rs
+++ b/src/ntfs.rs
@@ -3,10 +3,9 @@
use crate::attribute::NtfsAttributeType;
use crate::boot_sector::BootSector;
-//use crate::dir::Dir;
use crate::error::{NtfsError, Result};
use crate::ntfs_file::{KnownNtfsFile, NtfsFile};
-use crate::structured_values::{NtfsStructuredValue, NtfsVolumeInformation, NtfsVolumeName};
+use crate::structured_values::{NtfsVolumeInformation, NtfsVolumeName};
use binread::io::{Read, Seek, SeekFrom};
use binread::BinReaderExt;
@@ -62,6 +61,10 @@ impl Ntfs {
self.cluster_size
}
+ pub fn file_record_size(&self) -> u32 {
+ self.file_record_size
+ }
+
/// Returns the [`NtfsFile`] for the `n`-th NTFS file record.
///
/// The first few NTFS files have fixed indexes and contain filesystem
@@ -114,43 +117,37 @@ impl Ntfs {
let volume_file = self.ntfs_file(fs, KnownNtfsFile::Volume as u64)?;
let attribute = volume_file
.attributes()
- .find_first_by_ty(fs, NtfsAttributeType::VolumeInformation)
+ .find(|attribute| {
+ // TODO: Replace by attribute.ty().contains() once https://github.com/rust-lang/rust/issues/62358 has landed.
+ attribute
+ .ty()
+ .map(|ty| ty == NtfsAttributeType::VolumeInformation)
+ .unwrap_or(false)
+ })
.ok_or(NtfsError::AttributeNotFound {
position: volume_file.position(),
ty: NtfsAttributeType::VolumeName,
- })??;
- let value = attribute.structured_value(fs)?;
- let volume_info = match value {
- NtfsStructuredValue::VolumeInformation(volume_info) => volume_info,
- _ => unreachable!(
- "Got {:?} despite checking the type for NtfsAttributeType::VolumeInformation",
- value
- ),
- };
-
- Ok(volume_info)
+ })?;
+ attribute.resident_structured_value::<NtfsVolumeInformation>()
}
/// Returns an [`NtfsVolumeName`] to read the volume name (also called volume label)
/// of this NTFS volume.
/// Note that a volume may also have no label, which is why the return value is further
/// encapsulated in an `Option`.
- pub fn volume_name<'n, T>(&'n self, fs: &mut T) -> Option<Result<NtfsVolumeName<'n>>>
+ pub fn volume_name<'d, T>(&self, fs: &mut T) -> Option<Result<NtfsVolumeName>>
where
T: Read + Seek,
{
let volume_file = iter_try!(self.ntfs_file(fs, KnownNtfsFile::Volume as u64));
- let attribute = iter_try!(volume_file
- .attributes()
- .find_first_by_ty(fs, NtfsAttributeType::VolumeName)?);
- let value = iter_try!(attribute.structured_value(fs));
- let volume_name = match value {
- NtfsStructuredValue::VolumeName(volume_name) => volume_name,
- _ => unreachable!(
- "Got {:?} despite checking the type for NtfsAttributeType::VolumeName",
- value
- ),
- };
+ let attribute = volume_file.attributes().find(|attribute| {
+ // TODO: Replace by attribute.ty().contains() once https://github.com/rust-lang/rust/issues/62358 has landed.
+ attribute
+ .ty()
+ .map(|ty| ty == NtfsAttributeType::VolumeName)
+ .unwrap_or(false)
+ })?;
+ let volume_name = iter_try!(attribute.resident_structured_value::<NtfsVolumeName>());
Some(Ok(volume_name))
}
@@ -184,9 +181,6 @@ mod tests {
let ntfs = Ntfs::new(&mut testfs1).unwrap();
let volume_name = ntfs.volume_name(&mut testfs1).unwrap().unwrap();
assert_eq!(volume_name.name_length(), 14);
-
- let mut buf = [0u8; 14];
- let volume_name_string = volume_name.read_name(&mut testfs1, &mut buf).unwrap();
- assert_eq!(volume_name_string, "mylabel");
+ assert_eq!(volume_name.name(), "mylabel");
}
}
diff --git a/src/ntfs_file.rs b/src/ntfs_file.rs
index 2d28e27..056b3bb 100644
--- a/src/ntfs_file.rs
+++ b/src/ntfs_file.rs
@@ -4,10 +4,11 @@
use crate::attribute::NtfsAttributes;
use crate::error::{NtfsError, Result};
use crate::ntfs::Ntfs;
-use crate::record::RecordHeader;
+use crate::record::{Record, RecordHeader};
use binread::io::{Read, Seek, SeekFrom};
-use binread::{BinRead, BinReaderExt};
use bitflags::bitflags;
+use byteorder::{ByteOrder, LittleEndian};
+use memoffset::offset_of;
#[repr(u64)]
pub enum KnownNtfsFile {
@@ -25,8 +26,7 @@ pub enum KnownNtfsFile {
Extend = 11,
}
-#[allow(unused)]
-#[derive(BinRead, Debug)]
+#[repr(C, packed)]
struct FileRecordHeader {
record_header: RecordHeader,
sequence_number: u16,
@@ -48,10 +48,9 @@ bitflags! {
}
}
+#[derive(Debug)]
pub struct NtfsFile<'n> {
- ntfs: &'n Ntfs,
- header: FileRecordHeader,
- position: u64,
+ record: Record<'n>,
}
impl<'n> NtfsFile<'n> {
@@ -59,64 +58,99 @@ impl<'n> NtfsFile<'n> {
where
T: Read + Seek,
{
+ let mut data = vec![0; ntfs.file_record_size() as usize];
fs.seek(SeekFrom::Start(position))?;
- let header = fs.read_le::<FileRecordHeader>()?;
+ fs.read_exact(&mut data)?;
- let file = Self {
- ntfs,
- header,
- position,
- };
+ let mut record = Record::new(ntfs, data, position);
+ record.fixup()?;
+
+ let file = Self { record };
file.validate_signature()?;
+ file.validate_sizes()?;
Ok(file)
}
pub fn allocated_size(&self) -> u32 {
- self.header.allocated_size
+ let start = offset_of!(FileRecordHeader, allocated_size);
+ LittleEndian::read_u32(&self.record.data()[start..])
}
- pub fn attributes(&self) -> NtfsAttributes<'n> {
- NtfsAttributes::new(self.ntfs, &self)
+ pub fn attributes<'f>(&'f self) -> NtfsAttributes<'n, 'f> {
+ NtfsAttributes::new(self)
}
pub(crate) fn first_attribute_offset(&self) -> u16 {
- self.header.first_attribute_offset
+ let start = offset_of!(FileRecordHeader, first_attribute_offset);
+ LittleEndian::read_u16(&self.record.data()[start..])
}
/// Returns flags set for this NTFS file as specified by [`NtfsFileFlags`].
pub fn flags(&self) -> NtfsFileFlags {
- NtfsFileFlags::from_bits_truncate(self.header.flags)
+ let start = offset_of!(FileRecordHeader, flags);
+ NtfsFileFlags::from_bits_truncate(LittleEndian::read_u16(&self.record.data()[start..]))
}
pub fn hard_link_count(&self) -> u16 {
- self.header.hard_link_count
+ let start = offset_of!(FileRecordHeader, hard_link_count);
+ LittleEndian::read_u16(&self.record.data()[start..])
+ }
+
+ pub(crate) fn ntfs(&self) -> &'n Ntfs {
+ self.record.ntfs()
}
pub fn position(&self) -> u64 {
- self.position
+ self.record.position()
+ }
+
+ pub(crate) fn record_data(&self) -> &[u8] {
+ self.record.data()
}
pub fn sequence_number(&self) -> u16 {
- self.header.sequence_number
+ let start = offset_of!(FileRecordHeader, sequence_number);
+ LittleEndian::read_u16(&self.record.data()[start..])
}
pub fn used_size(&self) -> u32 {
- self.header.used_size
+ let start = offset_of!(FileRecordHeader, used_size);
+ LittleEndian::read_u32(&self.record.data()[start..])
}
fn validate_signature(&self) -> Result<()> {
- let signature = &self.header.record_header.signature;
+ let signature = &self.record.signature();
let expected = b"FILE";
if signature == expected {
Ok(())
} else {
Err(NtfsError::InvalidNtfsFileSignature {
- position: self.position,
+ position: self.record.position(),
expected,
actual: *signature,
})
}
}
+
+ fn validate_sizes(&self) -> Result<()> {
+ if self.allocated_size() > self.record.len() {
+ return Err(NtfsError::InvalidNtfsFileAllocatedSize {
+ position: self.record.position(),
+ expected: self.allocated_size(),
+ actual: self.record.len(),
+ });
+ }
+
+ if self.used_size() > self.allocated_size() {
+ return Err(NtfsError::InvalidNtfsFileUsedSize {
+ position: self.record.position(),
+ expected: self.used_size(),
+ actual: self.allocated_size(),
+ });
+ }
+
+ Ok(())
+ }
}
diff --git a/src/record.rs b/src/record.rs
index 91db32e..b8bc510 100644
--- a/src/record.rs
+++ b/src/record.rs
@@ -1,21 +1,142 @@
// Copyright 2021 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use binread::BinRead;
+use crate::error::{NtfsError, Result};
+use crate::ntfs::Ntfs;
+use alloc::vec::Vec;
+use byteorder::{ByteOrder, LittleEndian};
+use core::convert::TryInto;
+use core::mem;
+use memoffset::{offset_of, span_of};
-const UPDATE_SEQUENCE_ELEMENT_SIZE: u32 = 2;
-
-#[allow(unused)]
-#[derive(BinRead, Clone, Debug)]
+#[repr(C, packed)]
pub(crate) struct RecordHeader {
- pub(crate) signature: [u8; 4],
- update_sequence_array_offset: u16,
- update_sequence_array_count: u16,
+ signature: [u8; 4],
+ update_sequence_offset: u16,
+ update_sequence_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
+#[derive(Clone, Debug)]
+pub(crate) struct Record<'n> {
+ ntfs: &'n Ntfs,
+ data: Vec<u8>,
+ position: u64,
+}
+
+impl<'n> Record<'n> {
+ pub(crate) fn new(ntfs: &'n Ntfs, data: Vec<u8>, position: u64) -> Self {
+ Self {
+ ntfs,
+ data,
+ position,
+ }
+ }
+
+ pub(crate) fn data(&self) -> &[u8] {
+ &self.data
+ }
+
+ pub(crate) fn fixup(&mut self) -> Result<()> {
+ let update_sequence_number = self.update_sequence_number();
+ let mut array_position = self.update_sequence_array_start() as usize;
+ let array_end =
+ self.update_sequence_offset() as usize + self.update_sequence_size() as usize;
+
+ // The Update Sequence Number (USN) is written to the last 2 bytes of each sector.
+ let mut sector_position = self.ntfs.sector_size() as usize - mem::size_of::<u16>();
+
+ while array_position < array_end {
+ let array_position_end = array_position + mem::size_of::<u16>();
+ let sector_position_end = sector_position + mem::size_of::<u16>();
+
+ if sector_position_end > self.data.len() {
+ return Err(NtfsError::UpdateSequenceArrayExceedsRecordSize {
+ position: self.position,
+ array_count: self.update_sequence_array_count(),
+ sector_size: self.ntfs.sector_size(),
+ record_size: self.data.len(),
+ });
+ }
+
+ // The array contains the actual 2 bytes that need to be at `sector_position` after the fixup.
+ let new_bytes: [u8; 2] = self.data[array_position..array_position_end]
+ .try_into()
+ .unwrap();
+
+ // The current 2 bytes at `sector_position` before the fixup should equal the Update Sequence Number (USN).
+ // Otherwise, this sector is corrupted.
+ let bytes_to_update = &mut self.data[sector_position..sector_position_end];
+ if bytes_to_update != update_sequence_number {
+ return Err(NtfsError::UpdateSequenceNumberMismatch {
+ position: self.position + array_position as u64,
+ expected: update_sequence_number,
+ actual: (&*bytes_to_update).try_into().unwrap(),
+ });
+ }
+
+ // Perform the actual fixup.
+ bytes_to_update.copy_from_slice(&new_bytes);
+
+ // Advance to the next array entry and sector.
+ array_position += mem::size_of::<u16>();
+ sector_position += self.ntfs.sector_size() as usize;
+ }
+
+ Ok(())
+ }
+
+ pub(crate) fn into_data(self) -> Vec<u8> {
+ self.data
+ }
+
+ pub(crate) fn len(&self) -> u32 {
+ // A record is never larger than a u32.
+ // Usually, it shouldn't even exceed a u16, but our code could handle that.
+ self.data.len() as u32
+ }
+
+ pub(crate) fn ntfs(&self) -> &'n Ntfs {
+ self.ntfs
+ }
+
+ pub(crate) fn position(&self) -> u64 {
+ self.position
+ }
+
+ pub(crate) fn signature(&self) -> [u8; 4] {
+ self.data[span_of!(RecordHeader, signature)]
+ .try_into()
+ .unwrap()
+ }
+
+ fn update_sequence_array_count(&self) -> u16 {
+ let start = offset_of!(RecordHeader, update_sequence_count);
+ let update_sequence_count = LittleEndian::read_u16(&self.data[start..]);
+
+ // Subtract the Update Sequence Number (USN), so that only the number of array elements remains.
+ update_sequence_count - mem::size_of::<u16>() as u16
+ }
+
+ fn update_sequence_array_start(&self) -> u16 {
+ // The Update Sequence Number (USN) comes first and the array begins right after that.
+ self.update_sequence_offset() + mem::size_of::<u16>() as u16
+ }
+
+ fn update_sequence_number(&self) -> [u8; 2] {
+ let start = self.update_sequence_offset() as usize;
+ let end = start + mem::size_of::<u16>();
+ self.data[start..end].try_into().unwrap()
+ }
+
+ fn update_sequence_offset(&self) -> u16 {
+ let start = offset_of!(RecordHeader, update_sequence_offset);
+ LittleEndian::read_u16(&self.data[start..])
+ }
+
+ pub(crate) fn update_sequence_size(&self) -> u32 {
+ let start = offset_of!(RecordHeader, update_sequence_count);
+ let update_sequence_count = LittleEndian::read_u16(&self.data[start..]);
+ update_sequence_count as u32 * mem::size_of::<u16>() as u32
}
}
diff --git a/src/string.rs b/src/string.rs
index fa2f76e..18acc4d 100644
--- a/src/string.rs
+++ b/src/string.rs
@@ -1,9 +1,7 @@
// Copyright 2021 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use crate::error::{NtfsError, Result};
use alloc::string::String;
-use binread::io::Read;
use core::char;
use core::cmp::Ordering;
use core::convert::TryInto;
@@ -14,21 +12,6 @@ use core::fmt;
pub struct NtfsString<'a>(pub &'a [u8]);
impl<'a> NtfsString<'a> {
- pub(crate) fn from_reader<T>(mut rdr: T, length: usize, buf: &'a mut [u8]) -> Result<Self>
- where
- T: Read,
- {
- if buf.len() < length {
- return Err(NtfsError::BufferTooSmall {
- expected: length,
- actual: buf.len(),
- });
- }
-
- rdr.read_exact(&mut buf[..length])?;
- Ok(Self(&buf[..length]))
- }
-
fn cmp_iter<TI, OI>(mut this_iter: TI, mut other_iter: OI) -> Ordering
where
TI: Iterator<Item = u16>,
diff --git a/src/structured_values/file_name.rs b/src/structured_values/file_name.rs
index dd88a15..e58efeb 100644
--- a/src/structured_values/file_name.rs
+++ b/src/structured_values/file_name.rs
@@ -2,22 +2,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::attribute::NtfsAttributeType;
-use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
-use crate::ntfs::Ntfs;
use crate::string::NtfsString;
-use crate::structured_values::{NewNtfsStructuredValue, NtfsFileAttributeFlags};
+use crate::structured_values::{
+ NtfsFileAttributeFlags, NtfsStructuredValue, NtfsStructuredValueFromData,
+};
use crate::time::NtfsTime;
-use binread::io::{Read, Seek, SeekFrom};
+use alloc::vec::Vec;
+use binread::io::Cursor;
use binread::{BinRead, BinReaderExt};
use core::mem;
use enumn::N;
/// Size of all [`FileNameHeader`] fields.
-const FILE_NAME_HEADER_SIZE: i64 = 66;
+const FILE_NAME_HEADER_SIZE: usize = 66;
/// The smallest FileName attribute has a name containing just a single character.
-const FILE_NAME_MIN_SIZE: u64 = FILE_NAME_HEADER_SIZE as u64 + mem::size_of::<u16>() as u64;
+const FILE_NAME_MIN_SIZE: usize = FILE_NAME_HEADER_SIZE + mem::size_of::<u16>();
#[allow(unused)]
#[derive(BinRead, Clone, Debug)]
@@ -45,12 +46,12 @@ pub enum NtfsFileNamespace {
}
#[derive(Clone, Debug)]
-pub struct NtfsFileName<'n> {
+pub struct NtfsFileName {
header: FileNameHeader,
- value: NtfsAttributeValue<'n>,
+ name: Vec<u8>,
}
-impl<'n> NtfsFileName<'n> {
+impl NtfsFileName {
pub fn access_time(&self) -> NtfsTime {
self.header.access_time
}
@@ -79,6 +80,11 @@ impl<'n> NtfsFileName<'n> {
self.header.modification_time
}
+ /// Gets the file name and returns it wrapped in an [`NtfsString`].
+ pub fn name<'s>(&'s self) -> NtfsString<'s> {
+ NtfsString(&self.name)
+ }
+
/// Returns the file name length, in bytes.
///
/// A file name has a maximum length of 255 UTF-16 code points (510 bytes).
@@ -88,63 +94,69 @@ impl<'n> NtfsFileName<'n> {
/// Returns the namespace this name belongs to, or [`NtfsError::UnsupportedNtfsFileNamespace`]
/// if it's an unknown namespace.
- pub fn namespace(&self) -> Result<NtfsFileNamespace> {
- NtfsFileNamespace::n(self.header.namespace).ok_or(NtfsError::UnsupportedNtfsFileNamespace {
- position: self.value.data_position().unwrap(),
- actual: self.header.namespace,
- })
+ pub fn namespace(&self) -> NtfsFileNamespace {
+ NtfsFileNamespace::n(self.header.namespace).unwrap()
}
- /// Reads the file name into the given buffer, and returns an
- /// [`NtfsString`] wrapping that buffer.
- pub fn read_name<'a, T>(&self, fs: &mut T, buf: &'a mut [u8]) -> Result<NtfsString<'a>>
- where
- T: Read + Seek,
- {
- let mut value_attached = self.value.clone().attach(fs);
- value_attached.seek(SeekFrom::Current(FILE_NAME_HEADER_SIZE))?;
- NtfsString::from_reader(value_attached, self.name_length(), buf)
+ fn read_name(&mut self, data: &[u8]) {
+ debug_assert!(self.name.is_empty());
+ let start = FILE_NAME_HEADER_SIZE;
+ let end = start + self.name_length();
+ self.name.extend_from_slice(&data[start..end]);
}
- fn validate_name_length(&self, length: u64) -> Result<()> {
- let total_size = FILE_NAME_HEADER_SIZE as u64 + self.name_length() as u64;
+ fn validate_name_length(&self, data_size: usize, position: u64) -> Result<()> {
+ let total_size = FILE_NAME_HEADER_SIZE + self.name_length();
- if total_size > length {
+ if total_size > data_size {
return Err(NtfsError::InvalidStructuredValueSize {
- position: self.value.data_position().unwrap(),
+ position: position,
ty: NtfsAttributeType::FileName,
- expected: length,
+ expected: data_size,
actual: total_size,
});
}
Ok(())
}
+
+ fn validate_namespace(&self, position: u64) -> Result<()> {
+ if NtfsFileNamespace::n(self.header.namespace).is_none() {
+ return Err(NtfsError::UnsupportedNtfsFileNamespace {
+ position,
+ actual: self.header.namespace,
+ });
+ }
+
+ Ok(())
+ }
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsFileName<'n> {
- fn new<T>(
- _ntfs: &'n Ntfs,
- fs: &mut T,
- value: NtfsAttributeValue<'n>,
- length: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- if length < FILE_NAME_MIN_SIZE {
+impl NtfsStructuredValue for NtfsFileName {
+ const TY: NtfsAttributeType = NtfsAttributeType::FileName;
+}
+
+impl<'d> NtfsStructuredValueFromData<'d> for NtfsFileName {
+ fn from_data(data: &'d [u8], position: u64) -> Result<Self> {
+ if data.len() < FILE_NAME_MIN_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::FileName,
expected: FILE_NAME_MIN_SIZE,
- actual: length,
+ actual: data.len(),
});
}
- let mut value_attached = value.clone().attach(fs);
- let header = value_attached.read_le::<FileNameHeader>()?;
- let file_name = Self { header, value };
- file_name.validate_name_length(length)?;
+ let mut cursor = Cursor::new(data);
+ let header = cursor.read_le::<FileNameHeader>()?;
+
+ let mut file_name = Self {
+ header,
+ name: Vec::new(),
+ };
+ file_name.validate_name_length(data.len(), position)?;
+ file_name.validate_namespace(position)?;
+ file_name.read_name(data);
Ok(file_name)
}
@@ -155,7 +167,6 @@ mod tests {
use super::*;
use crate::ntfs::Ntfs;
use crate::ntfs_file::KnownNtfsFile;
- use crate::structured_values::NtfsStructuredValue;
use crate::time::tests::NT_TIMESTAMP_2021_01_01;
#[test]
@@ -165,10 +176,10 @@ mod tests {
let mft = ntfs
.ntfs_file(&mut testfs1, KnownNtfsFile::MFT as u64)
.unwrap();
- let mut mft_attributes = mft.attributes().attach(&mut testfs1);
+ let mut mft_attributes = mft.attributes();
// Check the FileName attribute of the MFT.
- let attribute = mft_attributes.nth(1).unwrap().unwrap();
+ let attribute = mft_attributes.nth(1).unwrap();
assert_eq!(attribute.ty().unwrap(), NtfsAttributeType::FileName);
assert_eq!(attribute.attribute_length(), 104);
assert!(attribute.is_resident());
@@ -176,11 +187,9 @@ mod tests {
assert_eq!(attribute.value_length(), 74);
// Check the actual "file name" of the MFT.
- let value = attribute.structured_value(&mut testfs1).unwrap();
- let file_name = match value {
- NtfsStructuredValue::FileName(file_name) => file_name,
- v => panic!("Unexpected NtfsStructuredValue: {:?}", v),
- };
+ let file_name = attribute
+ .resident_structured_value::<NtfsFileName>()
+ .unwrap();
let creation_time = file_name.creation_time();
assert!(*creation_time > NT_TIMESTAMP_2021_01_01);
@@ -194,14 +203,11 @@ mod tests {
assert_eq!(file_name.name_length(), 8);
- let mut buf = [0u8; 8];
- let file_name_string = file_name.read_name(&mut testfs1, &mut buf).unwrap();
-
// Test various ways to compare the same string.
- assert_eq!(file_name_string, "$MFT");
- assert_eq!(file_name_string.to_string_lossy(), String::from("$MFT"));
+ assert_eq!(file_name.name(), "$MFT");
+ assert_eq!(file_name.name().to_string_lossy(), String::from("$MFT"));
assert_eq!(
- file_name_string,
+ file_name.name(),
NtfsString(&[b'$', 0, b'M', 0, b'F', 0, b'T', 0])
);
}
diff --git a/src/structured_values/index_allocation.rs b/src/structured_values/index_allocation.rs
index 582faec..a1e52f2 100644
--- a/src/structured_values/index_allocation.rs
+++ b/src/structured_values/index_allocation.rs
@@ -1,33 +1,34 @@
// Copyright 2021 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use crate::attribute_value::NtfsAttributeValue;
+use crate::attribute::NtfsAttributeType;
+use crate::attribute_value::NtfsNonResidentAttributeValue;
use crate::error::{NtfsError, Result};
use crate::index_record::NtfsIndexRecord;
-use crate::ntfs::Ntfs;
use crate::structured_values::index_root::NtfsIndexRoot;
-use crate::structured_values::NewNtfsStructuredValue;
+use crate::structured_values::{
+ NtfsStructuredValue, NtfsStructuredValueFromNonResidentAttributeValue,
+};
use crate::traits::NtfsReadSeek;
use crate::types::Vcn;
use binread::io::{Read, Seek, SeekFrom};
use core::iter::FusedIterator;
#[derive(Clone, Debug)]
-pub struct NtfsIndexAllocation<'n> {
- ntfs: &'n Ntfs,
- value: NtfsAttributeValue<'n>,
+pub struct NtfsIndexAllocation<'n, 'f> {
+ value: NtfsNonResidentAttributeValue<'n, 'f>,
}
-impl<'n> NtfsIndexAllocation<'n> {
- pub fn iter(&self, index_root: &NtfsIndexRoot<'n>) -> NtfsIndexRecords<'n> {
+impl<'n, 'f> NtfsIndexAllocation<'n, 'f> {
+ pub fn iter(&self, index_root: &NtfsIndexRoot) -> NtfsIndexRecords<'n, 'f> {
let index_record_size = index_root.index_record_size();
- NtfsIndexRecords::new(self.ntfs, self.value.clone(), index_record_size)
+ NtfsIndexRecords::new(self.value.clone(), index_record_size)
}
pub fn record_from_vcn<T>(
&self,
fs: &mut T,
- index_root: &NtfsIndexRoot<'n>,
+ index_root: &NtfsIndexRoot,
vcn: Vcn,
) -> Result<NtfsIndexRecord<'n>>
where
@@ -35,18 +36,26 @@ impl<'n> NtfsIndexAllocation<'n> {
{
// Seek to the byte offset of the given VCN.
let mut value = self.value.clone();
- let offset = vcn.offset(self.ntfs)?;
+ let offset = vcn.offset(self.value.ntfs())?;
value.seek(fs, SeekFrom::Current(offset))?;
+ if value.stream_position() >= value.len() {
+ return Err(NtfsError::VcnOutOfBoundsInIndexAllocation {
+ position: self.value.position(),
+ vcn,
+ });
+ }
+
// Get the record.
let index_record_size = index_root.index_record_size();
- let record = NtfsIndexRecord::new(self.ntfs, fs, value, index_record_size)?;
+ let record = NtfsIndexRecord::new(fs, value, index_record_size)?;
// Validate that the VCN in the record is the requested one.
if record.vcn() != vcn {
- return Err(NtfsError::VcnMismatch {
- requested_vcn: vcn,
- record_vcn: record.vcn(),
+ return Err(NtfsError::VcnMismatchInIndexAllocation {
+ position: self.value.position(),
+ expected: vcn,
+ actual: record.vcn(),
});
}
@@ -54,37 +63,39 @@ impl<'n> NtfsIndexAllocation<'n> {
}
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsIndexAllocation<'n> {
- fn new<T>(
- ntfs: &'n Ntfs,
+impl<'n, 'f> NtfsStructuredValue for NtfsIndexAllocation<'n, 'f> {
+ const TY: NtfsAttributeType = NtfsAttributeType::IndexAllocation;
+}
+
+impl<'n, 'f> NtfsStructuredValueFromNonResidentAttributeValue<'n, 'f>
+ for NtfsIndexAllocation<'n, 'f>
+{
+ fn from_non_resident_attribute_value<T>(
_fs: &mut T,
- value: NtfsAttributeValue<'n>,
- _length: u64,
+ value: NtfsNonResidentAttributeValue<'n, 'f>,
) -> Result<Self>
where
T: Read + Seek,
{
- Ok(Self { ntfs, value })
+ Ok(Self { value })
}
}
#[derive(Clone, Debug)]
-pub struct NtfsIndexRecords<'n> {
- ntfs: &'n Ntfs,
- value: NtfsAttributeValue<'n>,
+pub struct NtfsIndexRecords<'n, 'f> {
+ value: NtfsNonResidentAttributeValue<'n, 'f>,
index_record_size: u32,
}
-impl<'n> NtfsIndexRecords<'n> {
- fn new(ntfs: &'n Ntfs, value: NtfsAttributeValue<'n>, index_record_size: u32) -> Self {
+impl<'n, 'f> NtfsIndexRecords<'n, 'f> {
+ fn new(value: NtfsNonResidentAttributeValue<'n, 'f>, index_record_size: u32) -> Self {
Self {
- ntfs,
value,
index_record_size,
}
}
- pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsIndexRecordsAttached<'n, 'a, T>
+ pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsIndexRecordsAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
@@ -101,7 +112,6 @@ impl<'n> NtfsIndexRecords<'n> {
// Get the current record.
let record = iter_try!(NtfsIndexRecord::new(
- self.ntfs,
fs,
self.value.clone(),
self.index_record_size
@@ -116,28 +126,28 @@ impl<'n> NtfsIndexRecords<'n> {
}
}
-pub struct NtfsIndexRecordsAttached<'n, 'a, T>
+pub struct NtfsIndexRecordsAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
fs: &'a mut T,
- index_records: NtfsIndexRecords<'n>,
+ index_records: NtfsIndexRecords<'n, 'f>,
}
-impl<'n, 'a, T> NtfsIndexRecordsAttached<'n, 'a, T>
+impl<'n, 'f, 'a, T> NtfsIndexRecordsAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
- fn new(fs: &'a mut T, index_records: NtfsIndexRecords<'n>) -> Self {
+ fn new(fs: &'a mut T, index_records: NtfsIndexRecords<'n, 'f>) -> Self {
Self { fs, index_records }
}
- pub fn detach(self) -> NtfsIndexRecords<'n> {
+ pub fn detach(self) -> NtfsIndexRecords<'n, 'f> {
self.index_records
}
}
-impl<'n, 'a, T> Iterator for NtfsIndexRecordsAttached<'n, 'a, T>
+impl<'n, 'f, 'a, T> Iterator for NtfsIndexRecordsAttached<'n, 'f, 'a, T>
where
T: Read + Seek,
{
@@ -148,4 +158,4 @@ where
}
}
-impl<'n, 'a, T> FusedIterator for NtfsIndexRecordsAttached<'n, 'a, T> where T: Read + Seek {}
+impl<'n, 'f, 'a, T> FusedIterator for NtfsIndexRecordsAttached<'n, 'f, 'a, T> where T: Read + Seek {}
diff --git a/src/structured_values/index_root.rs b/src/structured_values/index_root.rs
index 0355fc0..731d0da 100644
--- a/src/structured_values/index_root.rs
+++ b/src/structured_values/index_root.rs
@@ -2,20 +2,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::attribute::NtfsAttributeType;
-use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
-use crate::index_entry::NtfsIndexNodeEntries;
+use crate::index_entry::{IndexNodeEntryRanges, NtfsIndexNodeEntries};
use crate::index_record::{IndexNodeHeader, INDEX_NODE_HEADER_SIZE};
-use crate::ntfs::Ntfs;
-use crate::structured_values::NewNtfsStructuredValue;
-use crate::traits::NtfsReadSeek;
-use binread::io::{Read, Seek, SeekFrom};
-use binread::{BinRead, BinReaderExt};
+use crate::structured_values::{NtfsStructuredValue, NtfsStructuredValueFromData};
+use byteorder::{ByteOrder, LittleEndian};
+use core::ops::Range;
+use memoffset::offset_of;
/// Size of all [`IndexRootHeader`] fields plus some reserved bytes.
-const INDEX_ROOT_HEADER_SIZE: u64 = 16;
+const INDEX_ROOT_HEADER_SIZE: usize = 16;
-#[derive(BinRead, Clone, Debug)]
+#[repr(C, packed)]
struct IndexRootHeader {
ty: u32,
collation_rule: u32,
@@ -24,86 +22,110 @@ struct IndexRootHeader {
}
#[derive(Clone, Debug)]
-pub struct NtfsIndexRoot<'n> {
- ntfs: &'n Ntfs,
- value: NtfsAttributeValue<'n>,
- index_root_header: IndexRootHeader,
- index_node_header: IndexNodeHeader,
+pub struct NtfsIndexRoot<'f> {
+ data: &'f [u8],
+ position: u64,
}
const LARGE_INDEX_FLAG: u8 = 0x01;
-impl<'n> NtfsIndexRoot<'n> {
- pub fn index_allocated_size(&self) -> u32 {
- self.index_node_header.allocated_size
+impl<'f> NtfsIndexRoot<'f> {
+ pub fn entries(&self) -> Result<NtfsIndexNodeEntries<'f>> {
+ let (entries_range, position) = self.entries_range_and_position();
+ let data = &self.data[entries_range];
+
+ Ok(NtfsIndexNodeEntries::new(data, position))
}
- pub fn entries<K, T>(&self, fs: &mut T) -> Result<NtfsIndexNodeEntries<'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;
+ fn entries_range_and_position(&self) -> (Range<usize>, u64) {
+ let start = INDEX_ROOT_HEADER_SIZE as usize + self.index_entries_offset() as usize;
+ let end = INDEX_ROOT_HEADER_SIZE as usize + self.index_used_size() as usize;
+ let position = self.position + start as u64;
+
+ (start..end, position)
+ }
- let mut value = self.value.clone();
- value.seek(fs, SeekFrom::Start(start))?;
+ pub(crate) fn entry_ranges(&self) -> IndexNodeEntryRanges {
+ let (entries_range, position) = self.entries_range_and_position();
+ let entries_data = self.data[entries_range].to_vec();
+ let range = 0..entries_data.len();
- Ok(NtfsIndexNodeEntries::new(self.ntfs, value, end))
+ IndexNodeEntryRanges::new(entries_data, range, position)
+ }
+
+ pub fn index_allocated_size(&self) -> u32 {
+ let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, allocated_size);
+ LittleEndian::read_u32(&self.data[start..])
+ }
+
+ fn index_entries_offset(&self) -> u32 {
+ let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, entries_offset);
+ LittleEndian::read_u32(&self.data[start..])
}
pub fn index_record_size(&self) -> u32 {
- self.index_root_header.index_record_size
+ let start = offset_of!(IndexRootHeader, index_record_size);
+ LittleEndian::read_u32(&self.data[start..])
}
pub fn index_used_size(&self) -> u32 {
- self.index_node_header.index_size
+ let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, index_size);
+ LittleEndian::read_u32(&self.data[start..])
}
/// 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
+ let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, flags);
+ (self.data[start] & LARGE_INDEX_FLAG) != 0
}
pub fn position(&self) -> u64 {
- // A structured value is always created from a valid seek position,
- // and therefore we can safely unwrap here.
- self.value.data_position().unwrap()
+ self.position
+ }
+
+ fn validate_sizes(&self) -> Result<()> {
+ let (entries_range, _position) = self.entries_range_and_position();
+
+ if entries_range.start >= self.data.len() {
+ return Err(NtfsError::InvalidNtfsIndexRootEntriesOffset {
+ position: self.position,
+ expected: entries_range.start,
+ actual: self.data.len(),
+ });
+ }
+
+ if entries_range.end > self.data.len() {
+ return Err(NtfsError::InvalidNtfsIndexRootUsedSize {
+ position: self.position,
+ expected: entries_range.end,
+ actual: self.data.len(),
+ });
+ }
+
+ Ok(())
}
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsIndexRoot<'n> {
- fn new<T>(
- ntfs: &'n Ntfs,
- fs: &mut T,
- value: NtfsAttributeValue<'n>,
- _length: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- if value.len() < INDEX_ROOT_HEADER_SIZE + INDEX_NODE_HEADER_SIZE {
+impl<'f> NtfsStructuredValue for NtfsIndexRoot<'f> {
+ const TY: NtfsAttributeType = NtfsAttributeType::IndexRoot;
+}
+
+impl<'f> NtfsStructuredValueFromData<'f> for NtfsIndexRoot<'f> {
+ fn from_data(data: &'f [u8], position: u64) -> Result<Self> {
+ if data.len() < INDEX_ROOT_HEADER_SIZE + INDEX_NODE_HEADER_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::IndexRoot,
expected: INDEX_ROOT_HEADER_SIZE,
- actual: value.len(),
+ actual: data.len(),
});
}
- let mut value_attached = value.clone().attach(fs);
- 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 {
- ntfs,
- value,
- index_root_header,
- index_node_header,
- })
+ let index_root = Self { data, position };
+ index_root.validate_sizes()?;
+
+ Ok(index_root)
}
}
diff --git a/src/structured_values/mod.rs b/src/structured_values/mod.rs
index ce97f63..ce716d2 100644
--- a/src/structured_values/mod.rs
+++ b/src/structured_values/mod.rs
@@ -21,9 +21,9 @@ pub use standard_information::*;
pub use volume_information::*;
pub use volume_name::*;
-use crate::attribute_value::NtfsAttributeValue;
+use crate::attribute::NtfsAttributeType;
+use crate::attribute_value::NtfsNonResidentAttributeValue;
use crate::error::Result;
-use crate::ntfs::Ntfs;
use binread::io::{Read, Seek};
use bitflags::bitflags;
@@ -45,23 +45,21 @@ bitflags! {
}
}
-#[derive(Clone, Debug)]
-pub enum NtfsStructuredValue<'n> {
- StandardInformation(NtfsStandardInformation),
- FileName(NtfsFileName<'n>),
- ObjectId(NtfsObjectId),
- VolumeInformation(NtfsVolumeInformation),
- VolumeName(NtfsVolumeName<'n>),
- IndexRoot(NtfsIndexRoot<'n>),
- IndexAllocation(NtfsIndexAllocation<'n>),
+pub trait NtfsStructuredValue: Sized {
+ const TY: NtfsAttributeType;
}
-pub trait NewNtfsStructuredValue<'n>: Sized {
- fn new<T>(
- ntfs: &'n Ntfs,
+/// Create a structured value from an arbitrary data slice.
+/// This handles Resident Attributes of File Records AND Keys of Index Records (when an attribute is indexed).
+pub trait NtfsStructuredValueFromData<'d>: NtfsStructuredValue {
+ fn from_data(data: &'d [u8], position: u64) -> Result<Self>;
+}
+
+/// Create a structured value from a Non-Resident Attribute Value.
+pub trait NtfsStructuredValueFromNonResidentAttributeValue<'n, 'f>: NtfsStructuredValue {
+ fn from_non_resident_attribute_value<T>(
fs: &mut T,
- value: NtfsAttributeValue<'n>,
- length: u64,
+ value: NtfsNonResidentAttributeValue<'n, 'f>,
) -> Result<Self>
where
T: Read + Seek;
diff --git a/src/structured_values/object_id.rs b/src/structured_values/object_id.rs
index f9c84aa..292308a 100644
--- a/src/structured_values/object_id.rs
+++ b/src/structured_values/object_id.rs
@@ -2,12 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::attribute::NtfsAttributeType;
-use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
use crate::guid::{NtfsGuid, GUID_SIZE};
-use crate::ntfs::Ntfs;
-use crate::structured_values::NewNtfsStructuredValue;
-use binread::io::{Read, Seek};
+use crate::structured_values::{NtfsStructuredValue, NtfsStructuredValueFromData};
+use binread::io::Cursor;
use binread::BinReaderExt;
#[derive(Clone, Debug)]
@@ -36,41 +34,37 @@ impl NtfsObjectId {
}
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsObjectId {
- fn new<T>(
- _ntfs: &'n Ntfs,
- fs: &mut T,
- value: NtfsAttributeValue<'n>,
- length: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- if length < GUID_SIZE {
+impl NtfsStructuredValue for NtfsObjectId {
+ const TY: NtfsAttributeType = NtfsAttributeType::ObjectId;
+}
+
+impl<'d> NtfsStructuredValueFromData<'d> for NtfsObjectId {
+ fn from_data(data: &'d [u8], position: u64) -> Result<Self> {
+ if data.len() < GUID_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::ObjectId,
expected: GUID_SIZE,
- actual: length,
+ actual: data.len(),
});
}
- let mut value_attached = value.attach(fs);
- let object_id = value_attached.read_le::<NtfsGuid>()?;
+ let mut cursor = Cursor::new(data);
+ let object_id = cursor.read_le::<NtfsGuid>()?;
let mut birth_volume_id = None;
- if length >= 2 * GUID_SIZE {
- birth_volume_id = Some(value_attached.read_le::<NtfsGuid>()?);
+ if data.len() >= 2 * GUID_SIZE {
+ birth_volume_id = Some(cursor.read_le::<NtfsGuid>()?);
}
let mut birth_object_id = None;
- if length >= 3 * GUID_SIZE {
- birth_object_id = Some(value_attached.read_le::<NtfsGuid>()?);
+ if data.len() >= 3 * GUID_SIZE {
+ birth_object_id = Some(cursor.read_le::<NtfsGuid>()?);
}
let mut domain_id = None;
- if length >= 4 * GUID_SIZE {
- domain_id = Some(value_attached.read_le::<NtfsGuid>()?);
+ if data.len() >= 4 * GUID_SIZE {
+ domain_id = Some(cursor.read_le::<NtfsGuid>()?);
}
Ok(Self {
diff --git a/src/structured_values/standard_information.rs b/src/structured_values/standard_information.rs
index 07849f6..e6b3ffc 100644
--- a/src/structured_values/standard_information.rs
+++ b/src/structured_values/standard_information.rs
@@ -2,22 +2,22 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::attribute::NtfsAttributeType;
-use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
-use crate::ntfs::Ntfs;
-use crate::structured_values::{NewNtfsStructuredValue, NtfsFileAttributeFlags};
+use crate::structured_values::{
+ NtfsFileAttributeFlags, NtfsStructuredValue, NtfsStructuredValueFromData,
+};
use crate::time::NtfsTime;
-use binread::io::{Read, Seek};
+use binread::io::Cursor;
use binread::{BinRead, BinReaderExt};
/// Size of all [`StandardInformationData`] fields plus some reserved bytes.
-const STANDARD_INFORMATION_SIZE_NTFS1: u64 = 48;
+const STANDARD_INFORMATION_SIZE_NTFS1: usize = 48;
/// Size of all [`StandardInformationData`] plus [`StandardInformationDataNtfs3`] fields.
-const STANDARD_INFORMATION_SIZE_NTFS3: u64 = 72;
+const STANDARD_INFORMATION_SIZE_NTFS3: usize = 72;
#[derive(BinRead, Clone, Debug)]
-struct StandardInformationData {
+struct StandardInformationDataNtfs1 {
creation_time: NtfsTime,
modification_time: NtfsTime,
mft_record_modification_time: NtfsTime,
@@ -38,13 +38,13 @@ struct StandardInformationDataNtfs3 {
#[derive(Clone, Debug)]
pub struct NtfsStandardInformation {
- data: StandardInformationData,
+ ntfs1_data: StandardInformationDataNtfs1,
ntfs3_data: Option<StandardInformationDataNtfs3>,
}
impl NtfsStandardInformation {
pub fn access_time(&self) -> NtfsTime {
- self.data.access_time
+ self.ntfs1_data.access_time
}
pub fn class_id(&self) -> Option<u32> {
@@ -52,11 +52,11 @@ impl NtfsStandardInformation {
}
pub fn creation_time(&self) -> NtfsTime {
- self.data.creation_time
+ self.ntfs1_data.creation_time
}
pub fn file_attributes(&self) -> NtfsFileAttributeFlags {
- NtfsFileAttributeFlags::from_bits_truncate(self.data.file_attributes)
+ NtfsFileAttributeFlags::from_bits_truncate(self.ntfs1_data.file_attributes)
}
pub fn maximum_versions(&self) -> Option<u32> {
@@ -64,11 +64,11 @@ impl NtfsStandardInformation {
}
pub fn mft_record_modification_time(&self) -> NtfsTime {
- self.data.mft_record_modification_time
+ self.ntfs1_data.mft_record_modification_time
}
pub fn modification_time(&self) -> NtfsTime {
- self.data.modification_time
+ self.ntfs1_data.modification_time
}
pub fn owner_id(&self) -> Option<u32> {
@@ -92,34 +92,33 @@ impl NtfsStandardInformation {
}
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsStandardInformation {
- fn new<T>(
- _ntfs: &'n Ntfs,
- fs: &mut T,
- value: NtfsAttributeValue<'n>,
- length: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- if length < STANDARD_INFORMATION_SIZE_NTFS1 {
+impl NtfsStructuredValue for NtfsStandardInformation {
+ const TY: NtfsAttributeType = NtfsAttributeType::StandardInformation;
+}
+
+impl<'d> NtfsStructuredValueFromData<'d> for NtfsStandardInformation {
+ fn from_data(data: &'d [u8], position: u64) -> Result<Self> {
+ if data.len() < STANDARD_INFORMATION_SIZE_NTFS1 {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::StandardInformation,
expected: STANDARD_INFORMATION_SIZE_NTFS1,
- actual: length,
+ actual: data.len(),
});
}
- let mut value_attached = value.attach(fs);
- let data = value_attached.read_le::<StandardInformationData>()?;
+ let mut cursor = Cursor::new(data);
+ let ntfs1_data = cursor.read_le::<StandardInformationDataNtfs1>()?;
let mut ntfs3_data = None;
- if length >= STANDARD_INFORMATION_SIZE_NTFS3 {
- ntfs3_data = Some(value_attached.read_le::<StandardInformationDataNtfs3>()?);
+ if data.len() >= STANDARD_INFORMATION_SIZE_NTFS3 {
+ ntfs3_data = Some(cursor.read_le::<StandardInformationDataNtfs3>()?);
}
- Ok(Self { data, ntfs3_data })
+ Ok(Self {
+ ntfs1_data,
+ ntfs3_data,
+ })
}
}
@@ -128,7 +127,6 @@ mod tests {
use super::*;
use crate::ntfs::Ntfs;
use crate::ntfs_file::KnownNtfsFile;
- use crate::structured_values::NtfsStructuredValue;
#[test]
fn test_standard_information() {
@@ -137,10 +135,10 @@ mod tests {
let mft = ntfs
.ntfs_file(&mut testfs1, KnownNtfsFile::MFT as u64)
.unwrap();
- let mut mft_attributes = mft.attributes().attach(&mut testfs1);
+ let mut mft_attributes = mft.attributes();
// Check the StandardInformation attribute of the MFT.
- let attribute = mft_attributes.nth(0).unwrap().unwrap();
+ let attribute = mft_attributes.nth(0).unwrap();
assert_eq!(
attribute.ty().unwrap(),
NtfsAttributeType::StandardInformation,
@@ -151,11 +149,9 @@ mod tests {
assert_eq!(attribute.value_length(), 72);
// Try to read the actual information.
- let value = attribute.structured_value(&mut testfs1).unwrap();
- let _standard_info = match value {
- NtfsStructuredValue::StandardInformation(standard_info) => standard_info,
- v => panic!("Unexpected NtfsStructuredValue: {:?}", v),
- };
+ let _standard_info = attribute
+ .resident_structured_value::<NtfsStandardInformation>()
+ .unwrap();
// There are no reliable values to check here, so that's it.
}
diff --git a/src/structured_values/volume_information.rs b/src/structured_values/volume_information.rs
index 76ae2e3..dfc2290 100644
--- a/src/structured_values/volume_information.rs
+++ b/src/structured_values/volume_information.rs
@@ -2,16 +2,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::attribute::NtfsAttributeType;
-use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
-use crate::ntfs::Ntfs;
-use crate::structured_values::NewNtfsStructuredValue;
-use binread::io::{Read, Seek};
+use crate::structured_values::{NtfsStructuredValue, NtfsStructuredValueFromData};
+use binread::io::Cursor;
use binread::{BinRead, BinReaderExt};
use bitflags::bitflags;
/// Size of all [`VolumeInformationData`] fields.
-const VOLUME_INFORMATION_SIZE: u64 = 12;
+const VOLUME_INFORMATION_SIZE: usize = 12;
#[derive(BinRead, Clone, Debug)]
struct VolumeInformationData {
@@ -37,45 +35,41 @@ bitflags! {
#[derive(Clone, Debug)]
pub struct NtfsVolumeInformation {
- data: VolumeInformationData,
+ info: VolumeInformationData,
}
impl NtfsVolumeInformation {
pub fn flags(&self) -> NtfsVolumeFlags {
- NtfsVolumeFlags::from_bits_truncate(self.data.flags)
+ NtfsVolumeFlags::from_bits_truncate(self.info.flags)
}
pub fn major_version(&self) -> u8 {
- self.data.major_version
+ self.info.major_version
}
pub fn minor_version(&self) -> u8 {
- self.data.minor_version
+ self.info.minor_version
}
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsVolumeInformation {
- fn new<T>(
- _ntfs: &'n Ntfs,
- fs: &mut T,
- value: NtfsAttributeValue<'n>,
- length: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- if length < VOLUME_INFORMATION_SIZE {
+impl NtfsStructuredValue for NtfsVolumeInformation {
+ const TY: NtfsAttributeType = NtfsAttributeType::VolumeInformation;
+}
+
+impl<'d> NtfsStructuredValueFromData<'d> for NtfsVolumeInformation {
+ fn from_data(data: &'d [u8], position: u64) -> Result<Self> {
+ if data.len() < VOLUME_INFORMATION_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::StandardInformation,
expected: VOLUME_INFORMATION_SIZE,
- actual: length,
+ actual: data.len(),
});
}
- let mut value_attached = value.attach(fs);
- let data = value_attached.read_le::<VolumeInformationData>()?;
+ let mut cursor = Cursor::new(data);
+ let info = cursor.read_le::<VolumeInformationData>()?;
- Ok(Self { data })
+ Ok(Self { info })
}
}
diff --git a/src/structured_values/volume_name.rs b/src/structured_values/volume_name.rs
index 8d75c04..066ebe0 100644
--- a/src/structured_values/volume_name.rs
+++ b/src/structured_values/volume_name.rs
@@ -2,73 +2,60 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::attribute::NtfsAttributeType;
-use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
-use crate::ntfs::Ntfs;
use crate::string::NtfsString;
-use crate::structured_values::NewNtfsStructuredValue;
-use binread::io::{Read, Seek};
+use crate::structured_values::{NtfsStructuredValue, NtfsStructuredValueFromData};
+use alloc::vec::Vec;
use core::mem;
/// The smallest VolumeName attribute has a name containing just a single character.
-const VOLUME_NAME_MIN_SIZE: u64 = mem::size_of::<u16>() as u64;
+const VOLUME_NAME_MIN_SIZE: usize = mem::size_of::<u16>();
/// The largest VolumeName attribute has a name containing 128 UTF-16 code points (256 bytes).
-const VOLUME_NAME_MAX_SIZE: u64 = 128 * mem::size_of::<u16>() as u64;
+const VOLUME_NAME_MAX_SIZE: usize = 128 * mem::size_of::<u16>();
#[derive(Clone, Debug)]
-pub struct NtfsVolumeName<'n> {
- value: NtfsAttributeValue<'n>,
- name_length: u16,
+pub struct NtfsVolumeName {
+ name: Vec<u8>,
}
-impl<'n> NtfsVolumeName<'n> {
+impl NtfsVolumeName {
+ /// Gets the file name and returns it wrapped in an [`NtfsString`].
+ pub fn name<'s>(&'s self) -> NtfsString<'s> {
+ NtfsString(&self.name)
+ }
+
/// Returns the volume name length, in bytes.
///
/// A volume name has a maximum length of 128 UTF-16 code points (256 bytes).
pub fn name_length(&self) -> usize {
- self.name_length as usize
+ self.name.len()
}
+}
- /// Reads the volume name into the given buffer, and returns an
- /// [`NtfsString`] wrapping that buffer.
- pub fn read_name<'a, T>(&self, fs: &mut T, buf: &'a mut [u8]) -> Result<NtfsString<'a>>
- where
- T: Read + Seek,
- {
- let value_attached = self.value.clone().attach(fs);
- NtfsString::from_reader(value_attached, self.name_length(), buf)
- }
+impl NtfsStructuredValue for NtfsVolumeName {
+ const TY: NtfsAttributeType = NtfsAttributeType::VolumeName;
}
-impl<'n> NewNtfsStructuredValue<'n> for NtfsVolumeName<'n> {
- fn new<T>(
- _ntfs: &'n Ntfs,
- _fs: &mut T,
- value: NtfsAttributeValue<'n>,
- length: u64,
- ) -> Result<Self>
- where
- T: Read + Seek,
- {
- if length < VOLUME_NAME_MIN_SIZE {
+impl<'d> NtfsStructuredValueFromData<'d> for NtfsVolumeName {
+ fn from_data(data: &'d [u8], position: u64) -> Result<Self> {
+ if data.len() < VOLUME_NAME_MIN_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::VolumeName,
expected: VOLUME_NAME_MIN_SIZE,
- actual: length,
+ actual: data.len(),
});
- } else if length > VOLUME_NAME_MAX_SIZE {
+ } else if data.len() > VOLUME_NAME_MAX_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
- position: value.data_position().unwrap(),
+ position,
ty: NtfsAttributeType::VolumeName,
expected: VOLUME_NAME_MAX_SIZE,
- actual: length,
+ actual: data.len(),
});
}
- let name_length = length as u16;
-
- Ok(Self { value, name_length })
+ let name = data.to_vec();
+ Ok(Self { name })
}
}
diff --git a/src/time.rs b/src/time.rs
index ea351da..3f218da 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -43,6 +43,12 @@ impl Deref for NtfsTime {
}
}
+impl From<u64> for NtfsTime {
+ fn from(value: u64) -> Self {
+ Self(value)
+ }
+}
+
#[cfg(feature = "chrono")]
impl TryFrom<DateTime<Utc>> for NtfsTime {
type Error = NtfsError;