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

github.com/windirstat/ntfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Finck <colin@reactos.org>2021-12-15 22:06:35 +0300
committerColin Finck <colin@reactos.org>2021-12-15 22:06:35 +0300
commit5ff15ae17d618f846176ab40140c4141aab24bbb (patch)
treea23951f8c4b538ed628c2d159e2b13d8429f7511
parent14b8be0c9ab74257738cb36d7683386604c4bbac (diff)
Add the missing documentation and some final polishing.
-rw-r--r--Cargo.toml4
-rw-r--r--examples/ntfs-shell/main.rs24
-rw-r--r--src/attribute.rs135
-rw-r--r--src/attribute_value/attribute_list_non_resident.rs49
-rw-r--r--src/attribute_value/mod.rs24
-rw-r--r--src/attribute_value/non_resident.rs97
-rw-r--r--src/attribute_value/resident.rs10
-rw-r--r--src/error.rs41
-rw-r--r--src/file.rs108
-rw-r--r--src/file_reference.rs10
-rw-r--r--src/guid.rs1
-rw-r--r--src/index.rs32
-rw-r--r--src/index_entry.rs66
-rw-r--r--src/index_record.rs42
-rw-r--r--src/indexes/file_name.rs2
-rw-r--r--src/indexes/mod.rs34
-rw-r--r--src/lib.rs38
-rw-r--r--src/ntfs.rs13
-rw-r--r--src/string.rs3
-rw-r--r--src/structured_values/attribute_list.rs55
-rw-r--r--src/structured_values/file_name.rs90
-rw-r--r--src/structured_values/index_allocation.rs55
-rw-r--r--src/structured_values/index_root.rs31
-rw-r--r--src/structured_values/mod.rs34
-rw-r--r--src/structured_values/object_id.rs11
-rw-r--r--src/structured_values/standard_information.rs25
-rw-r--r--src/structured_values/volume_information.rs14
-rw-r--r--src/structured_values/volume_name.rs12
-rw-r--r--src/time.rs11
-rw-r--r--src/traits.rs8
-rw-r--r--src/types.rs14
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 <colin@reactos.org>");
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() {
"<DIR>"
@@ -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 {
"<NONE>".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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/index.html>
#[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: <https://flatcap.github.io/linux-ntfs/ntfs/concepts/attribute_header.html>
+///
+/// [`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<S>(&self) -> Result<S>
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<T, S>(&self, fs: &mut T) -> Result<S>
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<NtfsAttributeType> {
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<NtfsAttributeValue<'n, 'f>> {
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<NtfsAttributeListEntries<'n, 'f>>,
@@ -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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsAttributeItem<'n, 'f>>>
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::<T, NtfsAttributeList>(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<usize>,
@@ -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 <colin@reactos.org>
// 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<AttributeState<'n>>,
- /// 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<u64> {
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<bool> {
// 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<T>(&mut self, fs: &mut T) -> Result<bool>
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 <colin@reactos.org>
// 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<u64> {
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<u64> {
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 <colin@reactos.org>
// 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<u64> {
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<bool> {
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<u64> {
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<u64> {
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<NtfsDataRun>,
/// 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<u64> {
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<SeekFrom> {
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<T>(
&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<usize>,
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<NtfsError> 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: <https://flatcap.github.io/linux-ntfs/ntfs/files/index.html>
#[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: <https://flatcap.github.io/linux-ntfs/ntfs/files/logfile.html>
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: <https://flatcap.github.io/linux-ntfs/ntfs/files/attrdef.html>
AttrDef = 4,
+ /// The root directory of the filesystem.
+ ///
+ /// You can easily access it via [`Ntfs::root_directory`].
RootDirectory = 5,
+ /// Map of used clusters.
+ ///
+ /// Reference: <https://flatcap.github.io/linux-ntfs/ntfs/files/bitmap.html>
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: <https://flatcap.github.io/linux-ntfs/ntfs/files/badclus.html>
BadClus = 8,
+ /// A list of all Security Descriptors used by this filesystem.
+ ///
+ /// Reference: <https://flatcap.github.io/linux-ntfs/ntfs/files/secure.html>
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: <https://flatcap.github.io/linux-ntfs/ntfs/files/extend.html>
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: <https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_record.html>
+///
+/// [`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::<NtfsFileNameIndex>::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::<NtfsStandardInformation>(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: <https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_reference.html>
#[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<NtfsAttributeItem<'n, 'f>>,
@@ -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<Result<NtfsIndexEntry<'a, E>>>
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: <https://flatcap.github.io/linux-ntfs/ntfs/concepts/index_entry.html>
+///
+/// [`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<Result<E::DataType>>
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::<u64>()].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<Result<E::KeyType>> {
// 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<Result<Vcn>> {
@@ -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<NtfsFile<'n>>
where
E: NtfsIndexEntryHasFileReference,
@@ -313,6 +353,22 @@ where
impl<E> FusedIterator for IndexNodeEntryRanges<E> 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: <https://flatcap.github.io/linux-ntfs/ntfs/concepts/index_record.html>
#[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<NtfsIndexNodeEntries<'r, E>>
where
E: NtfsIndexEntryType,
@@ -81,7 +95,7 @@ impl<'n> NtfsIndexRecord<'n> {
fn entries_range_and_position(&self) -> (Range<usize>, u64) {
let start = INDEX_RECORD_HEADER_SIZE as usize + self.index_entries_offset() as usize;
- let end = INDEX_RECORD_HEADER_SIZE as usize + self.index_used_size() as usize;
+ let 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 <colin@reactos.org>
// 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<Self>;
}
-/// 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<Self>;
}
-/// 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 <colin@reactos.org>
// 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<T>(fs: &mut T) -> Result<Self>
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<T>(&self, fs: &mut T) -> Option<Result<NtfsVolumeName>>
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<NtfsString<'a>> for &str {
}
}
+/// Trait for a case-insensitive ordering with respect to the $UpCase table read from the filesystem.
pub trait UpcaseOrd<Rhs> {
- /// 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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/attribute_list.html>
#[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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsAttributeListEntry>>
where
T: Read + Seek,
@@ -107,7 +128,7 @@ impl<'n, 'f> NtfsAttributeListEntries<'n, 'f> {
}
}
- pub fn next_non_resident<T>(
+ fn next_non_resident<T>(
fs: &mut T,
value: &mut NtfsNonResidentAttributeValue<'n, 'f>,
) -> Option<Result<NtfsAttributeListEntry>>
@@ -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<Result<NtfsAttributeListEntry>> {
@@ -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::<u16>()
}
+ /// 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<NtfsAttribute<'n, 'f>> {
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<NtfsFile<'n>>
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> {
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: <https://flatcap.github.io/linux-ntfs/ntfs/concepts/filename_namespace.html>
#[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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html>
+///
+/// [`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<Self> {
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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/index_allocation.html>
+///
+/// [`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<T>(
&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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsIndexRecord<'n>>>
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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/index_root.html>
+///
+/// [`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<E>(&self) -> Result<NtfsIndexNodeEntries<'f, E>>
where
E: NtfsIndexEntryType,
@@ -63,7 +78,7 @@ impl<'f> NtfsIndexRoot<'f> {
fn entries_range_and_position(&self) -> (Range<usize>, u64) {
let start = INDEX_ROOT_HEADER_SIZE as usize + self.index_entries_offset() as usize;
- let end = INDEX_ROOT_HEADER_SIZE as usize + self.index_used_size() as usize;
+ let 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 <colin@reactos.org>
// 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. <https://en.wikipedia.org/wiki/Archive_bit>).
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<Self>;
}
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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/object_id.html>
#[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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/standard_information.html>
#[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<u32> {
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<u32> {
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<u32> {
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<u64> {
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<u32> {
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<u64> {
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<u32> {
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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/volume_information.html>
+///
+/// [`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::<u16>();
/// 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::<u16>();
+/// 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: <https://flatcap.github.io/linux-ntfs/ntfs/attributes/volume_name.html>
+///
+/// [`Ntfs::volume_name`]: crate::Ntfs::volume_name
#[derive(Clone, Debug)]
pub struct NtfsVolumeName {
name: ArrayVec<u8, VOLUME_NAME_MAX_SIZE>,
@@ -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<DateTime<Utc>> for NtfsTime {
type Error = NtfsError;
@@ -78,6 +83,7 @@ impl TryFrom<DateTime<Utc>> for NtfsTime {
}
#[cfg(feature = "chrono")]
+#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<NtfsTime> for DateTime<Utc> {
fn from(nt: NtfsTime) -> DateTime<Utc> {
let mut remainder = nt.nt_timestamp();
@@ -104,6 +110,7 @@ impl From<NtfsTime> for DateTime<Utc> {
}
#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl TryFrom<SystemTime> 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<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
where
T: Read + Seek;
+ /// See [`std::io::Read::read_exact`].
fn read_exact<T>(&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<T>(&mut self, fs: &mut T, pos: SeekFrom) -> Result<u64>
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 <colin@reactos.org>
// 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<Lcn> {
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<u64> {
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<i64> {
self.0
.checked_mul(ntfs.cluster_size() as i64)