diff options
Diffstat (limited to 'src/attribute.rs')
-rw-r--r-- | src/attribute.rs | 456 |
1 files changed, 223 insertions, 233 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> {} |