From 62da6dc109d669409c5d33d8f101c4a235940166 Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Mon, 9 Aug 2021 22:56:21 +0200 Subject: Add a few convenience functions to simplify working with the crate. --- src/error.rs | 2 + src/file.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++++++- src/file_reference.rs | 11 +++++ src/index_entry.rs | 15 ++++++ src/ntfs.rs | 35 +++++--------- src/upcase_table.rs | 14 +----- 6 files changed, 165 insertions(+), 38 deletions(-) diff --git a/src/error.rs b/src/error.rs index 85b6d3e..04f93df 100644 --- a/src/error.rs +++ b/src/error.rs @@ -132,6 +132,8 @@ 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 file at byte position {position:#010x} is not a directory. + NotADirectory { position: u64 }, /// The NTFS attribute at byte position {position:#010x} has type {ty:?}, but a different type has been requested StructuredValueOfDifferentType { position: u64, diff --git a/src/file.rs b/src/file.rs index 770ca67..c5f25a4 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,10 +1,15 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later -use crate::attribute::NtfsAttributes; +use crate::attribute::{NtfsAttribute, NtfsAttributeType, NtfsAttributes}; use crate::error::{NtfsError, Result}; +use crate::index::NtfsIndex; +use crate::indexes::NtfsFileNameIndex; use crate::ntfs::Ntfs; use crate::record::{Record, RecordHeader}; +use crate::structured_values::{ + NtfsFileName, NtfsIndexAllocation, NtfsIndexRoot, NtfsStandardInformation, +}; use binread::io::{Read, Seek, SeekFrom}; use bitflags::bitflags; use byteorder::{ByteOrder, LittleEndian}; @@ -77,10 +82,110 @@ impl<'n> NtfsFile<'n> { LittleEndian::read_u32(&self.record.data()[start..]) } + /// Returns the first attribute of the given type, or `NtfsError::AttributeNotFound`. + pub(crate) fn attribute_by_ty<'f>( + &'f self, + ty: NtfsAttributeType, + ) -> Result> { + self.attributes() + .find(|attribute| { + // TODO: Replace by attribute.ty().contains() once https://github.com/rust-lang/rust/issues/62358 has landed. + attribute.ty().map(|x| x == ty).unwrap_or(false) + }) + .ok_or(NtfsError::AttributeNotFound { + position: self.position(), + ty, + }) + } + pub fn attributes<'f>(&'f self) -> NtfsAttributes<'n, 'f> { NtfsAttributes::new(self) } + /// Convenience function to get a $DATA attribute of this file. + /// + /// As NTFS supports multiple data streams per file, you can optionally specify a data stream + /// name and NTFS will look up the corresponding $DATA attribute. + /// If you specify `None` for `data_stream_name`, the default unnamed $DATA attribute will be looked + /// up (commonly known as the "file data"). + /// + /// If you need more control over which $DATA attribute is available and picked up, + /// you can use [`NtfsFile::attributes`] to iterate over all attributes of this file. + pub fn data<'f>( + &'f self, + data_stream_name: Option<&str>, + ) -> Option>> { + // Create an iterator that emits all $DATA attributes. + let iter = self.attributes().filter(|attribute| { + // TODO: Replace by attribute.ty().contains() once https://github.com/rust-lang/rust/issues/62358 has landed. + attribute + .ty() + .map(|ty| ty == NtfsAttributeType::Data) + .unwrap_or(false) + }); + + for attribute in iter { + match (attribute.name(), data_stream_name) { + (None, None) => { + // We found the unnamed $DATA attribute and are looking for the unnamed $DATA attribute. + return Some(Ok(attribute)); + } + (Some(Ok(name)), Some(data_stream_name)) => { + // We found a named $DATA attribute and are looking for a named $DATA attribute. + if data_stream_name == name { + return Some(Ok(attribute)); + } + } + (Some(Err(e)), _) => { + // We hit an error while fetching the $DATA attribute name. + return Some(Err(e)); + } + _ => { + // In any other case, we didn't find what we are looking for. + continue; + } + } + } + + None + } + + /// Convenience function to return an [`NtfsIndex`] if this file is a directory. + /// + /// Apart from any propagated error, this function may return [`NtfsError::NotADirectory`] + /// if this [`NtfsFile`] is not a directory. + /// + /// If you need more control over the picked up $INDEX_ROOT and $INDEX_ALLOCATION attributes + /// you can use [`NtfsFile::attributes`] to iterate over all attributes of this file. + pub fn directory_index<'f, T>( + &'f self, + fs: &mut T, + ) -> Result> + where + T: Read + Seek, + { + if !self.flags().contains(NtfsFileFlags::IS_DIRECTORY) { + return Err(NtfsError::NotADirectory { + position: self.position(), + }); + } + + // Get the Index Root attribute that needs to exist. + let index_root = self + .attribute_by_ty(NtfsAttributeType::IndexRoot)? + .resident_structured_value::()?; + + // Get the Index Allocation attribute that is only required for large indexes. + let index_allocation_attribute = self.attribute_by_ty(NtfsAttributeType::IndexAllocation); + let index_allocation = if let Ok(attribute) = index_allocation_attribute { + Some(attribute.non_resident_structured_value::<_, NtfsIndexAllocation>(fs)?) + } else { + None + }; + + NtfsIndex::::new(index_root, index_allocation) + } + pub(crate) fn first_attribute_offset(&self) -> u16 { let start = offset_of!(FileRecordHeader, first_attribute_offset); LittleEndian::read_u16(&self.record.data()[start..]) @@ -97,6 +202,25 @@ impl<'n> NtfsFile<'n> { LittleEndian::read_u16(&self.record.data()[start..]) } + /// Convenience function to get the $STANDARD_INFORMATION attribute of this file + /// (see [`NtfsStandardInformation`]). + /// + /// This internally calls [`NtfsFile::attributes`] to iterate through the file's + /// attributes and pick up the first $STANDARD_INFORMATION attribute. + pub fn info(&self) -> Result { + let attribute = self.attribute_by_ty(NtfsAttributeType::StandardInformation)?; + attribute.resident_structured_value::() + } + + /// Convenience function to get the $FILE_NAME attribute of this file (see [`NtfsFileName`]). + /// + /// This internally calls [`NtfsFile::attributes`] to iterate through the file's + /// attributes and pick up the first $FILE_NAME attribute. + pub fn name(&self) -> Result { + let attribute = self.attribute_by_ty(NtfsAttributeType::FileName)?; + attribute.resident_structured_value::() + } + pub(crate) fn ntfs(&self) -> &'n Ntfs { self.record.ntfs() } diff --git a/src/file_reference.rs b/src/file_reference.rs index aa18764..836bfbf 100644 --- a/src/file_reference.rs +++ b/src/file_reference.rs @@ -1,6 +1,9 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +use crate::error::Result; +use crate::file::NtfsFile; +use crate::ntfs::Ntfs; use binread::io::{Read, Seek}; use binread::BinRead; @@ -19,4 +22,12 @@ impl NtfsFileReference { pub fn sequence_number(&self) -> u16 { (u64::from_le_bytes(self.0) >> 48) as u16 } + + /// Returns an [`NtfsFile`] for the file referenced by this object. + pub fn to_file<'n, T>(&self, ntfs: &'n Ntfs, fs: &mut T) -> Result> + where + T: Read + Seek, + { + ntfs.file(fs, self.file_record_number()) + } } diff --git a/src/index_entry.rs b/src/index_entry.rs index 804a5d6..c84b67f 100644 --- a/src/index_entry.rs +++ b/src/index_entry.rs @@ -2,14 +2,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::error::Result; +use crate::file::NtfsFile; use crate::file_reference::NtfsFileReference; use crate::indexes::{ NtfsIndexEntryData, NtfsIndexEntryHasData, NtfsIndexEntryHasFileReference, NtfsIndexEntryKey, NtfsIndexEntryType, }; +use crate::ntfs::Ntfs; use crate::types::Vcn; +use binread::io::{Read, Seek}; use bitflags::bitflags; use byteorder::{ByteOrder, LittleEndian}; +use core::convert::TryInto; use core::iter::FusedIterator; use core::marker::PhantomData; use core::mem; @@ -43,6 +47,7 @@ bitflags! { } } +#[derive(Clone, Debug)] pub(crate) struct IndexEntryRange where E: NtfsIndexEntryType, @@ -128,6 +133,7 @@ where LittleEndian::read_u16(&self.slice[start..]) } + /// Returns an [`NtfsFileReference`] for the file referenced by this index entry. pub fn file_reference(&self) -> NtfsFileReference where E: NtfsIndexEntryHasFileReference, @@ -186,6 +192,15 @@ where Some(vcn) } + + /// Returns an [`NtfsFile`] for the file referenced by this index entry. + pub fn to_file<'n, T>(&self, ntfs: &'n Ntfs, fs: &mut T) -> Result> + where + E: NtfsIndexEntryHasFileReference, + T: Read + Seek, + { + self.file_reference().to_file(ntfs, fs) + } } pub(crate) struct IndexNodeEntryRanges diff --git a/src/ntfs.rs b/src/ntfs.rs index e1fcb0e..22b7306 100644 --- a/src/ntfs.rs +++ b/src/ntfs.rs @@ -106,9 +106,12 @@ impl Ntfs { Ok(()) } - /// Returns the root [`Dir`] of this NTFS volume. - pub fn root_dir(&self) -> ! { - panic!("TODO") + /// Returns the root directory of this NTFS volume as an [`NtfsFile`]. + pub fn root_directory<'n, T>(&'n self, fs: &mut T) -> Result> + where + T: Read + Seek, + { + self.file(fs, KnownNtfsFile::RootDirectory as u64) } /// Returns the size of a single sector in bytes. @@ -144,19 +147,7 @@ impl Ntfs { T: Read + Seek, { let volume_file = self.file(fs, KnownNtfsFile::Volume as u64)?; - 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::VolumeInformation) - .unwrap_or(false) - }) - .ok_or(NtfsError::AttributeNotFound { - position: volume_file.position(), - ty: NtfsAttributeType::VolumeName, - })?; + let attribute = volume_file.attribute_by_ty(NtfsAttributeType::VolumeInformation)?; attribute.resident_structured_value::() } @@ -169,13 +160,9 @@ impl Ntfs { T: Read + Seek, { let volume_file = iter_try!(self.file(fs, KnownNtfsFile::Volume as u64)); - 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 attribute = volume_file + .attribute_by_ty(NtfsAttributeType::VolumeName) + .ok()?; let volume_name = iter_try!(attribute.resident_structured_value::()); Some(Ok(volume_name)) @@ -192,7 +179,7 @@ mod tests { let ntfs = Ntfs::new(&mut testfs1).unwrap(); assert_eq!(ntfs.cluster_size(), 512); assert_eq!(ntfs.sector_size(), 512); - assert_eq!(ntfs.size(), 1049088); + assert_eq!(ntfs.size(), 2096640); } #[test] diff --git a/src/upcase_table.rs b/src/upcase_table.rs index 51464f7..9ee5883 100644 --- a/src/upcase_table.rs +++ b/src/upcase_table.rs @@ -36,19 +36,7 @@ impl UpcaseTable { { // Lookup the $UpCase file and its $DATA attribute. let upcase_file = ntfs.file(fs, KnownNtfsFile::UpCase as u64)?; - let data_attribute = upcase_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::Data) - .unwrap_or(false) - }) - .ok_or(NtfsError::AttributeNotFound { - position: upcase_file.position(), - ty: NtfsAttributeType::VolumeName, - })?; + let data_attribute = upcase_file.attribute_by_ty(NtfsAttributeType::Data)?; if data_attribute.value_length() != UPCASE_TABLE_SIZE { return Err(NtfsError::InvalidUpcaseTableSize { expected: UPCASE_TABLE_SIZE, -- cgit v1.2.3