From 5ff15ae17d618f846176ab40140c4141aab24bbb Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Wed, 15 Dec 2021 20:06:35 +0100 Subject: Add the missing documentation and some final polishing. --- Cargo.toml | 4 + examples/ntfs-shell/main.rs | 24 ++-- src/attribute.rs | 135 ++++++++++++++++++--- src/attribute_value/attribute_list_non_resident.rs | 49 ++++---- src/attribute_value/mod.rs | 24 ++++ src/attribute_value/non_resident.rs | 97 +++++++++------ src/attribute_value/resident.rs | 10 +- src/error.rs | 41 ++++--- src/file.rs | 108 ++++++++++++++--- src/file_reference.rs | 10 ++ src/guid.rs | 1 + src/index.rs | 32 +++-- src/index_entry.rs | 66 +++++++++- src/index_record.rs | 42 +++++-- src/indexes/file_name.rs | 2 +- src/indexes/mod.rs | 34 +++++- src/lib.rs | 38 +++++- src/ntfs.rs | 13 +- src/string.rs | 3 +- src/structured_values/attribute_list.rs | 55 ++++++++- src/structured_values/file_name.rs | 90 +++++++++++++- src/structured_values/index_allocation.rs | 55 ++++++++- src/structured_values/index_root.rs | 31 ++++- src/structured_values/mod.rs | 34 +++++- src/structured_values/object_id.rs | 11 ++ src/structured_values/standard_information.rs | 25 ++++ src/structured_values/volume_information.rs | 14 +++ src/structured_values/volume_name.rs | 12 +- src/time.rs | 11 +- src/traits.rs | 8 ++ src/types.rs | 14 +++ 31 files changed, 914 insertions(+), 179 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70ccd5e..4fd9dc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,7 @@ std = ["arrayvec/std", "binread/std", "byteorder/std"] [[example]] name = "ntfs-shell" required-features = ["chrono"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/examples/ntfs-shell/main.rs b/examples/ntfs-shell/main.rs index e4b8a22..32f9a71 100644 --- a/examples/ntfs-shell/main.rs +++ b/examples/ntfs-shell/main.rs @@ -55,7 +55,7 @@ fn main() -> Result<()> { }; println!("**********************************************************************"); - println!("ntfs-shell - Demonstration of rust-ntfs"); + println!("ntfs-shell - Demonstration of the ntfs Rust crate"); println!("by Colin Finck "); println!("**********************************************************************"); println!(); @@ -129,7 +129,7 @@ where if ty == NtfsAttributeType::AttributeList { let list = attribute.structured_value::<_, NtfsAttributeList>(&mut info.fs)?; - let mut list_iter = list.iter(); + let mut list_iter = list.entries(); while let Some(entry) = list_iter.next(&mut info.fs) { let entry = entry?; @@ -221,7 +221,7 @@ where } bail!( - "Found no FileName attribute for FILE record {:#x}", + "Found no FileName attribute for File Record {:#x}", file.file_record_number() ) } @@ -260,7 +260,7 @@ where let entry = maybe_entry.unwrap()?; let file_name = entry .key() - .expect("key must exist for a found index entry")?; + .expect("key must exist for a found Index Entry")?; if !file_name.is_directory() { println!("\"{}\" is not a directory.", arg); @@ -293,13 +293,13 @@ where .last() .unwrap() .directory_index(&mut info.fs)?; - let mut iter = index.iter(); + let mut iter = index.entries(); while let Some(entry) = iter.next(&mut info.fs) { let entry = entry?; let file_name = entry .key() - .expect("key must exist for a found index entry")?; + .expect("key must exist for a found Index Entry")?; let prefix = if file_name.is_directory() { "" @@ -321,11 +321,11 @@ where println!("{:=^72}", " FILE RECORD "); println!("{:34}{}", "Allocated Size:", file.allocated_size()); println!("{:34}{:#x}", "Byte Position:", file.position()); + println!("{:34}{}", "Data Size:", file.data_size()); println!("{:34}{}", "Hard-Link Count:", file.hard_link_count()); println!("{:34}{}", "Is Directory:", file.is_directory()); println!("{:34}{:#x}", "Record Number:", file.file_record_number()); println!("{:34}{}", "Sequence Number:", file.sequence_number()); - println!("{:34}{}", "Used Size:", file.used_size()); let mut attributes = file.attributes(); while let Some(attribute_item) = attributes.next(&mut info.fs) { @@ -452,7 +452,7 @@ where println!("{:20}{}", "Size:", info.ntfs.size()); let volume_name = if let Some(Ok(volume_name)) = info.ntfs.volume_name(&mut info.fs) { - volume_name.name().to_string_lossy() + format!("\"{}\"", volume_name.name()) } else { "".to_string() }; @@ -525,14 +525,14 @@ fn help(arg: &str) -> Result<()> { println!("Usage: attr FILE"); println!(); println!("Shows the structure of all NTFS attributes of a single file, not including their data runs."); - println!("Try \"attr_runs\" if you are also interested in data run information."); + println!("Try \"attr_runs\" if you are also interested in Data Run information."); help_file("attr"); } "attr_runs" => { println!("Usage: attr_runs FILE"); println!(); println!("Shows the structure of all NTFS attributes of a single file, including their data runs."); - println!("Try \"attr\" if you don't need the data run information."); + println!("Try \"attr\" if you don't need the Data Run information."); help_file("attr_runs"); } "cd" => { @@ -596,8 +596,8 @@ fn help_file(command: &str) { println!(" Examples:"); println!(" ○ {} ntoskrnl.exe", command); println!(" ○ {} File with spaces.exe", command); - println!(" ● A file record number anywhere on the filesystem."); - println!(" This is indicated through a leading slash (/). A hexadecimal file record number is indicated via 0x."); + println!(" ● A File Record Number anywhere on the filesystem."); + println!(" This is indicated through a leading slash (/). A hexadecimal File Record Number is indicated via 0x."); println!(" Examples:"); println!(" ○ {} /5", command); println!(" ○ {} /0xa299", command); diff --git a/src/attribute.rs b/src/attribute.rs index e6c1fd2..f44686d 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -23,7 +23,7 @@ use enumn::N; use memoffset::offset_of; use strum_macros::Display; -/// On-disk structure of the generic header of an NTFS attribute. +/// On-disk structure of the generic header of an NTFS Attribute. #[repr(C, packed)] struct NtfsAttributeHeader { /// Type of the attribute, known types are in [`NtfsAttributeType`]. @@ -43,6 +43,7 @@ struct NtfsAttributeHeader { } bitflags! { + /// Flags returned by [`NtfsAttribute::flags`]. pub struct NtfsAttributeFlags: u16 { /// The attribute value is compressed. const COMPRESSED = 0x0001; @@ -53,7 +54,7 @@ bitflags! { } } -/// On-disk structure of the extra header of an NTFS attribute that has a resident value. +/// On-disk structure of the extra header of an NTFS Attribute that has a resident value. #[repr(C, packed)] struct NtfsResidentAttributeHeader { attribute_header: NtfsAttributeHeader, @@ -65,7 +66,7 @@ struct NtfsResidentAttributeHeader { indexed_flag: u8, } -/// On-disk structure of the extra header of an NTFS attribute that has a non-resident value. +/// On-disk structure of the extra header of an NTFS Attribute that has a non-resident value. #[repr(C, packed)] struct NtfsNonResidentAttributeHeader { attribute_header: NtfsAttributeHeader, @@ -96,28 +97,73 @@ struct NtfsNonResidentAttributeHeader { initialized_size: u64, } +/// All known NTFS Attribute types. +/// +/// Reference: #[derive(Clone, Copy, Debug, Display, Eq, N, PartialEq)] #[repr(u32)] pub enum NtfsAttributeType { + /// $STANDARD_INFORMATION, see [`NtfsStandardInformation`]. + /// + /// [`NtfsStandardInformation`]: crate::structured_values::NtfsStandardInformation StandardInformation = 0x10, + /// $ATTRIBUTE_LIST, see [`NtfsAttributeList`]. + /// + /// [`NtfsAttributeList`]: crate::structured_values::NtfsAttributeList AttributeList = 0x20, + /// $FILE_NAME, see [`NtfsFileName`]. + /// + /// [`NtfsFileName`]: crate::structured_values::NtfsFileName FileName = 0x30, + /// $OBJECT_ID, see [`NtfsObjectId`]. + /// + /// [`NtfsObjectId`]: crate::structured_values::NtfsObjectId ObjectId = 0x40, + /// $SECURITY_DESCRIPTOR SecurityDescriptor = 0x50, + /// $VOLUME_NAME, see [`NtfsVolumeName`]. + /// + /// [`NtfsVolumeName`]: crate::structured_values::NtfsVolumeName VolumeName = 0x60, + /// $VOLUME_INFORMATION, see [`NtfsVolumeInformation`]. + /// + /// [`NtfsVolumeInformation`]: crate::structured_values::NtfsVolumeInformation VolumeInformation = 0x70, + /// $DATA, see [`NtfsFile::data`]. Data = 0x80, + /// $INDEX_ROOT, see [`NtfsIndexRoot`]. + /// + /// [`NtfsIndexRoot`]: crate::structured_values::NtfsIndexRoot IndexRoot = 0x90, + /// $INDEX_ALLOCATION, see [`NtfsIndexAllocation`]. + /// + /// [`NtfsIndexAllocation`]: crate::structured_values::NtfsIndexAllocation IndexAllocation = 0xA0, + /// $BITMAP Bitmap = 0xB0, + /// $REPARSE_POINT ReparsePoint = 0xC0, + /// $EA_INFORMATION EAInformation = 0xD0, + /// $EA EA = 0xE0, + /// $PROPERTY_SET PropertySet = 0xF0, + /// $LOGGED_UTILITY_STREAM LoggedUtilityStream = 0x100, + /// Marks the end of the valid attributes. End = 0xFFFF_FFFF, } +/// A single NTFS Attribute of an [`NtfsFile`]. +/// +/// Not to be confused with [`NtfsFileAttributeFlags`]. +/// +/// This structure is returned by the [`NtfsAttributesRaw`] iterator as well as [`NtfsAttributeItem::to_attribute`]. +/// +/// Reference: +/// +/// [`NtfsFileAttributeFlags`]: crate::structured_values::NtfsFileAttributeFlags #[derive(Clone, Debug)] pub struct NtfsAttribute<'n, 'f> { file: &'f NtfsFile<'n>, @@ -140,7 +186,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { } } - /// Returns the length of this NTFS attribute, in bytes. + /// Returns the length of this NTFS Attribute, in bytes. /// /// This denotes the length of the attribute structure on disk. /// Apart from various headers, this structure also includes the name and, @@ -172,7 +218,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { is_non_resident == 0 } - /// Gets the name of this NTFS attribute (if any) and returns it wrapped in an [`NtfsString`]. + /// 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. @@ -195,7 +241,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { LittleEndian::read_u16(&self.file.record_data()[start..]) } - /// Returns the length of the name of this NTFS attribute, in bytes. + /// Returns the length of the name of this NTFS Attribute, in bytes. /// /// An attribute name has a maximum length of 255 UTF-16 code points (510 bytes). /// It is always part of the attribute itself and hence also of the length @@ -243,11 +289,20 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { self.offset } - /// Returns the absolute position of this NTFS attribute within the filesystem, in bytes. + /// 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 } + /// Attempts to parse the value data as the given resident structured value type and returns that. + /// + /// This is a fast path for attributes that are always resident. + /// It doesn't need a reference to the filesystem reader. + /// + /// This function first checks that the attribute is of the required type for that structured value + /// and if it's a resident attribute. + /// It returns with an error if that is not the case. + /// It also returns an error for any parsing problem. pub fn resident_structured_value(&self) -> Result where S: NtfsStructuredValueFromResidentAttributeValue<'n, 'f>, @@ -294,6 +349,11 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { LittleEndian::read_u16(&self.file.record_data()[start..]) } + /// Attempts to parse the value data as the given structured value type and returns that. + /// + /// This function first checks that the attribute is of the required type for that structured value. + /// It returns with an error if that is not the case. + /// It also returns an error for any parsing problem. pub fn structured_value(&self, fs: &mut T) -> Result where T: Read + Seek, @@ -311,7 +371,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { S::from_attribute_value(fs, self.value()?) } - /// Returns the type of this NTFS attribute, or [`NtfsError::UnsupportedAttributeType`] + /// Returns the type of this NTFS Attribute, or [`NtfsError::UnsupportedAttributeType`] /// if it's an unknown type. pub fn ty(&self) -> Result { let start = self.offset + offset_of!(NtfsAttributeHeader, ty); @@ -369,7 +429,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { Ok(()) } - /// Returns an [`NtfsAttributeValue`] structure to read the value of this NTFS attribute. + /// Returns an [`NtfsAttributeValue`] structure to read the value of this NTFS Attribute. pub fn value(&self) -> Result> { if let Some(list_entries) = self.list_entries { // The first attribute reports the entire data size for all connected attributes @@ -394,7 +454,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { } } - /// Returns the length of the value of this NTFS attribute, in bytes. + /// Returns the length of the value data of this NTFS Attribute, in bytes. pub fn value_length(&self) -> u64 { if self.is_resident() { self.resident_value_length() as u64 @@ -404,6 +464,17 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { } } +/// Iterator over +/// all attributes of an [`NtfsFile`], +/// returning an [`NtfsAttributeItem`] for each entry. +/// +/// This iterator is returned from the [`NtfsFile::attributes`] function. +/// It provides a flattened "data-centric" view of the attributes and abstracts away the filesystem details +/// to deal with many or large attributes (Attribute Lists and connected attributes). +/// +/// Check [`NtfsAttributesRaw`] if you want to iterate over the plain attributes on the filesystem. +/// See [`NtfsAttributesAttached`] for an iterator that implements [`Iterator`] and [`FusedIterator`]. +#[derive(Clone, Debug)] pub struct NtfsAttributes<'n, 'f> { raw_iter: NtfsAttributesRaw<'n, 'f>, list_entries: Option>, @@ -419,6 +490,8 @@ impl<'n, 'f> NtfsAttributes<'n, 'f> { } } + /// Returns a variant of this iterator that implements [`Iterator`] and [`FusedIterator`] + /// by mutably borrowing the filesystem reader. pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributesAttached<'n, 'f, 'a, T> where T: Read + Seek, @@ -426,6 +499,7 @@ impl<'n, 'f> NtfsAttributes<'n, 'f> { NtfsAttributesAttached::new(fs, self) } + /// See [`Iterator::next`]. pub fn next(&mut self, fs: &mut T) -> Option>> where T: Read + Seek, @@ -433,7 +507,7 @@ impl<'n, 'f> NtfsAttributes<'n, 'f> { loop { if let Some(attribute_list_entries) = &mut self.list_entries { loop { - // If the next AttributeList entry turns out to be a non-resident attribute, that attribute's + // If the next Attribute List entry turns out to be a non-resident attribute, that attribute's // value may be split over multiple (adjacent) attributes. // To view this value as a single one, we need an `AttributeListConnectedEntries` iterator // and that iterator needs `NtfsAttributeListEntries` where the next call to `next` yields @@ -450,12 +524,12 @@ impl<'n, 'f> NtfsAttributes<'n, 'f> { let entry_record_number = entry.base_file_reference().file_record_number(); let entry_ty = iter_try!(entry.ty()); - // Ignore all AttributeList entries that just repeat attributes of the raw iterator. + // Ignore all Attribute List entries that just repeat attributes of the raw iterator. if entry_record_number == self.raw_iter.file.file_record_number() { continue; } - // Ignore all AttributeList entries that are connected attributes of a previous one. + // Ignore all Attribute List entries that are connected attributes of a previous one. if let Some((skip_instance, skip_ty)) = self.list_skip_info { if entry_instance == skip_instance && entry_ty == skip_ty { continue; @@ -490,7 +564,7 @@ impl<'n, 'f> NtfsAttributes<'n, 'f> { if let Ok(NtfsAttributeType::AttributeList) = attribute.ty() { let attribute_list = iter_try!(attribute.structured_value::(fs)); - self.list_entries = Some(attribute_list.iter()); + self.list_entries = Some(attribute_list.entries()); } else { let item = NtfsAttributeItem { attribute_file: self.raw_iter.file, @@ -504,6 +578,15 @@ impl<'n, 'f> NtfsAttributes<'n, 'f> { } } +/// Iterator over +/// all attributes of an [`NtfsFile`], +/// returning an [`NtfsAttributeItem`] for each entry, +/// implementing [`Iterator`] and [`FusedIterator`]. +/// +/// This iterator is returned from the [`NtfsAttributes::attach`] function. +/// Conceptually the same as [`NtfsAttributes`], but mutably borrows the filesystem +/// to implement aforementioned traits. +#[derive(Debug)] pub struct NtfsAttributesAttached<'n, 'f, 'a, T: Read + Seek> { fs: &'a mut T, attributes: NtfsAttributes<'n, 'f>, @@ -517,6 +600,7 @@ where Self { fs, attributes } } + /// Consumes this iterator and returns the inner [`NtfsAttributes`]. pub fn detach(self) -> NtfsAttributes<'n, 'f> { self.attributes } @@ -535,6 +619,15 @@ where impl<'n, 'f, 'a, T> FusedIterator for NtfsAttributesAttached<'n, 'f, 'a, T> where T: Read + Seek {} +/// Item returned by the [`NtfsAttributes`] iterator. +/// +/// [`NtfsAttributes`] provides a flattened view over the attributes by traversing Attribute Lists. +/// Attribute Lists may contain entries with references to other [`NtfsFile`]s. +/// Therefore, the attribute's information may either be stored in the original [`NtfsFile`] or in another +/// [`NtfsFile`] that has been read just for this attribute. +/// +/// [`NtfsAttributeItem`] abstracts over both cases by providing a reference to the original [`NtfsFile`], +/// and optionally holding another [`NtfsFile`] if the attribute is actually stored there. #[derive(Clone, Debug)] pub struct NtfsAttributeItem<'n, 'f> { attribute_file: &'f NtfsFile<'n>, @@ -544,6 +637,7 @@ pub struct NtfsAttributeItem<'n, 'f> { } impl<'n, 'f> NtfsAttributeItem<'n, 'f> { + /// Returns the actual [`NtfsAttribute`] structure for this NTFS Attribute. pub fn to_attribute<'i>(&'i self) -> NtfsAttribute<'n, 'i> { if let Some(file) = &self.attribute_value_file { NtfsAttribute::new(file, self.attribute_offset, self.list_entries.as_ref()) @@ -557,6 +651,17 @@ impl<'n, 'f> NtfsAttributeItem<'n, 'f> { } } +/// Iterator over +/// all top-level attributes of an [`NtfsFile`], +/// returning an [`NtfsAttribute`] for each entry, +/// implementing [`Iterator`] and [`FusedIterator`]. +/// +/// This iterator is returned from the [`NtfsFile::attributes_raw`] function. +/// Contrary to [`NtfsAttributes`], it does not traverse $ATTRIBUTE_LIST attributes and returns them +/// as raw [`NtfsAttribute`]s. +/// Check that structure if you want an iterator providing a flattened "data-centric" view over +/// the attributes by traversing Attribute Lists automatically. +#[derive(Clone, Debug)] pub struct NtfsAttributesRaw<'n, 'f> { file: &'f NtfsFile<'n>, items_range: Range, @@ -565,7 +670,7 @@ pub struct NtfsAttributesRaw<'n, 'f> { impl<'n, 'f> NtfsAttributesRaw<'n, 'f> { pub(crate) fn new(file: &'f NtfsFile<'n>) -> Self { let start = file.first_attribute_offset() as usize; - let end = file.used_size() as usize; + let end = file.data_size() as usize; let items_range = start..end; Self { file, items_range } diff --git a/src/attribute_value/attribute_list_non_resident.rs b/src/attribute_value/attribute_list_non_resident.rs index cbf0636..d736deb 100644 --- a/src/attribute_value/attribute_list_non_resident.rs +++ b/src/attribute_value/attribute_list_non_resident.rs @@ -1,14 +1,10 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later // -//! This module implements a reader for a non-resident attribute value that is part of an AttributeList. -//! Such values are not only split up into data runs, but may also be continued by connected attributes which are listed in the same AttributeList. -//! This reader provides one contiguous data stream for all data runs in all connected attributes. -// // It is important to note that `NtfsAttributeListNonResidentAttributeValue` can't just encapsulate `NtfsNonResidentAttributeValue` and provide one // layer on top to connect the attributes! // Connected attributes are stored in a way that the first attribute reports the entire data size and all further attributes report a zero value length. -// We have to go down to the data run level to get trustable lengths again, and this is what `NtfsAttributeListNonResidentAttributeValue` does here. +// We have to go down to the Data Run level to get trustable lengths again, and this is what `NtfsAttributeListNonResidentAttributeValue` does here. use super::{DataRunsState, NtfsDataRuns, StreamState}; use crate::attribute::{NtfsAttribute, NtfsAttributeType}; @@ -19,19 +15,24 @@ use crate::structured_values::{NtfsAttributeListEntries, NtfsAttributeListEntry} use crate::traits::NtfsReadSeek; use binread::io::{Read, Seek, SeekFrom}; +/// Reader for a non-resident attribute value that is part of an Attribute List. +/// +/// Such values are not only split up into data runs, but may also be continued by connected attributes +/// which are listed in the same Attribute List. +/// This reader considers that by providing one contiguous data stream for all data runs in all connected attributes. #[derive(Clone, Debug)] pub struct NtfsAttributeListNonResidentAttributeValue<'n, 'f> { /// Reference to the base `Ntfs` object of this filesystem. ntfs: &'n Ntfs, /// An untouched copy of the `attribute_list_entries` passed in [`Self::new`] to rewind to the beginning when desired. initial_attribute_list_entries: NtfsAttributeListEntries<'n, 'f>, - /// Iterator through all connected attributes of this attribute in the AttributeList. + /// Iterator through all connected attributes of this attribute in the Attribute List. connected_entries: AttributeListConnectedEntries<'n, 'f>, /// Total length of the value data, in bytes. data_size: u64, /// File, location, and data runs iteration state of the current attribute. attribute_state: Option>, - /// Iteration state of the current data run. + /// Iteration state of the current Data Run. stream_state: StreamState, } @@ -59,16 +60,17 @@ impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { /// 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 + /// * The current Data Run is a "sparse" Data Run. pub fn data_position(&self) -> Option { self.stream_state.data_position() } + /// Returns the total length of the non-resident attribute value data, in bytes. pub fn len(&self) -> u64 { self.data_size } - /// Returns whether we got another data run. + /// Advances to the next Data Run and returns whether we got another Data Run. fn next_data_run(&mut self) -> Result { // Do we have a file and a (non-resident) attribute to iterate through its data runs? let attribute_state = match &mut self.attribute_state { @@ -92,7 +94,7 @@ impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { let mut stream_data_runs = NtfsDataRuns::from_state(self.ntfs, data, position, data_runs_state); - // Do we have a next data run? Save that. + // Do we have a next Data Run? Save that. let stream_data_run = match stream_data_runs.next() { Some(stream_data_run) => stream_data_run, None => return Ok(false), @@ -100,14 +102,14 @@ impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { let stream_data_run = stream_data_run?; self.stream_state.set_stream_data_run(stream_data_run); - // We got another data run, so serialize the updated `NtfsDataRuns` state for the next iteration. - // This step is skipped when we got no data run, because it means we have fully iterated this iterator (and hence also the attribute and file). + // We got another Data Run, so serialize the updated `NtfsDataRuns` state for the next iteration. + // This step is skipped when we got no Data Run, because it means we have fully iterated this iterator (and hence also the attribute and file). attribute_state.data_runs_state = Some(stream_data_runs.into_state()); Ok(true) } - /// Returns whether we got another connected attribute. + /// Advances to the next attribute and returns whether we got another connected attribute. fn next_attribute(&mut self, fs: &mut T) -> Result where T: Read + Seek, @@ -118,7 +120,7 @@ impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { None => return Ok(false), }; - // Read the correspoding FILE record into an `NtfsFile` and get the corresponding `NtfsAttribute`. + // Read the correspoding File Record into an `NtfsFile` and get the corresponding `NtfsAttribute`. let entry = entry?; let file = entry.to_file(self.ntfs, fs)?; let attribute = entry.to_attribute(&file)?; @@ -135,7 +137,7 @@ impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { let (data, position) = attribute.non_resident_value_data_and_position(); let mut stream_data_runs = NtfsDataRuns::new(self.ntfs, data, position); - // Get the first data run already here to save time and let `data_position` return something meaningful. + // Get the first Data Run already here to save time and let `data_position` return something meaningful. let stream_data_run = match stream_data_runs.next() { Some(stream_data_run) => stream_data_run, None => return Ok(false), @@ -154,6 +156,7 @@ impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { Ok(true) } + /// Returns the [`Ntfs`] object reference associated to this value. pub fn ntfs(&self) -> &'n Ntfs { self.ntfs } @@ -167,19 +170,19 @@ impl<'n, 'f> NtfsReadSeek for NtfsAttributeListNonResidentAttributeValue<'n, 'f> let mut bytes_read = 0usize; while bytes_read < buf.len() { - // Read from the current data run if there is one. + // Read from the current Data Run if there is one. if self.stream_state.read_data_run(fs, buf, &mut bytes_read)? { // We read something, so check the loop condition again if we need to read more. continue; } - // Move to the next data run of the current attribute. + // Move to the next Data Run of the current attribute. if self.next_data_run()? { - // We got another data run of the current attribute, so read again. + // We got another Data Run of the current attribute, so read again. continue; } - // Move to the first data run of the next connected attribute. + // Move to the first Data Run of the next connected attribute. if self.next_attribute(fs)? { // We got another attribute, so read again. continue; @@ -212,7 +215,7 @@ impl<'n, 'f> NtfsReadSeek for NtfsAttributeListNonResidentAttributeValue<'n, 'f> }; while bytes_left_to_seek > 0 { - // Seek inside the current data run if there is one. + // Seek inside the current Data Run if there is one. if self .stream_state .seek_data_run(fs, pos, &mut bytes_left_to_seek)? @@ -221,13 +224,13 @@ impl<'n, 'f> NtfsReadSeek for NtfsAttributeListNonResidentAttributeValue<'n, 'f> break; } - // Move to the next data run of the current attribute. + // Move to the next Data Run of the current attribute. if self.next_data_run()? { - // We got another data run of the current attribute, so seek some more. + // We got another Data Run of the current attribute, so seek some more. continue; } - // Move to the first data run of the next connected attribute. + // Move to the first Data Run of the next connected attribute. if self.next_attribute(fs)? { // We got another connected attribute, so seek some more. continue; diff --git a/src/attribute_value/mod.rs b/src/attribute_value/mod.rs index f7f359f..e7d2a6e 100644 --- a/src/attribute_value/mod.rs +++ b/src/attribute_value/mod.rs @@ -1,5 +1,7 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +// +//! Readers for attribute value types. mod attribute_list_non_resident; mod non_resident; @@ -15,14 +17,22 @@ use binread::io::{Read, Seek, SeekFrom}; use crate::error::{NtfsError, Result}; use crate::traits::NtfsReadSeek; +/// Reader that abstracts over all attribute value types, returned by [`NtfsAttribute::value`]. +/// +/// [`NtfsAttribute::value`]: crate::NtfsAttribute::value #[derive(Clone, Debug)] pub enum NtfsAttributeValue<'n, 'f> { + /// A resident attribute value (which is entirely contained in the NTFS File Record). Resident(NtfsResidentAttributeValue<'f>), + /// A non-resident attribute value (whose data is in a cluster range outside the File Record). NonResident(NtfsNonResidentAttributeValue<'n, 'f>), + /// A non-resident attribute value that is part of an Attribute List (and may span multiple connected attributes). AttributeListNonResident(NtfsAttributeListNonResidentAttributeValue<'n, 'f>), } impl<'n, 'f> NtfsAttributeValue<'n, 'f> { + /// Returns a variant of this reader that implements [`Read`] and [`Seek`] + /// by mutably borrowing the filesystem reader. pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributeValueAttached<'n, 'f, 'a, T> where T: Read + Seek, @@ -30,6 +40,10 @@ impl<'n, 'f> NtfsAttributeValue<'n, 'f> { NtfsAttributeValueAttached::new(fs, self) } + /// 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 { match self { Self::Resident(inner) => inner.data_position(), @@ -38,6 +52,7 @@ impl<'n, 'f> NtfsAttributeValue<'n, 'f> { } } + /// Returns the total length of the attribute value data, in bytes. pub fn len(&self) -> u64 { match self { Self::Resident(inner) => inner.len(), @@ -79,6 +94,9 @@ impl<'n, 'f> NtfsReadSeek for NtfsAttributeValue<'n, 'f> { } } +/// A variant of [`NtfsAttributeValue`] that implements [`Read`] and [`Seek`] +/// by mutably borrowing the filesystem reader. +#[derive(Debug)] pub struct NtfsAttributeValueAttached<'n, 'f, 'a, T: Read + Seek> { fs: &'a mut T, value: NtfsAttributeValue<'n, 'f>, @@ -92,14 +110,20 @@ where Self { fs, value } } + /// 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 { self.value.data_position() } + /// Consumes this reader and returns the inner [`NtfsAttributeValue`]. pub fn detach(self) -> NtfsAttributeValue<'n, 'f> { self.value } + /// Returns the total length of the attribute value, in bytes. pub fn len(&self) -> u64 { self.value.len() } diff --git a/src/attribute_value/non_resident.rs b/src/attribute_value/non_resident.rs index 4639261..8e06053 100644 --- a/src/attribute_value/non_resident.rs +++ b/src/attribute_value/non_resident.rs @@ -1,7 +1,7 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later // -//! This module implements a reader for a non-resident attribute value (that is not part of an AttributeList). +//! This module implements a reader for a non-resident attribute value (that is not part of an Attribute List). //! Non-resident attribute values are split up into one or more data runs, which are spread across the filesystem. //! This reader provides one contiguous data stream for all data runs. @@ -20,17 +20,18 @@ use crate::ntfs::Ntfs; use crate::traits::NtfsReadSeek; use crate::types::{Lcn, Vcn}; +/// Reader for a non-resident attribute value (whose data is in a cluster range outside the File Record). #[derive(Clone, Debug)] pub struct NtfsNonResidentAttributeValue<'n, 'f> { /// Reference to the base `Ntfs` object of this filesystem. ntfs: &'n Ntfs, - /// Attribute bytes where the data run information of this non-resident value is stored on the filesystem. + /// 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. + /// Absolute position of the Data Run information within the filesystem, in bytes. position: u64, /// Iterator of data runs used for reading/seeking. stream_data_runs: NtfsDataRuns<'n, 'f>, - /// Iteration state of the current data run. + /// Iteration state of the current Data Run. stream_state: StreamState, } @@ -44,7 +45,7 @@ impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> { let mut stream_data_runs = NtfsDataRuns::new(ntfs, data, position); let mut stream_state = StreamState::new(data_size); - // Get the first data run already here to let `data_position` return something meaningful. + // Get the first Data Run already here to let `data_position` return something meaningful. if let Some(stream_data_run) = stream_data_runs.next() { let stream_data_run = stream_data_run?; stream_state.set_stream_data_run(stream_data_run); @@ -59,6 +60,8 @@ impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> { }) } + /// Returns a variant of this reader that implements [`Read`] and [`Seek`] + /// by mutably borrowing the filesystem reader. pub fn attach<'a, T>( self, fs: &'a mut T, @@ -72,20 +75,22 @@ impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> { /// 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 + /// * The current Data Run is a "sparse" Data Run pub fn data_position(&self) -> Option { self.stream_state.data_position() } + /// Returns an iterator over all data runs of this non-resident attribute. pub fn data_runs(&self) -> NtfsDataRuns<'n, 'f> { NtfsDataRuns::new(self.ntfs, self.data, self.position) } + /// Returns the total length of the non-resident attribute value data, in bytes. pub fn len(&self) -> u64 { self.stream_state.data_size() } - /// Returns whether we got another data run. + /// Returns whether we got another Data Run. fn next_data_run(&mut self) -> Result { let stream_data_run = match self.stream_data_runs.next() { Some(stream_data_run) => stream_data_run, @@ -97,11 +102,12 @@ impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> { Ok(true) } + /// Returns the [`Ntfs`] object reference associated to this value. pub fn ntfs(&self) -> &'n Ntfs { self.ntfs } - /// Returns the absolute position of the data run information within the filesystem, in bytes. + /// Returns the absolute position of the Data Run information within the filesystem, in bytes. pub fn position(&self) -> u64 { self.position } @@ -115,15 +121,15 @@ impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> { let mut bytes_read = 0usize; while bytes_read < buf.len() { - // Read from the current data run if there is one. + // Read from the current Data Run if there is one. if self.stream_state.read_data_run(fs, buf, &mut bytes_read)? { // We read something, so check the loop condition again if we need to read more. continue; } - // Move to the next data run. + // Move to the next Data Run. if self.next_data_run()? { - // We got another data run, so read again. + // We got another Data Run, so read again. continue; } else { // We read everything we could. @@ -152,7 +158,7 @@ impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> { }; while bytes_left_to_seek > 0 { - // Seek inside the current data run if there is one. + // Seek inside the current Data Run if there is one. if self .stream_state .seek_data_run(fs, pos, &mut bytes_left_to_seek)? @@ -161,9 +167,9 @@ impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> { break; } - // Move to the next data run. + // Move to the next Data Run. if self.next_data_run()? { - // We got another data run, so seek some more. + // We got another Data Run, so seek some more. continue; } else { // We seeked as far as we could. @@ -187,6 +193,9 @@ impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> { } } +/// A variant of [`NtfsNonResidentAttributeValue`] that implements [`Read`] and [`Seek`] +/// by mutably borrowing the filesystem reader. +#[derive(Debug)] pub struct NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T: Read + Seek> { fs: &'a mut T, value: NtfsNonResidentAttributeValue<'n, 'f>, @@ -200,14 +209,20 @@ where Self { fs, value } } + /// 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 { self.value.data_position() } + /// Consumes this reader and returns the inner [`NtfsNonResidentAttributeValue`]. pub fn detach(self) -> NtfsNonResidentAttributeValue<'n, 'f> { self.value } + /// Returns the total length of the attribute value, in bytes. pub fn len(&self) -> u64 { self.value.len() } @@ -231,6 +246,12 @@ where } } +/// Iterator over +/// all data runs of a non-resident attribute, +/// returning an [`NtfsDataRun`] for each entry, +/// implementing [`Iterator`] and [`FusedIterator`]. +/// +/// This iterator is returned from the [`NtfsNonResidentAttributeValue::data_runs`] function. #[derive(Clone, Debug)] pub struct NtfsDataRuns<'n, 'f> { ntfs: &'n Ntfs, @@ -272,6 +293,7 @@ impl<'n, 'f> NtfsDataRuns<'n, 'f> { self.state } + /// Returns the absolute position of the current Data Run header within the filesystem, in bytes. pub fn position(&self) -> u64 { self.position + self.state.offset as u64 } @@ -383,20 +405,20 @@ pub(crate) struct DataRunsState { previous_lcn: Lcn, } -/// Describes a single NTFS data run, which is a continuous cluster range of a non-resident value. +/// A single NTFS Data Run, which is a continuous cluster range of a non-resident value. /// -/// A data run's size is a multiple of the cluster size configured for the filesystem. -/// However, a data run does not know about the actual size used by data. This information is only available in the corresponding attribute. +/// A Data Run's size is a multiple of the cluster size configured for the filesystem. +/// However, a Data Run does not know about the actual size used by data. This information is only available in the corresponding attribute. /// Keep this in mind when doing reads and seeks on data runs. You may end up on allocated but unused data. #[derive(Clone, Debug)] pub struct NtfsDataRun { - /// Absolute position of the data run within the filesystem, in bytes. - /// This may be zero if this is a "sparse" data run. + /// Absolute position of the Data Run within the filesystem, in bytes. + /// This may be zero if this is a "sparse" Data Run. position: u64, - /// Total allocated size of the data run, in bytes. - /// The actual size used by data may be lower, but a data run does not know about that. + /// Total allocated size of the Data Run, in bytes. + /// The actual size used by data may be lower, but a Data Run does not know about that. allocated_size: u64, - /// Current relative position within the data run value, in bytes. + /// Current relative position within the Data Run value, in bytes. stream_position: u64, } @@ -417,7 +439,7 @@ impl NtfsDataRun { /// 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 + /// * The Data Run is a "sparse" Data Run pub fn data_position(&self) -> Option { if self.position > 0 && self.stream_position < self.len() { Some(self.position + self.stream_position) @@ -426,6 +448,7 @@ impl NtfsDataRun { } } + /// Returns the allocated size of the Data Run, in bytes. pub fn len(&self) -> u64 { self.allocated_size } @@ -448,10 +471,10 @@ impl NtfsReadSeek for NtfsDataRun { let work_slice = &mut buf[..bytes_to_read]; if self.position == 0 { - // This is a sparse data run. + // This is a sparse Data Run. work_slice.fill(0); } else { - // This data run contains "real" data. + // This Data Run contains "real" data. // 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)?; @@ -476,7 +499,7 @@ impl NtfsReadSeek for NtfsDataRun { #[derive(Clone, Debug)] pub(crate) struct StreamState { - /// Current data run we are reading from. + /// Current Data Run we are reading from. stream_data_run: Option, /// Current relative position within the entire value, in bytes. stream_position: u64, @@ -496,7 +519,7 @@ impl StreamState { /// 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 + /// * The current Data Run is a "sparse" Data Run pub(crate) fn data_position(&self) -> Option { let stream_data_run = self.stream_data_run.as_ref()?; stream_data_run.data_position() @@ -524,7 +547,7 @@ impl StreamState { } /// Simplifies any [`SeekFrom`] to the two cases [`SeekFrom::Start(n)`] and [`SeekFrom::Current(n)`], with n >= 0. - /// This is necessary, because an NTFS data run has necessary information for the next data run, but not the other way round. + /// This is necessary, because an NTFS Data Run has necessary information for the next Data Run, but not the other way round. /// Hence, we can't efficiently move backwards. fn simplify_seek(&self, pos: SeekFrom, data_size: u64) -> Result { match pos { @@ -580,19 +603,19 @@ impl StreamState { where T: Read + Seek, { - // Is there a data run to read from? + // Is there a Data Run to read from? let data_run = match &mut self.stream_data_run { Some(data_run) => data_run, None => return Ok(false), }; - // Have we already seeked past the size of the data run? + // Have we already seeked past the size of the Data Run? if data_run.stream_position() >= data_run.len() { return Ok(false); } // We also must not read past the (used) data size of the entire value. - // (remember that a data run only knows about its allocated size, not its used size!) + // (remember that a Data Run only knows about its allocated size, not its used size!) let remaining_data_size = self.data_size.saturating_sub(self.stream_position); if remaining_data_size == 0 { return Ok(false); @@ -611,9 +634,9 @@ impl StreamState { Ok(true) } - /// Returns whether we have reached the final seek position within this data run and can therefore stop seeking. + /// Returns whether we have reached the final seek position within this Data Run and can therefore stop seeking. /// - /// In all other cases, the caller should move to the next data run and seek again. + /// In all other cases, the caller should move to the next Data Run and seek again. pub(crate) fn seek_data_run( &mut self, fs: &mut T, @@ -623,17 +646,17 @@ impl StreamState { where T: Read + Seek, { - // Is there a data run to seek in? + // Is there a Data Run to seek in? let data_run = match &mut self.stream_data_run { Some(data_run) => data_run, None => return Ok(false), }; if *bytes_left_to_seek < data_run.remaining_len() { - // We have found the right data run, now we have to seek inside the data run. + // We have found the right Data Run, now we have to seek inside the Data Run. // // If we were called to seek from the very beginning, we can be sure that this - // data run is also seeked from the beginning. + // Data Run is also seeked from the beginning. // Hence, we can use SeekFrom::Start and use the full u64 range. // // If we were called to seek from the current position, we have to use @@ -649,7 +672,7 @@ impl StreamState { data_run.seek(fs, pos)?; Ok(true) } else { - // We can skip the entire data run. + // We can skip the entire Data Run. *bytes_left_to_seek -= data_run.remaining_len(); Ok(false) } diff --git a/src/attribute_value/resident.rs b/src/attribute_value/resident.rs index a81fb8f..5cd47ac 100644 --- a/src/attribute_value/resident.rs +++ b/src/attribute_value/resident.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // //! This module implements a reader for a value that is already in memory and can therefore be accessed via a slice. -//! This is the case for all resident attribute values and index record values. +//! This is the case for all resident attribute values and Index Record values. //! Such values are part of NTFS records. NTFS records can't be directly read from the filesystem, which is why they //! are always read into a buffer first and then fixed up in memory. //! Further accesses to the record data can then happen via slices. @@ -13,6 +13,7 @@ use super::seek_contiguous; use crate::error::Result; use crate::traits::NtfsReadSeek; +/// Reader for a value of a resident NTFS Attribute (which is entirely contained in the NTFS File Record). #[derive(Clone, Debug)] pub struct NtfsResidentAttributeValue<'f> { data: &'f [u8], @@ -29,6 +30,12 @@ impl<'f> NtfsResidentAttributeValue<'f> { } } + /// Returns a slice of the entire value data. + /// + /// Remember that a resident attribute fits entirely inside the NTFS File Record + /// of the requested file. + /// Hence, the fixed up File Record is entirely in memory at this stage and a slice + /// to a resident attribute value can be obtained easily. pub fn data(&self) -> &'f [u8] { self.data } @@ -43,6 +50,7 @@ impl<'f> NtfsResidentAttributeValue<'f> { } } + /// Returns the total length of the resident attribute value data, in bytes. pub fn len(&self) -> u64 { self.data.len() as u64 } diff --git a/src/error.rs b/src/error.rs index 5bb202d..92d1dd7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,7 +18,7 @@ pub enum NtfsError { position: u64, ty: NtfsAttributeType, }, - /// The NTFS attribute at byte position {position:#010x} should have type {expected:?}, but it actually has type {actual:?} + /// The NTFS Attribute at byte position {position:#010x} should have type {expected:?}, but it actually has type {actual:?} AttributeOfDifferentType { position: u64, expected: NtfsAttributeType, @@ -26,19 +26,19 @@ pub enum NtfsError { }, /// The given buffer should have at least {expected} bytes, but it only has {actual} bytes BufferTooSmall { expected: usize, actual: usize }, - /// 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 + /// 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 InvalidAttributeNameLength { position: u64, expected: usize, actual: u32, }, - /// 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 + /// 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 InvalidAttributeNameOffset { position: u64, expected: u16, actual: u32, }, - /// The NTFS data run header at byte position {position:#010x} indicates a maximum byte count of {expected}, but {actual} is the limit + /// 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, @@ -46,39 +46,39 @@ pub enum NtfsError { }, /// The cluster count {cluster_count} is too big InvalidClusterCount { cluster_count: u64 }, - /// 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 + /// 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 InvalidFileAllocatedSize { position: u64, expected: u32, actual: u32, }, - /// The requested NTFS file record number {file_record_number} is invalid + /// The requested NTFS File Record Number {file_record_number} is invalid InvalidFileRecordNumber { file_record_number: u64 }, - /// The NTFS file record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} + /// The NTFS File Record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} InvalidFileSignature { position: u64, expected: &'static [u8], actual: [u8; 4], }, - /// The NTFS file record at byte position {position:#010x} indicates a used size of {expected} bytes, but only {actual} bytes are allocated + /// The NTFS File Record at byte position {position:#010x} indicates a used size of {expected} bytes, but only {actual} bytes are allocated InvalidFileUsedSize { position: u64, expected: u32, actual: u32, }, - /// The NTFS index record at byte position {position:#010x} indicates an allocated size of {expected} bytes, but the record only has a size of {actual} bytes + /// The NTFS Index Record at byte position {position:#010x} indicates an allocated size of {expected} bytes, but the record only has a size of {actual} bytes InvalidIndexAllocatedSize { position: u64, expected: u32, actual: u32, }, - /// The NTFS index entry at byte position {position:#010x} references a data field in the range {range:?}, but the entry only has a size of {size} bytes + /// The NTFS Index Entry at byte position {position:#010x} references a data field in the range {range:?}, but the entry only has a size of {size} bytes InvalidIndexEntryDataRange { position: u64, range: Range, size: u16, }, - /// The NTFS index entry at byte position {position:#010x} reports a size of {expected} bytes, but it only has {actual} bytes + /// The NTFS Index Entry at byte position {position:#010x} reports a size of {expected} bytes, but it only has {actual} bytes InvalidIndexEntrySize { position: u64, expected: u16, @@ -96,25 +96,25 @@ pub enum NtfsError { expected: usize, actual: usize, }, - /// The NTFS index record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} + /// The NTFS Index Record at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} InvalidIndexSignature { position: u64, expected: &'static [u8], actual: [u8; 4], }, - /// The NTFS index record at byte position {position:#010x} indicates a used size of {expected} bytes, but only {actual} bytes are allocated + /// The NTFS Index Record at byte position {position:#010x} indicates a used size of {expected} bytes, but only {actual} bytes are allocated InvalidIndexUsedSize { position: u64, expected: u32, 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 + /// 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 InvalidResidentAttributeValueLength { 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 + /// 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 InvalidResidentAttributeValueOffset { position: u64, expected: u16, @@ -139,7 +139,7 @@ pub enum NtfsError { }, /// The Upcase Table should have a size of {expected} bytes, but it has {actual} bytes InvalidUpcaseTableSize { expected: u64, actual: u64 }, - /// The VCN {vcn} read from the NTFS data run header at byte position {position:#010x} cannot be added to the LCN {previous_lcn} calculated from previous data runs + /// The VCN {vcn} read from the NTFS Data Run header at byte position {position:#010x} cannot be added to the LCN {previous_lcn} calculated from previous data runs InvalidVcnInDataRunHeader { position: u64, vcn: Vcn, @@ -153,13 +153,13 @@ pub enum NtfsError { 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} should not belong to an Attribute List, but it does + /// The NTFS Attribute at byte position {position:#010x} should not belong to an Attribute List, but it does UnexpectedAttributeListAttribute { position: u64 }, - /// The NTFS attribute at byte position {position:#010x} should be resident, but it is non-resident + /// 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 + /// The NTFS Attribute at byte position {position:#010x} should be non-resident, but it is resident UnexpectedResidentAttribute { position: u64 }, - /// The type of the NTFS attribute at byte position {position:#010x} is {actual:#010x}, which is not supported + /// The type of the NTFS Attribute at byte position {position:#010x} is {actual:#010x}, which is not supported UnsupportedAttributeType { position: u64, actual: u32 }, /// The cluster size is {actual} bytes, but the maximum supported one is {expected} UnsupportedClusterSize { expected: u32, actual: u32 }, @@ -220,4 +220,5 @@ impl From for binread::io::Error { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for NtfsError {} diff --git a/src/file.rs b/src/file.rs index 378671e..4b015c1 100644 --- a/src/file.rs +++ b/src/file.rs @@ -17,19 +17,57 @@ use bitflags::bitflags; use byteorder::{ByteOrder, LittleEndian}; use memoffset::offset_of; +/// A list of standardized NTFS File Record Numbers. +/// +/// Most of these files store internal NTFS housekeeping information. +/// +/// Reference: #[repr(u64)] pub enum KnownNtfsFileRecordNumber { + /// A back-reference to the Master File Table (MFT). + /// + /// Leads to the same File Record as [`Ntfs::mft_position`]. MFT = 0, + /// A mirror copy of the Master File Table (MFT). MFTMirr = 1, + /// The journaling logfile. + /// + /// Reference: LogFile = 2, + /// File containing basic filesystem information and the user-defined volume name. + /// + /// You can easily access that information via [`Ntfs::volume_info`] and [`Ntfs::volume_name`]. Volume = 3, + /// File defining all attributes supported by this NTFS filesystem. + /// + /// Reference: AttrDef = 4, + /// The root directory of the filesystem. + /// + /// You can easily access it via [`Ntfs::root_directory`]. RootDirectory = 5, + /// Map of used clusters. + /// + /// Reference: Bitmap = 6, + /// A back-reference to the boot sector of the filesystem. Boot = 7, + /// A file consisting of Data Runs to bad cluster ranges. + /// + /// Reference: BadClus = 8, + /// A list of all Security Descriptors used by this filesystem. + /// + /// Reference: Secure = 9, + /// The $UpCase file that contains a table of all uppercase characters for the + /// 65536 characters of the Unicode Basic Multilingual Plane. + /// + /// NTFS uses this table to perform case-insensitive comparisons. UpCase = 10, + /// A directory of further files containing housekeeping information. + /// + /// Reference: Extend = 11, } @@ -40,13 +78,14 @@ struct FileRecordHeader { hard_link_count: u16, first_attribute_offset: u16, flags: u16, - used_size: u32, + data_size: u32, allocated_size: u32, base_file_record: NtfsFileReference, next_attribute_instance: u16, } bitflags! { + /// Flags returned by [`NtfsFile::flags`]. pub struct NtfsFileFlags: u16 { /// Record is in use. const IN_USE = 0x0001; @@ -55,6 +94,17 @@ bitflags! { } } +/// A single NTFS File Record. +/// +/// These records are denoted via a `FILE` signature on the filesystem. +/// +/// NTFS uses File Records to manage all user-facing files and directories, as well as some internal files for housekeeping. +/// Every File Record consists of [`NtfsAttribute`]s, which may reference additional File Records. +/// Even the Master File Table (MFT) itself is organized as a File Record. +/// +/// Reference: +/// +/// [`NtfsAttribute`]: crate::attribute::NtfsAttribute #[derive(Clone, Debug)] pub struct NtfsFile<'n> { record: Record<'n>, @@ -88,18 +138,35 @@ impl<'n> NtfsFile<'n> { Ok(file) } + /// Returns the allocated size of this NTFS File Record, in bytes. pub fn allocated_size(&self) -> u32 { let start = offset_of!(FileRecordHeader, allocated_size); LittleEndian::read_u32(&self.record.data()[start..]) } + /// Returns an iterator over all attributes of this file. + /// /// This provides a flattened "data-centric" view of the attributes and abstracts away the filesystem details - /// to deal with many or large attributes (Attribute Lists and split attributes). + /// to deal with many or large attributes (Attribute Lists and connected attributes). /// Use [`NtfsFile::attributes_raw`] to iterate over the plain attributes on the filesystem. + /// + /// Due to the abstraction, the iterator returns an [`NtfsAttributeItem`] for each entry. + /// + /// [`NtfsAttributeItem`]: crate::NtfsAttributeItem pub fn attributes<'f>(&'f self) -> NtfsAttributes<'n, 'f> { NtfsAttributes::<'n, 'f>::new(self) } + /// Returns an iterator over all top-level attributes of this file. + /// + /// Contrary to [`NtfsFile::attributes`], it does not traverse $ATTRIBUTE_LIST attributes, but returns + /// them as raw attributes. + /// Check that function if you want an iterator providing a flattened "data-centric" view over + /// the attributes by traversing Attribute Lists automatically. + /// + /// The iterator returns an [`NtfsAttribute`] for each entry. + /// + /// [`NtfsAttribute`]: crate::NtfsAttribute pub fn attributes_raw<'f>(&'f self) -> NtfsAttributesRaw<'n, 'f> { NtfsAttributesRaw::new(self) } @@ -142,7 +209,16 @@ impl<'n> NtfsFile<'n> { None } + /// Returns the size actually used by data of this NTFS File Record, in bytes. + /// + /// This is less or equal than [`NtfsFile::allocated_size`]. + pub fn data_size(&self) -> u32 { + let start = offset_of!(FileRecordHeader, data_size); + LittleEndian::read_u32(&self.record.data()[start..]) + } + /// Convenience function to return an [`NtfsIndex`] if this file is a directory. + /// This structure can be used to iterate over all files of this directory or a find a specific one. /// /// Apart from any propagated error, this function may return [`NtfsError::NotADirectory`] /// if this [`NtfsFile`] is not a directory. @@ -162,7 +238,7 @@ impl<'n> NtfsFile<'n> { }); } - // A FILE record may contain multiple indexes, so we have to match the name of the directory index. + // A File Record may contain multiple indexes, so we have to match the name of the directory index. let directory_index_name = "$I30"; // The IndexRoot attribute is always resident and has to exist for every directory. @@ -171,7 +247,7 @@ impl<'n> NtfsFile<'n> { ))?; // The IndexAllocation attribute is only required for "large" indexes. - // It is always non-resident and may even be in an AttributeList. + // It is always non-resident and may even be in an Attribute List. let mut index_allocation_item = None; if index_root.is_large_index() { let mut iter = self.attributes(); @@ -198,7 +274,7 @@ impl<'n> NtfsFile<'n> { NtfsIndex::::new(index_root, index_allocation_item) } - /// Returns the NTFS file record number of this file. + /// Returns the NTFS File Record Number of this file. /// /// This number uniquely identifies this file and can be used to recreate this [`NtfsFile`] /// object via [`Ntfs::file`]. @@ -244,12 +320,13 @@ impl<'n> NtfsFile<'n> { LittleEndian::read_u16(&self.record.data()[start..]) } - /// Returns flags set for this NTFS file as specified by [`NtfsFileFlags`]. + /// Returns flags set for this file as specified by [`NtfsFileFlags`]. pub fn flags(&self) -> NtfsFileFlags { let start = offset_of!(FileRecordHeader, flags); NtfsFileFlags::from_bits_truncate(LittleEndian::read_u16(&self.record.data()[start..])) } + /// Returns the number of hard links to this NTFS File Record. pub fn hard_link_count(&self) -> u16 { let start = offset_of!(FileRecordHeader, hard_link_count); LittleEndian::read_u16(&self.record.data()[start..]) @@ -264,6 +341,7 @@ impl<'n> NtfsFile<'n> { self.find_resident_attribute_structured_value::(None) } + /// Returns whether this NTFS File Record represents a directory. pub fn is_directory(&self) -> bool { self.flags().contains(NtfsFileFlags::IS_DIRECTORY) } @@ -318,12 +396,12 @@ impl<'n> NtfsFile<'n> { None } - /// Returns the [`Ntfs`] object associated to this file. + /// Returns the [`Ntfs`] object reference associated to this file. pub fn ntfs(&self) -> &'n Ntfs { self.record.ntfs() } - /// Returns the absolute byte position of this file record in the NTFS filesystem. + /// Returns the absolute byte position of this File Record in the NTFS filesystem. pub fn position(&self) -> u64 { self.record.position() } @@ -332,16 +410,16 @@ impl<'n> NtfsFile<'n> { self.record.data() } + /// Returns the sequence number of this file. + /// + /// NTFS reuses records of deleted files when new files are created. + /// This number is incremented every time a file is deleted. + /// Hence, it gives a count how many time this File Record has been reused. pub fn sequence_number(&self) -> u16 { let start = offset_of!(FileRecordHeader, sequence_number); LittleEndian::read_u16(&self.record.data()[start..]) } - pub fn used_size(&self) -> u32 { - let start = offset_of!(FileRecordHeader, used_size); - LittleEndian::read_u32(&self.record.data()[start..]) - } - fn validate_signature(record: &Record) -> Result<()> { let signature = &record.signature(); let expected = b"FILE"; @@ -366,10 +444,10 @@ impl<'n> NtfsFile<'n> { }); } - if self.used_size() > self.allocated_size() { + if self.data_size() > self.allocated_size() { return Err(NtfsError::InvalidFileUsedSize { position: self.record.position(), - expected: self.used_size(), + expected: self.data_size(), actual: self.allocated_size(), }); } diff --git a/src/file_reference.rs b/src/file_reference.rs index 836bfbf..65b443f 100644 --- a/src/file_reference.rs +++ b/src/file_reference.rs @@ -7,6 +7,9 @@ use crate::ntfs::Ntfs; use binread::io::{Read, Seek}; use binread::BinRead; +/// Absolute reference to a File Record on the filesystem, composed out of a File Record Number and a Sequence Number. +/// +/// Reference: #[derive(BinRead, Clone, Copy, Debug)] pub struct NtfsFileReference([u8; 8]); @@ -15,10 +18,17 @@ impl NtfsFileReference { Self(file_reference_bytes) } + /// Returns the 48-bit File Record Number. + /// + /// This can be fed into [`Ntfs::file`] to create an [`NtfsFile`] object for the corresponding File Record + /// (if you cannot use [`Self::to_file`] for some reason). pub fn file_record_number(&self) -> u64 { u64::from_le_bytes(self.0) & 0xffff_ffff_ffff } + /// Returns the 16-bit sequence number of the File Record. + /// + /// In a consistent file system, this number matches what [`NtfsFile::sequence_number`] returns. pub fn sequence_number(&self) -> u16 { (u64::from_le_bytes(self.0) >> 48) as u16 } diff --git a/src/guid.rs b/src/guid.rs index 66ba657..ac2e152 100644 --- a/src/guid.rs +++ b/src/guid.rs @@ -7,6 +7,7 @@ use core::fmt; /// Size of a single GUID on disk (= size of all GUID fields). pub(crate) const GUID_SIZE: usize = 16; +/// A Globally Unique Identifier (GUID), used for Object IDs in NTFS. #[derive(BinRead, Clone, Debug, Eq, PartialEq)] pub struct NtfsGuid { pub data1: u32, diff --git a/src/index.rs b/src/index.rs index 91df495..0583323 100644 --- a/src/index.rs +++ b/src/index.rs @@ -13,6 +13,15 @@ use binread::io::{Read, Seek}; use core::cmp::Ordering; use core::marker::PhantomData; +/// Helper structure to iterate over all entries of an index or find a specific one. +/// +/// The `E` type parameter of [`NtfsIndexEntryType`] specifies the type of the index entries. +/// The most common one is [`NtfsFileNameIndex`] for file name indexes, commonly known as "directories". +/// Check out [`NtfsFile::directory_index`] to return an [`NtfsIndex`] object for a directory without +/// any hassles. +/// +/// [`NtfsFile::directory_index`]: crate::NtfsFile::directory_index +/// [`NtfsFileNameIndex`]: crate::indexes::NtfsFileNameIndex #[derive(Clone, Debug)] pub struct NtfsIndex<'n, 'f, E> where @@ -27,6 +36,14 @@ impl<'n, 'f, E> NtfsIndex<'n, 'f, E> where E: NtfsIndexEntryType, { + /// Creates a new [`NtfsIndex`] object from a previously looked up [`NtfsIndexRoot`] attribute + /// and, in case of a large index, a matching [`NtfsIndexAllocation`] attribute + /// (contained in an [`NtfsAttributeItem`]). + /// + /// If you just want to look up files in a directory, check out [`NtfsFile::directory_index`], + /// which looks up the correct [`NtfsIndexRoot`] and [`NtfsIndexAllocation`] attributes for you. + /// + /// [`NtfsFile::directory_index`]: crate::NtfsFile::directory_index pub fn new( index_root: NtfsIndexRoot<'f>, index_allocation_item: Option>, @@ -57,15 +74,15 @@ where }) } + /// Returns an [`NtfsIndexEntries`] iterator to perform an in-order traversal of this index. + pub fn entries<'i>(&'i self) -> NtfsIndexEntries<'n, 'f, 'i, E> { + NtfsIndexEntries::new(self) + } + /// Returns an [`NtfsIndexFinder`] structure to efficiently find an entry in this index. pub fn finder<'i>(&'i self) -> NtfsIndexFinder<'n, 'f, 'i, E> { NtfsIndexFinder::new(self) } - - /// Returns an [`NtfsIndexEntries`] iterator to perform an in-order traversal of this index. - pub fn iter<'i>(&'i self) -> NtfsIndexEntries<'n, 'f, 'i, E> { - NtfsIndexEntries::new(self) - } } /// Iterator over @@ -73,7 +90,7 @@ where /// sorted ascending by the index key, /// returning an [`NtfsIndexEntry`] for each entry. /// -/// See [`NtfsIndexEntriesAttached`] for an iterator that implements [`Iterator`] and [`FusedIterator`]. +/// This iterator is returned from the [`NtfsIndex::entries`] function. #[derive(Clone, Debug)] pub struct NtfsIndexEntries<'n, 'f, 'i, E> where @@ -99,6 +116,7 @@ where } } + /// See [`Iterator::next`]. pub fn next<'a, T>(&'a mut self, fs: &mut T) -> Option>> where T: Read + Seek, @@ -358,7 +376,7 @@ mod tests { dir_names.sort_unstable(); let subdir_index = subdir.directory_index(&mut testfs1).unwrap(); - let mut subdir_iter = subdir_index.iter(); + let mut subdir_iter = subdir_index.entries(); for dir_name in dir_names { let entry = subdir_iter.next(&mut testfs1).unwrap().unwrap(); diff --git a/src/index_entry.rs b/src/index_entry.rs index c9f0805..23fa786 100644 --- a/src/index_entry.rs +++ b/src/index_entry.rs @@ -39,10 +39,11 @@ struct IndexEntryHeader { } bitflags! { + /// Flags returned by [`NtfsIndexEntry::flags`]. pub struct NtfsIndexEntryFlags: u8 { - /// This index entry points to a sub-node. + /// This Index Entry points to a sub-node. const HAS_SUBNODE = 0x01; - /// This is the last index entry in the list. + /// This is the last Index Entry in the list. const LAST_ENTRY = 0x02; } } @@ -75,6 +76,23 @@ where } } +/// A single entry of an NTFS index. +/// +/// NTFS uses B-tree indexes to quickly look up directories, Object IDs, Reparse Points, Security Descriptors, etc. +/// They are described via [`NtfsIndexRoot`] and [`NtfsIndexAllocation`] attributes, which can be comfortably +/// accessed via [`NtfsIndex`]. +/// +/// The `E` type parameter of [`NtfsIndexEntryType`] specifies the type of the Index Entry. +/// The most common one is [`NtfsFileNameIndex`] for file name indexes, commonly known as "directories". +/// Check out [`NtfsFile::directory_index`] to return an [`NtfsIndex`] object for a directory without +/// any hassles. +/// +/// Reference: +/// +/// [`NtfsFileNameIndex`]: crate::indexes::NtfsFileNameIndex +/// [`NtfsIndex`]: crate::NtfsIndex +/// [`NtfsIndexAllocation`]: crate::structured_values::NtfsIndexAllocation +/// [`NtfsIndexRoot`]: crate::structured_values::NtfsIndexRoot #[derive(Clone, Debug)] pub struct NtfsIndexEntry<'s, E> where @@ -103,6 +121,10 @@ where Ok(entry) } + /// Returns the data of this Index Entry, if any and if supported by this Index Entry type. + /// + /// This function is mutually exclusive with [`NtfsIndexEntry::file_reference`]. + /// An Index Entry can either have data or a file reference. pub fn data(&self) -> Option> where E: NtfsIndexEntryHasData, @@ -134,6 +156,7 @@ where LittleEndian::read_u16(&self.slice[start..]) } + /// Returns the length of the data of this Index Entry (if supported by this Index Entry type). pub fn data_length(&self) -> u16 where E: NtfsIndexEntryHasData, @@ -142,7 +165,11 @@ where LittleEndian::read_u16(&self.slice[start..]) } - /// Returns an [`NtfsFileReference`] for the file referenced by this index entry. + /// Returns an [`NtfsFileReference`] for the file referenced by this Index Entry + /// (if supported by this Index Entry type). + /// + /// This function is mutually exclusive with [`NtfsIndexEntry::data`]. + /// An Index Entry can either have data or a file reference. pub fn file_reference(&self) -> NtfsFileReference where E: NtfsIndexEntryHasFileReference, @@ -152,11 +179,17 @@ where NtfsFileReference::new(self.slice[..mem::size_of::()].try_into().unwrap()) } + /// Returns flags set for this attribute as specified by [`NtfsIndexEntryFlags`]. pub fn flags(&self) -> NtfsIndexEntryFlags { let flags = self.slice[offset_of!(IndexEntryHeader, flags)]; NtfsIndexEntryFlags::from_bits_truncate(flags) } + /// Returns the total length of this Index Entry, in bytes. + /// + /// The next Index Entry is exactly at [`NtfsIndexEntry::position`] + [`NtfsIndexEntry::index_entry_length`] + /// on the filesystem, unless this is the last entry ([`NtfsIndexEntry::flags`] contains + /// [`NtfsIndexEntryFlags::LAST_ENTRY`]). pub fn index_entry_length(&self) -> u16 { let start = offset_of!(IndexEntryHeader, index_entry_length); LittleEndian::read_u16(&self.slice[start..]) @@ -164,10 +197,11 @@ where /// 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(&self) -> Option> { // The key/stream is only set when the last entry flag is not set. - // https://flatcap.org/linux-ntfs/ntfs/concepts/index_entry.html + // https://flatcap.github.io/linux-ntfs/ntfs/concepts/index_entry.html if self.key_length() == 0 || self.flags().contains(NtfsIndexEntryFlags::LAST_ENTRY) { return None; } @@ -187,11 +221,17 @@ where Some(Ok(key)) } + /// Returns the length of the key of this Index Entry. pub fn key_length(&self) -> u16 { let start = offset_of!(IndexEntryHeader, key_length); LittleEndian::read_u16(&self.slice[start..]) } + /// Returns the absolute position of this NTFS Index Entry within the filesystem, in bytes. + pub fn position(&self) -> u64 { + self.position + } + /// Returns the Virtual Cluster Number (VCN) of the subnode of this Index Entry, /// or `None` if this Index Entry has no subnode. pub fn subnode_vcn(&self) -> Option> { @@ -217,7 +257,7 @@ where Some(Ok(vcn)) } - /// Returns an [`NtfsFile`] for the file referenced by this index entry. + /// 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, @@ -313,6 +353,22 @@ where impl FusedIterator for IndexNodeEntryRanges where E: NtfsIndexEntryType {} +/// Iterator over +/// all index entries of a single index node, +/// sorted ascending by the index key, +/// returning an [`NtfsIndexEntry`] for each entry. +/// +/// An index node can be an [`NtfsIndexRoot`] attribute or an [`NtfsIndexRecord`] +/// (which comes from an [`NtfsIndexAllocation`] attribute). +/// +/// As such, this iterator is returned from the [`NtfsIndexRoot::entries`] and +/// [`NtfsIndexRecord::entries`] functions. +/// +/// [`NtfsIndexAllocation`]: crate::structured_values::NtfsIndexAllocation +/// [`NtfsIndexRecord`]: crate::NtfsIndexRecord +/// [`NtfsIndexRecord::entries`]: crate::NtfsIndexRecord::entries +/// [`NtfsIndexRoot`]: crate::structured_values::NtfsIndexRoot +/// [`NtfsIndexRoot::entries`]: crate::structured_values::NtfsIndexRoot::entries #[derive(Clone, Debug)] pub struct NtfsIndexNodeEntries<'s, E> where diff --git a/src/index_record.rs b/src/index_record.rs index d73827d..016727a 100644 --- a/src/index_record.rs +++ b/src/index_record.rs @@ -35,6 +35,17 @@ pub(crate) struct IndexNodeHeader { pub(crate) flags: u8, } +/// A single NTFS Index Record. +/// +/// These records are denoted via an `INDX` signature on the filesystem. +/// +/// NTFS uses B-tree indexes to quickly look up directories, Object IDs, Reparse Points, Security Descriptors, etc. +/// An Index Record is further comprised of Index Entries, which contain the actual key/data (see [`NtfsIndexEntry`], +/// iterated via [`NtfsIndexNodeEntries`]). +/// +/// [`NtfsIndexEntry`]: crate::NtfsIndexEntry +/// +/// Reference: #[derive(Debug)] pub struct NtfsIndexRecord<'n> { record: Record<'n>, @@ -69,6 +80,9 @@ impl<'n> NtfsIndexRecord<'n> { Ok(index_record) } + /// Returns an iterator over all entries of this Index Record (cf. [`NtfsIndexEntry`]). + /// + /// [`NtfsIndexEntry`]: crate::NtfsIndexEntry pub fn entries<'r, E>(&'r self) -> Result> where E: NtfsIndexEntryType, @@ -81,7 +95,7 @@ impl<'n> NtfsIndexRecord<'n> { fn entries_range_and_position(&self) -> (Range, 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 end = INDEX_RECORD_HEADER_SIZE as usize + self.index_data_size() as usize; let position = self.record.position() + start as u64; (start..end, position) @@ -95,18 +109,20 @@ impl<'n> NtfsIndexRecord<'n> { (flags & HAS_SUBNODES_FLAG) != 0 } + /// Returns the allocated size of this NTFS Index Record, in bytes. pub fn index_allocated_size(&self) -> u32 { 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); + /// Returns the size actually used by index data within this NTFS Index Record, in bytes. + pub fn index_data_size(&self) -> u32 { + let start = INDEX_RECORD_HEADER_SIZE as usize + offset_of!(IndexNodeHeader, index_size); LittleEndian::read_u32(&self.record.data()[start..]) } - pub fn index_used_size(&self) -> u32 { - let start = INDEX_RECORD_HEADER_SIZE as usize + offset_of!(IndexNodeHeader, index_size); + 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..]) } @@ -136,7 +152,7 @@ impl<'n> NtfsIndexRecord<'n> { 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 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 { @@ -147,20 +163,26 @@ impl<'n> NtfsIndexRecord<'n> { }); } - // Furthermore, the total used size for this index record must not be + // 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_used_size(); - if total_used_size > total_allocated_size { + let total_data_size = INDEX_RECORD_HEADER_SIZE + self.index_data_size(); + if total_data_size > total_allocated_size { return Err(NtfsError::InvalidIndexUsedSize { position: self.record.position(), expected: total_allocated_size, - actual: total_used_size, + actual: total_data_size, }); } Ok(()) } + /// Returns the Virtual Cluster Number (VCN) of this Index Record, as reported by the header of this Index Record. + /// + /// This can be used to double-check that an Index Record is the actually requested one. + /// [`NtfsIndexAllocation::record_from_vcn`] uses it for that purpose. + /// + /// [`NtfsIndexAllocation::record_from_vcn`]: crate::structured_values::NtfsIndexAllocation::record_from_vcn pub fn vcn(&self) -> Vcn { let start = offset_of!(IndexRecordHeader, vcn); Vcn::from(LittleEndian::read_i64(&self.record.data()[start..])) diff --git a/src/indexes/file_name.rs b/src/indexes/file_name.rs index 5f77bc7..dd8b1e3 100644 --- a/src/indexes/file_name.rs +++ b/src/indexes/file_name.rs @@ -10,7 +10,7 @@ use crate::string::UpcaseOrd; use crate::structured_values::NtfsFileName; use binread::io::{Read, Seek}; -/// Defines the [`NtfsIndexEntryType`] for filename indexes (more commonly known as "directories"). +/// Defines the [`NtfsIndexEntryType`] for filename indexes (commonly known as "directories"). #[derive(Debug)] pub struct NtfsFileNameIndex {} diff --git a/src/indexes/mod.rs b/src/indexes/mod.rs index ba3508c..2d80ff0 100644 --- a/src/indexes/mod.rs +++ b/src/indexes/mod.rs @@ -1,5 +1,18 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +// +//! Various types of NTFS indexes and traits to work with them. +//! +//! Thanks to Rust's typesystem, the traits make using the various types of NTFS indexes (and their distinct key +//! and data types) possible in a typesafe way. +//! +//! NTFS uses B-tree indexes to quickly look up directories, Object IDs, Reparse Points, Security Descriptors, etc. +//! They are described via [`NtfsIndexRoot`] and [`NtfsIndexAllocation`] attributes, which can be comfortably +//! accessed via [`NtfsIndex`]. +//! +//! [`NtfsIndex`]: crate::NtfsIndex +//! [`NtfsIndexAllocation`]: crate::structured_values::NtfsIndexAllocation +//! [`NtfsIndexRoot`]: crate::structured_values::NtfsIndexRoot mod file_name; @@ -8,24 +21,37 @@ pub use file_name::*; use crate::error::Result; use core::fmt; +/// Trait implemented by structures that describe Index Entry types. +/// +/// See also [`NtfsIndex`] and [`NtfsIndexEntry`], and [`NtfsFileNameIndex`] for the most popular Index Entry type. +/// +/// [`NtfsFileNameIndex`]: crate::indexes::NtfsFileNameIndex +/// [`NtfsIndex`]: crate::NtfsIndex +/// [`NtfsIndexEntry`]: crate::NtfsIndexEntry pub trait NtfsIndexEntryType: fmt::Debug { type KeyType: NtfsIndexEntryKey; } +/// Trait implemented by a structure that describes an Index Entry key. pub trait NtfsIndexEntryKey: fmt::Debug + Sized { fn key_from_slice(slice: &[u8], position: u64) -> Result; } -/// Indicates that the index entry type has additional data. -// This would benefit from negative trait bounds, as this trait and `NtfsIndexEntryHasFileReference` are mutually exclusive! +/// Indicates that the Index Entry type has additional data (of [`NtfsIndexEntryData`] datatype). +/// +/// This trait and [`NtfsIndexEntryHasFileReference`] are mutually exclusive. +// TODO: Use negative trait bounds of future Rust to enforce mutual exclusion. pub trait NtfsIndexEntryHasData: NtfsIndexEntryType { type DataType: NtfsIndexEntryData; } +/// Trait implemented by a structure that describes Index Entry data. pub trait NtfsIndexEntryData: fmt::Debug + Sized { fn data_from_slice(slice: &[u8], position: u64) -> Result; } -/// Indicates that the index entry type has a file reference. -// This would benefit from negative trait bounds, as this trait and `NtfsIndexEntryHasData` are mutually exclusive! +/// Indicates that the Index Entry type has a file reference. +/// +/// This trait and [`NtfsIndexEntryHasData`] are mutually exclusive. +// TODO: Use negative trait bounds of future Rust to enforce mutual exclusion. pub trait NtfsIndexEntryHasFileReference: NtfsIndexEntryType {} diff --git a/src/lib.rs b/src/lib.rs index e32b5f0..c97cd8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,40 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +// +//! A low-level NTFS filesystem library implemented in Rust. +//! +//! [NTFS](https://en.wikipedia.org/wiki/NTFS) is the primary filesystem in all versions of Windows (since Windows NT 3.1 in 1993). +//! This crate is geared towards the NTFS 3.x versions used in Windows 2000 up to the current Windows 11. +//! However, the basics are expected to be compatible to even earlier versions. +//! +//! The crate is `no_std`-compatible and therefore usable from firmware level code up to user-mode applications. +//! +//! # Getting started +//! 1. Create an [`Ntfs`] structure from a reader by calling [`Ntfs::new`]. +//! 2. Retrieve the [`NtfsFile`] of the root directory via [`Ntfs::root_directory`]. +//! 3. Dig into its attributes via [`NtfsFile::attributes`], go even deeper via [`NtfsFile::attributes_raw`] or use one of the convenience functions, like [`NtfsFile::directory_index`], [`NtfsFile::info`] or [`NtfsFile::name`]. +//! +//! # Example +//! The following example dumps the names of all files and folders in the root directory of a given NTFS filesystem. +//! The list is directly taken from the NTFS index, hence it's sorted in ascending order with respect to NTFS's understanding of case-insensitive string comparison. +//! +//! ```rust,no_run +//! let mut ntfs = Ntfs::new(&mut fs).unwrap(); +//! let root_dir = ntfs.root_directory(&mut fs).unwrap(); +//! let index = root_dir.directory_index(&mut fs).unwrap(); +//! let mut iter = index.entries(); +//! +//! while let Some(entry) = iter.next(&mut fs) { +//! let entry = entry.unwrap(); +//! let file_name = entry.key().unwrap(); +//! println!("{}", file_name.name()); +//! } +//! ``` +//! +//! Check out the [docs](https://docs.rs/ntfs), the tests, and the supplied [`ntfs-shell`](https://github.com/ColinFinck/ntfs/tree/master/examples/ntfs-shell) application for more examples on how to use the `ntfs` library. #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] #![forbid(unsafe_code)] extern crate alloc; @@ -26,7 +59,7 @@ mod string; pub mod structured_values; mod time; mod traits; -mod types; +pub mod types; mod upcase_table; pub use crate::attribute::*; @@ -35,8 +68,9 @@ pub use crate::file::*; pub use crate::file_reference::*; pub use crate::guid::*; pub use crate::index::*; +pub use crate::index_entry::*; +pub use crate::index_record::*; pub use crate::ntfs::*; pub use crate::string::*; pub use crate::time::*; pub use crate::traits::*; -pub use crate::types::*; diff --git a/src/ntfs.rs b/src/ntfs.rs index 78fbd90..48ab07f 100644 --- a/src/ntfs.rs +++ b/src/ntfs.rs @@ -11,6 +11,7 @@ use crate::upcase_table::UpcaseTable; use binread::io::{Read, Seek, SeekFrom}; use binread::BinReaderExt; +/// Root structure describing an NTFS filesystem. #[derive(Debug)] pub struct Ntfs { /// The size of a single cluster, in bytes. This is usually 4096. @@ -21,7 +22,7 @@ pub struct Ntfs { size: u64, /// Absolute position of the Master File Table (MFT), in bytes. mft_position: u64, - /// Size of a single file record, in bytes. + /// Size of a single File Record, in bytes. file_record_size: u32, /// Serial number of the NTFS volume. serial_number: u64, @@ -30,6 +31,10 @@ pub struct Ntfs { } impl Ntfs { + /// Creates a new [`Ntfs`] object from a reader and validates its boot sector information. + /// + /// The reader must cover the entire NTFS partition, not more and not less. + /// It will be rewinded to the beginning before reading anything. pub fn new(fs: &mut T) -> Result where T: Read + Seek, @@ -67,7 +72,7 @@ impl Ntfs { self.cluster_size } - /// Returns the [`NtfsFile`] for the given NTFS file record number. + /// Returns the [`NtfsFile`] for the given NTFS File Record Number. /// /// The first few NTFS files have fixed indexes and contain filesystem /// management information (see the [`KnownNtfsFileRecordNumber`] enum). @@ -81,7 +86,7 @@ impl Ntfs { // The MFT may be split into multiple data runs, referenced by its $DATA attribute. // We therefore read it just like any other non-resident attribute value. - // However, this code assumes that the MFT does not have an AttributeList! + // However, this code assumes that the MFT does not have an Attribute List! let mft = NtfsFile::new(&self, fs, self.mft_position, 0)?; let mft_data_attribute = mft .attributes_raw() @@ -105,6 +110,7 @@ impl Ntfs { NtfsFile::new(&self, fs, position, file_record_number) } + /// Returns the size of a File Record of this NTFS filesystem, in bytes. pub fn file_record_size(&self) -> u32 { self.file_record_size } @@ -173,6 +179,7 @@ impl Ntfs { /// 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(&self, fs: &mut T) -> Option> diff --git a/src/string.rs b/src/string.rs index 2d6d4a9..7f5f9b2 100644 --- a/src/string.rs +++ b/src/string.rs @@ -177,8 +177,9 @@ impl<'a> PartialOrd> for &str { } } +/// Trait for a case-insensitive ordering with respect to the $UpCase table read from the filesystem. pub trait UpcaseOrd { - /// Performs a case-insensitive ordering based on the upcase table read from the filesystem. + /// Performs a case-insensitive ordering based on the $UpCase table read from the filesystem. /// /// # Panics /// diff --git a/src/structured_values/attribute_list.rs b/src/structured_values/attribute_list.rs index e336e84..7c6ad04 100644 --- a/src/structured_values/attribute_list.rs +++ b/src/structured_values/attribute_list.rs @@ -38,23 +38,38 @@ struct AttributeListEntryHeader { /// This becomes relevant when file data is split over multiple attributes. /// Otherwise, it's zero. lowest_vcn: Vcn, - /// Reference to the [`NtfsFile`] record where this attribute is stored. + /// Reference to the File Record where this attribute is stored. base_file_reference: NtfsFileReference, /// Identifier of this attribute that is unique within the [`NtfsFile`]. instance: u16, } +/// Structure of an $ATTRIBUTE_LIST attribute. +/// +/// When a File Record lacks space to incorporate further attributes, NTFS creates an additional File Record, +/// moves all or some of the existing attributes there, and references them via a resident $ATTRIBUTE_LIST attribute +/// in the original File Record. +/// When you add even more attributes, NTFS may turn the resident $ATTRIBUTE_LIST into a non-resident one to +/// make up the required space. +/// +/// An $ATTRIBUTE_LIST attribute can hence be resident or non-resident. +/// +/// Reference: #[derive(Clone, Debug)] pub enum NtfsAttributeList<'n, 'f> { + /// A resident $ATTRIBUTE_LIST attribute. Resident(&'f [u8], u64), + /// A non-resident $ATTRIBUTE_LIST attribute. NonResident(NtfsNonResidentAttributeValue<'n, 'f>), } impl<'n, 'f> NtfsAttributeList<'n, 'f> { - pub fn iter(&self) -> NtfsAttributeListEntries<'n, 'f> { + /// Returns an iterator over all entries of this $ATTRIBUTE_LIST attribute (cf. [`NtfsAttributeListEntry`]). + pub fn entries(&self) -> NtfsAttributeListEntries<'n, 'f> { NtfsAttributeListEntries::new(self.clone()) } + /// Returns the absolute position of this $ATTRIBUTE_LIST attribute value within the filesystem, in bytes. pub fn position(&self) -> u64 { match self { Self::Resident(_slice, position) => *position, @@ -87,6 +102,11 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsAttributeList<'n, 'f> { } } +/// Iterator over +/// all entries of an [`NtfsAttributeList`] attribute, +/// returning an [`NtfsAttributeListEntry`] for each entry. +/// +/// This iterator is returned from the [`NtfsAttributeList::entries`] function. #[derive(Clone, Debug)] pub struct NtfsAttributeListEntries<'n, 'f> { attribute_list: NtfsAttributeList<'n, 'f>, @@ -97,6 +117,7 @@ impl<'n, 'f> NtfsAttributeListEntries<'n, 'f> { Self { attribute_list } } + /// See [`Iterator::next`]. pub fn next(&mut self, fs: &mut T) -> Option> where T: Read + Seek, @@ -107,7 +128,7 @@ impl<'n, 'f> NtfsAttributeListEntries<'n, 'f> { } } - pub fn next_non_resident( + fn next_non_resident( fs: &mut T, value: &mut NtfsNonResidentAttributeValue<'n, 'f>, ) -> Option> @@ -129,7 +150,7 @@ impl<'n, 'f> NtfsAttributeListEntries<'n, 'f> { Some(Ok(entry)) } - pub fn next_resident( + fn next_resident( slice: &mut &'f [u8], position: &mut u64, ) -> Option> { @@ -150,6 +171,7 @@ impl<'n, 'f> NtfsAttributeListEntries<'n, 'f> { } } +/// A single entry of an [`NtfsAttributeList`] attribute. #[derive(Clone, Debug)] pub struct NtfsAttributeListEntry { header: AttributeListEntryHeader, @@ -175,18 +197,32 @@ impl NtfsAttributeListEntry { Ok(entry) } + /// Returns a reference to the File Record where the attribute is stored. pub fn base_file_reference(&self) -> NtfsFileReference { self.header.base_file_reference } + /// Returns the instance number of this attribute list entry. + /// + /// An instance number is unique within a single NTFS File Record. + /// + /// Multiple entries of the same type and instance number form a connected attribute, + /// meaning an attribute whose value is stretched over multiple attributes. pub fn instance(&self) -> u16 { self.header.instance } + /// Returns the length of this attribute list entry, in bytes. pub fn list_entry_length(&self) -> u16 { self.header.list_entry_length } + /// Returns the offset of this attribute's value data as a Virtual Cluster Number (VCN). + /// + /// This is zero for all unconnected attributes and for the first attribute of a connected attribute. + /// For subsequent attributes of a connected attribute, this value is nonzero. + /// + /// The lowest_vcn + data length of one attribute equal the lowest_vcn of its following connected attribute. pub fn lowest_vcn(&self) -> Vcn { self.header.lowest_vcn } @@ -203,6 +239,7 @@ impl NtfsAttributeListEntry { self.header.name_length as usize * mem::size_of::() } + /// Returns the absolute position of this attribute list entry within the filesystem, in bytes. pub fn position(&self) -> u64 { self.position } @@ -220,6 +257,13 @@ impl NtfsAttributeListEntry { Ok(()) } + /// Returns an [`NtfsAttribute`] for the attribute described by this list entry. + /// + /// Use [`NtfsAttributeListEntry::to_file`] first to get the required File Record. + /// + /// # Panics + /// + /// Panics if a wrong File Record has been passed. pub fn to_attribute<'n, 'f>(&self, file: &'f NtfsFile<'n>) -> Result> { let file_record_number = self.base_file_reference().file_record_number(); assert_eq!( @@ -243,6 +287,7 @@ impl NtfsAttributeListEntry { }) } + /// Reads the entire File Record referenced by this attribute and returns it. pub fn to_file<'n, T>(&self, ntfs: &'n Ntfs, fs: &mut T) -> Result> where T: Read + Seek, @@ -251,7 +296,7 @@ impl NtfsAttributeListEntry { ntfs.file(fs, file_record_number) } - /// Returns the type of this NTFS attribute, or [`NtfsError::UnsupportedAttributeType`] + /// Returns the type of this NTFS Attribute, or [`NtfsError::UnsupportedAttributeType`] /// if it's an unknown type. pub fn ty(&self) -> Result { NtfsAttributeType::n(self.header.ty).ok_or(NtfsError::UnsupportedAttributeType { diff --git a/src/structured_values/file_name.rs b/src/structured_values/file_name.rs index 056bfa3..909769d 100644 --- a/src/structured_values/file_name.rs +++ b/src/structured_values/file_name.rs @@ -41,15 +41,38 @@ struct FileNameHeader { namespace: u8, } +/// Character set constraint of the filename, returned by [`NtfsFileName::namespace`]. +/// +/// Reference: #[derive(Clone, Copy, Debug, Eq, N, PartialEq)] #[repr(u8)] pub enum NtfsFileNamespace { + /// A POSIX-compatible filename, which is case-sensitive and supports all Unicode + /// characters except for the forward slash (/) and the NUL character. Posix = 0, + /// A long filename for Windows, which is case-insensitive and supports all Unicode + /// characters except for " * < > ? \ | / : (and doesn't end with a dot or a space). Win32 = 1, + /// An MS-DOS 8+3 filename (8 uppercase characters with a 3-letter uppercase extension) + /// that consists entirely of printable ASCII characters (except for " * < > ? \ | / : ; . , + = [ ]). Dos = 2, + /// A Windows filename that also fulfills all requirements of an MS-DOS 8+3 filename (minus the + /// uppercase requirement), and therefore only got a single $FILE_NAME record with this name. Win32AndDos = 3, } +/// Structure of a $FILE_NAME attribute. +/// +/// NTFS creates a $FILE_NAME attribute for every hard link. +/// Its valuable information is the actual file name and whether this file represents a directory. +/// Apart from that, it duplicates several fields of $STANDARD_INFORMATION, but these are only updated when the file name changes. +/// You usually want to use the corresponding fields from [`NtfsStandardInformation`] instead. +/// +/// A $FILE_NAME attribute can be resident or non-resident. +/// +/// Reference: +/// +/// [`NtfsStandardInformation`]: crate::structured_values::NtfsStandardInformation #[derive(Clone, Debug)] pub struct NtfsFileName { header: FileNameHeader, @@ -83,35 +106,99 @@ impl NtfsFileName { Ok(file_name) } + /// Returns the last access time stored in this $FILE_NAME record. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// Check [`NtfsStandardInformation::access_time`] for a last access time that is always up to date. + /// + /// [`NtfsStandardInformation::access_time`]: crate::structured_values::NtfsStandardInformation::access_time pub fn access_time(&self) -> NtfsTime { self.header.access_time } + /// Returns the allocated size of the file data, in bytes. + /// "Data" refers to the unnamed $DATA attribute only. + /// Other $DATA attributes are not considered. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// If you need an always up-to-date allocated size, use [`NtfsFile::data`] to get the unnamed $DATA attribute, + /// fetch the corresponding [`NtfsAttribute`], and use [`NtfsAttribute::value`] to fetch the corresponding + /// [`NtfsAttributeValue`]. + /// For non-resident attribute values, you now need to walk through each Data Run and sum up the return values of + /// [`NtfsDataRun::len`]. + /// For resident attribute values, there is no extra allocated size. + /// + /// [`NtfsAttribute`]: crate::NtfsAttribute + /// [`NtfsAttribute::value`]: crate::NtfsAttribute::value + /// [`NtfsDataRun::len`]: crate::attribute_value::NtfsDataRun::len + /// [`NtfsFile::data`]: crate::NtfsFile::data pub fn allocated_size(&self) -> u64 { self.header.allocated_size } + /// Returns the creation time stored in this $FILE_NAME record. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// Check [`NtfsStandardInformation::creation_time`] for a creation time that is always up to date. + /// + /// [`NtfsStandardInformation::creation_time`]: crate::structured_values::NtfsStandardInformation::creation_time pub fn creation_time(&self) -> NtfsTime { self.header.creation_time } + /// Returns the size actually used by the file data, in bytes. + /// + /// "Data" refers to the unnamed $DATA attribute only. + /// Other $DATA attributes are not considered. + /// + /// This is less or equal than [`NtfsFileName::allocated_size`]. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// If you need an always up-to-date size, use [`NtfsFile::data`] to get the unnamed $DATA attribute, + /// fetch the corresponding [`NtfsAttribute`], and use [`NtfsAttribute::value`] to fetch the corresponding + /// [`NtfsAttributeValue`]. + /// Then query [`NtfsAttributeValue::len`]. + /// + /// [`NtfsAttribute`]: crate::attribute::NtfsAttribute + /// [`NtfsAttribute::value`]: crate::attribute::NtfsAttribute::value + /// [`NtfsFile::data`]: crate::file::NtfsFile::data pub fn data_size(&self) -> u64 { self.header.data_size } + /// Returns flags that a user can set for a file (Read-Only, Hidden, System, Archive, etc.). + /// Commonly called "File Attributes" in Windows Explorer. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// Check [`NtfsStandardInformation::file_attributes`] for file attributes that are always up to date. + /// + /// [`NtfsStandardInformation::file_attributes`]: crate::structured_values::NtfsStandardInformation::file_attributes pub fn file_attributes(&self) -> NtfsFileAttributeFlags { NtfsFileAttributeFlags::from_bits_truncate(self.header.file_attributes) } + /// Returns whether this file is a directory. pub fn is_directory(&self) -> bool { self.file_attributes() .contains(NtfsFileAttributeFlags::IS_DIRECTORY) } + /// Returns the MFT record modification time stored in this $FILE_NAME record. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// Check [`NtfsStandardInformation::mft_record_modification_time`] for an MFT record modification time that is always up to date. + /// + /// [`NtfsStandardInformation::mft_record_modification_time`]: crate::structured_values::NtfsStandardInformation::mft_record_modification_time pub fn mft_record_modification_time(&self) -> NtfsTime { self.header.mft_record_modification_time } + /// Returns the modification time stored in this $FILE_NAME record. + /// + /// **Note that NTFS only updates it when the file name is changed!** + /// Check [`NtfsStandardInformation::modification_time`] for a modification time that is always up to date. + /// + /// [`NtfsStandardInformation::modification_time`]: crate::structured_values::NtfsStandardInformation::modification_time pub fn modification_time(&self) -> NtfsTime { self.header.modification_time } @@ -133,6 +220,7 @@ impl NtfsFileName { NtfsFileNamespace::n(self.header.namespace).unwrap() } + /// Returns an [`NtfsFileReference`] for the directory where this file is located. pub fn parent_directory_reference(&self) -> NtfsFileReference { self.header.parent_directory_reference } @@ -192,7 +280,7 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsFileName { } } -// `NtfsFileName` is special in the regard that the index entry key has the same structure as the structured value. +// `NtfsFileName` is special in the regard that the Index Entry key has the same structure as the structured value. impl NtfsIndexEntryKey for NtfsFileName { fn key_from_slice(slice: &[u8], position: u64) -> Result { let value_length = slice.len() as u64; diff --git a/src/structured_values/index_allocation.rs b/src/structured_values/index_allocation.rs index 01da45d..3f81089 100644 --- a/src/structured_values/index_allocation.rs +++ b/src/structured_values/index_allocation.rs @@ -13,6 +13,19 @@ use crate::types::Vcn; use binread::io::{Read, Seek, SeekFrom}; use core::iter::FusedIterator; +/// Structure of an $INDEX_ALLOCATION attribute. +/// +/// This attribute describes the sub-nodes of a B-tree. +/// The top-level nodes are managed via [`NtfsIndexRoot`]. +/// +/// NTFS uses B-trees for describing directories (as indexes of [`NtfsFileName`]s), looking up Object IDs, +/// Reparse Points, and Security Descriptors, to just name a few. +/// +/// An $INDEX_ALLOCATION attribute can be resident or non-resident. +/// +/// Reference: +/// +/// [`NtfsFileName`]: crate::structured_values::NtfsFileName #[derive(Clone, Debug)] pub struct NtfsIndexAllocation<'n, 'f> { ntfs: &'n Ntfs, @@ -20,11 +33,14 @@ pub struct NtfsIndexAllocation<'n, 'f> { } 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.clone(), index_record_size) - } - + /// Returns the [`NtfsIndexRecord`] located at the given Virtual Cluster Number (VCN). + /// + /// The record is fully read, fixed up, and validated. + /// + /// This function is usually called on the return value of [`NtfsIndexEntry::subnode_vcn`] to move further + /// down in the B-tree. + /// + /// [`NtfsIndexEntry::subnode_vcn`]: crate::NtfsIndexEntry::subnode_vcn pub fn record_from_vcn( &self, fs: &mut T, @@ -61,6 +77,14 @@ impl<'n, 'f> NtfsIndexAllocation<'n, 'f> { Ok(record) } + + /// Returns an iterator over all Index Records of this $INDEX_ALLOCATION attribute (cf. [`NtfsIndexRecord`]). + /// + /// Each Index Record is fully read, fixed up, and validated. + pub fn records(&self, index_root: &NtfsIndexRoot) -> NtfsIndexRecords<'n, 'f> { + let index_record_size = index_root.index_record_size(); + NtfsIndexRecords::new(self.clone(), index_record_size) + } } impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsIndexAllocation<'n, 'f> { @@ -83,6 +107,13 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsIndexAllocation<'n, 'f> { } } +/// Iterator over +/// all index records of an [`NtfsIndexAllocation`], +/// returning an [`NtfsIndexRecord`] for each record. +/// +/// This iterator is returned from the [`NtfsIndexAllocation::records`] function. +/// +/// See [`NtfsIndexRecordsAttached`] for an iterator that implements [`Iterator`] and [`FusedIterator`]. #[derive(Clone, Debug)] pub struct NtfsIndexRecords<'n, 'f> { index_allocation: NtfsIndexAllocation<'n, 'f>, @@ -97,6 +128,8 @@ impl<'n, 'f> NtfsIndexRecords<'n, 'f> { } } + /// Returns a variant of this iterator that implements [`Iterator`] and [`FusedIterator`] + /// by mutably borrowing the filesystem reader. pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsIndexRecordsAttached<'n, 'f, 'a, T> where T: Read + Seek, @@ -104,6 +137,7 @@ impl<'n, 'f> NtfsIndexRecords<'n, 'f> { NtfsIndexRecordsAttached::new(fs, self) } + /// See [`Iterator::next`]. pub fn next(&mut self, fs: &mut T) -> Option>> where T: Read + Seek, @@ -130,6 +164,15 @@ impl<'n, 'f> NtfsIndexRecords<'n, 'f> { } } +/// Iterator over +/// all index records of an [`NtfsIndexAllocation`], +/// returning an [`NtfsIndexRecord`] for each record, +/// implementing [`Iterator`] and [`FusedIterator`]. +/// +/// This iterator is returned from the [`NtfsIndexRecords::attach`] function. +/// Conceptually the same as [`NtfsIndexRecords`], but mutably borrows the filesystem +/// to implement aforementioned traits. +#[derive(Debug)] pub struct NtfsIndexRecordsAttached<'n, 'f, 'a, T> where T: Read + Seek, @@ -145,7 +188,7 @@ where fn new(fs: &'a mut T, index_records: NtfsIndexRecords<'n, 'f>) -> Self { Self { fs, index_records } } - + /// Consumes this iterator and returns the inner [`NtfsIndexRecords`]. pub fn detach(self) -> NtfsIndexRecords<'n, 'f> { self.index_records } diff --git a/src/structured_values/index_root.rs b/src/structured_values/index_root.rs index 86a12af..acb91be 100644 --- a/src/structured_values/index_root.rs +++ b/src/structured_values/index_root.rs @@ -26,6 +26,20 @@ struct IndexRootHeader { clusters_per_index_record: i8, } +/// Structure of an $INDEX_ROOT attribute. +/// +/// This attribute describes the top-level nodes of a B-tree. +/// The sub-nodes are managed via [`NtfsIndexAllocation`]. +/// +/// NTFS uses B-trees for describing directories (as indexes of [`NtfsFileName`]s), looking up Object IDs, +/// Reparse Points, and Security Descriptors, to just name a few. +/// +/// An $INDEX_ROOT attribute is always resident. +/// +/// Reference: +/// +/// [`NtfsFileName`]: crate::structured_values::NtfsFileName +/// [`NtfsIndexAllocation`]: crate::structured_values::NtfsIndexAllocation #[derive(Clone, Debug)] pub struct NtfsIndexRoot<'f> { slice: &'f [u8], @@ -51,6 +65,7 @@ impl<'f> NtfsIndexRoot<'f> { Ok(index_root) } + /// Returns an iterator over all top-level nodes of the B-tree. pub fn entries(&self) -> Result> where E: NtfsIndexEntryType, @@ -63,7 +78,7 @@ impl<'f> NtfsIndexRoot<'f> { fn entries_range_and_position(&self) -> (Range, 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 end = INDEX_ROOT_HEADER_SIZE as usize + self.index_data_size() as usize; let position = self.position + start as u64; (start..end, position) @@ -80,26 +95,29 @@ impl<'f> NtfsIndexRoot<'f> { IndexNodeEntryRanges::new(entries_data, range, position) } + /// Returns the allocated size of this NTFS Index Root, in bytes. pub fn index_allocated_size(&self) -> u32 { let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, allocated_size); LittleEndian::read_u32(&self.slice[start..]) } + /// Returns the size actually used by index data within this NTFS Index Root, in bytes. + pub fn index_data_size(&self) -> u32 { + let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, index_size); + LittleEndian::read_u32(&self.slice[start..]) + } + fn index_entries_offset(&self) -> u32 { let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, entries_offset); LittleEndian::read_u32(&self.slice[start..]) } + /// Returns the size of a single Index Record, in bytes. pub fn index_record_size(&self) -> u32 { let start = offset_of!(IndexRootHeader, index_record_size); LittleEndian::read_u32(&self.slice[start..]) } - pub fn index_used_size(&self) -> u32 { - let start = INDEX_ROOT_HEADER_SIZE + offset_of!(IndexNodeHeader, index_size); - LittleEndian::read_u32(&self.slice[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. @@ -108,6 +126,7 @@ impl<'f> NtfsIndexRoot<'f> { (self.slice[start] & LARGE_INDEX_FLAG) != 0 } + /// Returns the absolute position of this Index Root within the filesystem, in bytes. pub fn position(&self) -> u64 { self.position } diff --git a/src/structured_values/mod.rs b/src/structured_values/mod.rs index d8fcc0d..8f2d7ea 100644 --- a/src/structured_values/mod.rs +++ b/src/structured_values/mod.rs @@ -1,5 +1,7 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +// +//! Various types of NTFS Attribute structured values. mod attribute_list; mod file_name; @@ -28,24 +30,50 @@ use binread::io::{Read, Seek}; use bitflags::bitflags; bitflags! { + /// Flags that a user can set for a file (Read-Only, Hidden, System, Archive, etc.). + /// Commonly called "File Attributes" in Windows Explorer. + /// + /// Not to be confused with [`NtfsAttribute`]. + /// + /// Returned by [`NtfsStandardInformation::file_attributes`] and [`NtfsFileName::file_attributes`]. + /// + /// [`NtfsAttribute`]: crate::attribute::NtfsAttribute pub struct NtfsFileAttributeFlags: u32 { + /// File is marked read-only. const READ_ONLY = 0x0001; + /// File is hidden (in file browsers that care). const HIDDEN = 0x0002; + /// File is marked as a system file. const SYSTEM = 0x0004; + /// File is marked for archival (cf. ). const ARCHIVE = 0x0020; + /// File denotes a device. const DEVICE = 0x0040; + /// Set when no other attributes are set. const NORMAL = 0x0080; + /// File is a temporary file that is likely to be deleted. const TEMPORARY = 0x0100; + /// File is stored sparsely. const SPARSE_FILE = 0x0200; + /// File is a reparse point. const REPARSE_POINT = 0x0400; + /// File is transparently compressed by the filesystem (using LZNT1 algorithm). + /// For directories, this attribute denotes that compression is enabled by default for new files inside that directory. const COMPRESSED = 0x0800; const OFFLINE = 0x1000; + /// File has not (yet) been indexed by the Windows Indexing Service. const NOT_CONTENT_INDEXED = 0x2000; + /// File is encrypted via EFS. + /// For directories, this attribute denotes that encryption is enabled by default for new files inside that directory. const ENCRYPTED = 0x4000; + /// File is a directory. + /// + /// This attribute is only returned from [`NtfsFileName::file_attributes`]. const IS_DIRECTORY = 0x1000_0000; } } +/// Trait implemented by every NTFS attribute structured value. pub trait NtfsStructuredValue<'n, 'f>: Sized { const TY: NtfsAttributeType; @@ -55,10 +83,12 @@ pub trait NtfsStructuredValue<'n, 'f>: Sized { T: Read + Seek; } -/// Create a structured value from an arbitrary data slice. -/// This is a fast path for the few structured values that are always in resident attributes. +/// Trait implemented by NTFS Attribute structured values that are always in resident attributes. pub trait NtfsStructuredValueFromResidentAttributeValue<'n, 'f>: NtfsStructuredValue<'n, 'f> { + /// Create a structured value from a resident attribute value. + /// + /// This is a fast path for the few structured values that are always in resident attributes. fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result; } diff --git a/src/structured_values/object_id.rs b/src/structured_values/object_id.rs index 3f03cc6..c6fabff 100644 --- a/src/structured_values/object_id.rs +++ b/src/structured_values/object_id.rs @@ -11,6 +11,13 @@ use crate::structured_values::{ use binread::io::{Cursor, Read, Seek}; use binread::BinReaderExt; +/// Structure of an $OBJECT_ID attribute. +/// +/// This optional attribute contains a globally unique identifier of the file. +/// +/// An $OBJECT_ID attribute is always resident. +/// +/// Reference: #[derive(Clone, Debug)] pub struct NtfsObjectId { object_id: NtfsGuid, @@ -58,18 +65,22 @@ impl NtfsObjectId { }) } + /// Returns the (optional) first Object ID that has ever been assigned to this file. pub fn birth_object_id(&self) -> Option<&NtfsGuid> { self.birth_object_id.as_ref() } + /// Returns the (optional) Object ID of the $Volume file of the partition where this file was created. pub fn birth_volume_id(&self) -> Option<&NtfsGuid> { self.birth_volume_id.as_ref() } + /// Returns the (optional) Domain ID of this file. pub fn domain_id(&self) -> Option<&NtfsGuid> { self.domain_id.as_ref() } + /// Returns the Object ID, a globally unique identifier of the file. pub fn object_id(&self) -> &NtfsGuid { &self.object_id } diff --git a/src/structured_values/standard_information.rs b/src/structured_values/standard_information.rs index 5ae8891..7ae31d6 100644 --- a/src/structured_values/standard_information.rs +++ b/src/structured_values/standard_information.rs @@ -37,6 +37,14 @@ struct StandardInformationDataNtfs3 { usn: u64, } +/// Structure of a $STANDARD_INFORMATION attribute. +/// +/// Among other things, this is the place where the file times and "File Attributes" +/// (Read-Only, Hidden, System, Archive, etc.) are stored. +/// +/// A $STANDARD_INFORMATION attribute is always resident. +/// +/// Reference: #[derive(Clone, Debug)] pub struct NtfsStandardInformation { ntfs1_data: StandardInformationDataNtfs1, @@ -70,50 +78,67 @@ impl NtfsStandardInformation { }) } + /// Returns the time this file was last accessed. pub fn access_time(&self) -> NtfsTime { self.ntfs1_data.access_time } + /// Returns the Class ID of the file, if stored via NTFS 3.x file information. pub fn class_id(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.class_id) } + /// Returns the time this file was created. pub fn creation_time(&self) -> NtfsTime { self.ntfs1_data.creation_time } + /// Returns flags that a user can set for a file (Read-Only, Hidden, System, Archive, etc.). + /// Commonly called "File Attributes" in Windows Explorer. pub fn file_attributes(&self) -> NtfsFileAttributeFlags { NtfsFileAttributeFlags::from_bits_truncate(self.ntfs1_data.file_attributes) } + /// Returns the maximum allowed versions for this file, if stored via NTFS 3.x file information. + /// + /// A value of zero means that versioning is disabled for this file. pub fn maximum_versions(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.maximum_versions) } + /// Returns the time the MFT record of this file was last modified. pub fn mft_record_modification_time(&self) -> NtfsTime { self.ntfs1_data.mft_record_modification_time } + /// Returns the time this file was last modified. pub fn modification_time(&self) -> NtfsTime { self.ntfs1_data.modification_time } + /// Returns the Owner ID of the file, if stored via NTFS 3.x file information. pub fn owner_id(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.owner_id) } + /// Returns the quota charged by this file, if stored via NTFS 3.x file information. pub fn quota_charged(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.quota_charged) } + /// Returns the Security ID of the file, if stored via NTFS 3.x file information. pub fn security_id(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.security_id) } + /// Returns the Update Sequence Number (USN) of the file, if stored via NTFS 3.x file information. pub fn usn(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.usn) } + /// Returns the version of the file, if stored via NTFS 3.x file information. + /// + /// This will be zero if versioning is disabled for this file. pub fn version(&self) -> Option { self.ntfs3_data.as_ref().map(|x| x.version) } diff --git a/src/structured_values/volume_information.rs b/src/structured_values/volume_information.rs index ff16018..befb410 100644 --- a/src/structured_values/volume_information.rs +++ b/src/structured_values/volume_information.rs @@ -23,6 +23,7 @@ struct VolumeInformationData { } bitflags! { + /// Flags returned by [`NtfsVolumeInformation::flags`]. pub struct NtfsVolumeFlags: u16 { /// The volume needs to be checked by `chkdsk`. const IS_DIRTY = 0x0001; @@ -36,6 +37,16 @@ bitflags! { } } +/// Structure of a $VOLUME_INFORMATION attribute. +/// +/// This attribute is only used by the top-level $Volume file and contains general information about the filesystem. +/// You can easily access it via [`Ntfs::volume_info`]. +/// +/// A $VOLUME_INFORMATION attribute is always resident. +/// +/// Reference: +/// +/// [`Ntfs::volume_info`]: crate::Ntfs::volume_info #[derive(Clone, Debug)] pub struct NtfsVolumeInformation { info: VolumeInformationData, @@ -60,14 +71,17 @@ impl NtfsVolumeInformation { Ok(Self { info }) } + /// Returns flags set for this NTFS filesystem/volume as specified by [`NtfsVolumeFlags`]. pub fn flags(&self) -> NtfsVolumeFlags { NtfsVolumeFlags::from_bits_truncate(self.info.flags) } + /// Returns the major NTFS version of this filesystem (e.g. `3` for NTFS 3.1). pub fn major_version(&self) -> u8 { self.info.major_version } + /// Returns the minor NTFS version of this filesystem (e.g. `1` for NTFS 3.1). pub fn minor_version(&self) -> u8 { self.info.minor_version } diff --git a/src/structured_values/volume_name.rs b/src/structured_values/volume_name.rs index b2cc823..1155ccf 100644 --- a/src/structured_values/volume_name.rs +++ b/src/structured_values/volume_name.rs @@ -18,6 +18,16 @@ const VOLUME_NAME_MIN_SIZE: usize = mem::size_of::(); /// The largest VolumeName attribute has a name containing 128 UTF-16 code points (256 bytes). const VOLUME_NAME_MAX_SIZE: usize = 128 * mem::size_of::(); +/// Structure of a $VOLUME_NAME attribute. +/// +/// This attribute is only used by the top-level $Volume file and contains the user-defined name of this filesystem. +/// You can easily access it via [`Ntfs::volume_name`]. +/// +/// A $VOLUME_NAME attribute is always resident. +/// +/// Reference: +/// +/// [`Ntfs::volume_name`]: crate::Ntfs::volume_name #[derive(Clone, Debug)] pub struct NtfsVolumeName { name: ArrayVec, @@ -53,7 +63,7 @@ impl NtfsVolumeName { Ok(Self { name }) } - /// Gets the file name and returns it wrapped in an [`NtfsString`]. + /// Gets the volume name and returns it wrapped in an [`NtfsString`]. pub fn name<'s>(&'s self) -> NtfsString<'s> { NtfsString(&self.name) } diff --git a/src/time.rs b/src/time.rs index 5e1d46c..7b8ca60 100644 --- a/src/time.rs +++ b/src/time.rs @@ -24,14 +24,18 @@ const DAYS_FROM_0001_TO_1601: i32 = 584389; #[cfg(feature = "std")] const EPOCH_DIFFERENCE_IN_INTERVALS: i64 = 116_444_736_000_000_000; -/// How many 100-nanosecond intervals we have in a second. +/// Number of 100-nanosecond intervals in a second. #[cfg(any(feature = "chrono", feature = "std"))] const INTERVALS_PER_SECOND: u64 = 10_000_000; -/// How many 100-nanosecond intervals we have in a day. +/// Number of 100-nanosecond intervals in a day. #[cfg(feature = "chrono")] const INTERVALS_PER_DAY: u64 = 24 * 60 * 60 * INTERVALS_PER_SECOND; +/// An NTFS timestamp, used for expressing file times. +/// +/// NTFS (and the Windows NT line of operating systems) represent time as an unsigned 64-bit integer +/// counting the number of 100-nanosecond intervals since January 1, 1601. #[derive(BinRead, Clone, Copy, Debug, Eq, From, Ord, PartialEq, PartialOrd)] pub struct NtfsTime(u64); @@ -43,6 +47,7 @@ impl NtfsTime { } #[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] impl TryFrom> for NtfsTime { type Error = NtfsError; @@ -78,6 +83,7 @@ impl TryFrom> for NtfsTime { } #[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] impl From for DateTime { fn from(nt: NtfsTime) -> DateTime { let mut remainder = nt.nt_timestamp(); @@ -104,6 +110,7 @@ impl From for DateTime { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl TryFrom for NtfsTime { type Error = SystemTimeError; diff --git a/src/traits.rs b/src/traits.rs index 150e150..0a9f202 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,11 +2,18 @@ use crate::error::{NtfsError, Result}; use binread::io; use binread::io::{Read, Seek, SeekFrom}; +/// Trait to read/seek in a source by the help of a temporarily passed mutable reference to the filesystem reader. +/// +/// By requiring the user to pass the filesystem reader on every read, we circumvent the problems associated with permanently +/// holding a mutable reference. +/// If we held one, we could not read from two objects in alternation. pub trait NtfsReadSeek { + /// See [`std::io::Read::read`]. fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result where T: Read + Seek; + /// See [`std::io::Read::read_exact`]. fn read_exact(&mut self, fs: &mut T, mut buf: &mut [u8]) -> Result<()> where T: Read + Seek, @@ -34,6 +41,7 @@ pub trait NtfsReadSeek { } } + /// See [`std::io::Seek::seek`]. fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result where T: Read + Seek; diff --git a/src/types.rs b/src/types.rs index 563f353..842635c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,11 +1,17 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +// +//! Supplementary helper types. use crate::error::{NtfsError, Result}; use crate::ntfs::Ntfs; use binread::BinRead; use derive_more::{Binary, Display, From, LowerHex, Octal, UpperHex}; +/// A Logical Cluster Number (LCN). +/// +/// NTFS divides a filesystem into clusters of a given size (power of two), see [`Ntfs::cluster_size`]. +/// The LCN is an absolute cluster index into the filesystem. #[derive( Binary, BinRead, @@ -25,6 +31,7 @@ use derive_more::{Binary, Display, From, LowerHex, Octal, UpperHex}; pub struct Lcn(u64); impl Lcn { + /// Performs a checked addition of the given Virtual Cluster Number (VCN), returning a new LCN. pub fn checked_add(&self, vcn: Vcn) -> Option { if vcn.0 >= 0 { self.0.checked_add(vcn.0 as u64).map(Into::into) @@ -35,6 +42,7 @@ impl Lcn { } } + /// Returns the absolute byte position of this LCN within the filesystem. pub fn position(&self, ntfs: &Ntfs) -> Result { self.0 .checked_mul(ntfs.cluster_size() as u64) @@ -42,6 +50,11 @@ impl Lcn { } } +/// A Virtual Cluster Number (VCN). +/// +/// NTFS divides a filesystem into clusters of a given size (power of two), see [`Ntfs::cluster_size`]. +/// The VCN is a cluster index into the filesystem that is relative to a Logical Cluster Number (LCN) +/// or relative to the start of an attribute value. #[derive( Binary, BinRead, @@ -61,6 +74,7 @@ impl Lcn { pub struct Vcn(i64); impl Vcn { + /// Converts this VCN into a byte offset (with respect to the cluster size of the provided [`Ntfs`] filesystem). pub fn offset(&self, ntfs: &Ntfs) -> Result { self.0 .checked_mul(ntfs.cluster_size() as i64) -- cgit v1.2.3