From 8d974038adaebaf763b44b962942d5c5f3f3fcd0 Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Sun, 3 Oct 2021 20:05:07 +0200 Subject: `NtfsValue` -> `NtfsAttributeValue` and `Slice` -> `Resident`. Turns out that we won't use this enum for anything else than attribute values, and `Slice` always corresponds to a resident attribute value, so reflect that in the naming. Index keys still use slices without this enum. --- examples/ntfs-shell/main.rs | 4 +- src/attribute.rs | 22 +- src/attribute_value/attribute_list_non_resident.rs | 296 +++++++++ src/attribute_value/mod.rs | 158 +++++ src/attribute_value/non_resident.rs | 670 +++++++++++++++++++++ src/attribute_value/resident.rs | 86 +++ src/index_record.rs | 4 +- src/lib.rs | 2 +- src/structured_values/attribute_list.rs | 11 +- src/structured_values/file_name.rs | 4 +- src/structured_values/index_allocation.rs | 12 +- src/structured_values/index_root.rs | 15 +- src/structured_values/mod.rs | 9 +- src/structured_values/object_id.rs | 7 +- src/structured_values/standard_information.rs | 7 +- src/structured_values/volume_information.rs | 7 +- src/structured_values/volume_name.rs | 7 +- src/value/attribute_list_non_resident_attribute.rs | 296 --------- src/value/mod.rs | 157 ----- src/value/non_resident_attribute.rs | 670 --------------------- src/value/slice.rs | 86 --- 21 files changed, 1262 insertions(+), 1268 deletions(-) create mode 100644 src/attribute_value/attribute_list_non_resident.rs create mode 100644 src/attribute_value/mod.rs create mode 100644 src/attribute_value/non_resident.rs create mode 100644 src/attribute_value/resident.rs delete mode 100644 src/value/attribute_list_non_resident_attribute.rs delete mode 100644 src/value/mod.rs delete mode 100644 src/value/non_resident_attribute.rs delete mode 100644 src/value/slice.rs diff --git a/examples/ntfs-shell/main.rs b/examples/ntfs-shell/main.rs index 988f675..3df1b59 100644 --- a/examples/ntfs-shell/main.rs +++ b/examples/ntfs-shell/main.rs @@ -10,11 +10,11 @@ use std::io::{BufReader, Read, Seek, Write}; use anyhow::{anyhow, bail, Context, Result}; use chrono::{DateTime, Utc}; +use ntfs::attribute_value::NtfsAttributeValue; use ntfs::indexes::NtfsFileNameIndex; use ntfs::structured_values::{ NtfsAttributeList, NtfsFileName, NtfsFileNamespace, NtfsStandardInformation, }; -use ntfs::value::NtfsValue; use ntfs::{Ntfs, NtfsAttribute, NtfsAttributeType, NtfsFile, NtfsReadSeek}; use sector_reader::SectorReader; @@ -176,7 +176,7 @@ fn attr_print_attribute<'n>( ); if with_runs { - if let NtfsValue::NonResidentAttribute(non_resident_value) = attribute.value()? { + if let NtfsAttributeValue::NonResident(non_resident_value) = attribute.value()? { for (i, data_run) in non_resident_value.data_runs().enumerate() { let data_run = data_run?; let instance = format!("{}{}", data_run_prefix, i); diff --git a/src/attribute.rs b/src/attribute.rs index 567acd1..a4b6ae3 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,6 +1,10 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +use crate::attribute_value::{ + NtfsAttributeListNonResidentAttributeValue, NtfsAttributeValue, NtfsNonResidentAttributeValue, + NtfsResidentAttributeValue, +}; use crate::error::{NtfsError, Result}; use crate::file::NtfsFile; use crate::string::NtfsString; @@ -9,10 +13,6 @@ use crate::structured_values::{ NtfsStructuredValueFromResidentAttributeValue, }; use crate::types::Vcn; -use crate::value::attribute_list_non_resident_attribute::NtfsAttributeListNonResidentAttributeValue; -use crate::value::non_resident_attribute::NtfsNonResidentAttributeValue; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use binread::io::{Read, Seek}; use bitflags::bitflags; use byteorder::{ByteOrder, LittleEndian}; @@ -271,7 +271,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { S::from_resident_attribute_value(resident_value) } - pub(crate) fn resident_value(&self) -> Result> { + pub(crate) fn resident_value(&self) -> Result> { debug_assert!(self.is_resident()); self.validate_resident_value_sizes()?; @@ -279,7 +279,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { let end = start + self.resident_value_length() as usize; let data = &self.file.record_data()[start..end]; - Ok(NtfsSliceValue::new(data, self.position())) + Ok(NtfsResidentAttributeValue::new(data, self.position())) } fn resident_value_length(&self) -> u32 { @@ -308,7 +308,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { }); } - S::from_value(fs, self.value()?) + S::from_attribute_value(fs, self.value()?) } /// Returns the type of this NTFS attribute, or [`NtfsError::UnsupportedAttributeType`] @@ -370,7 +370,7 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { } /// Returns an [`NtfsAttributeValue`] structure to read the value of this NTFS attribute. - pub fn value(&self) -> Result> { + pub fn value(&self) -> Result> { if let Some(list_entries) = self.list_entries { // The first attribute reports the entire data size for all connected attributes // (remaining ones are set to zero). @@ -384,13 +384,13 @@ impl<'n, 'f> NtfsAttribute<'n, 'f> { self.ty()?, data_size, ); - Ok(NtfsValue::AttributeListNonResidentAttribute(value)) + Ok(NtfsAttributeValue::AttributeListNonResident(value)) } else if self.is_resident() { let value = self.resident_value()?; - Ok(NtfsValue::Slice(value)) + Ok(NtfsAttributeValue::Resident(value)) } else { let value = self.non_resident_value()?; - Ok(NtfsValue::NonResidentAttribute(value)) + Ok(NtfsAttributeValue::NonResident(value)) } } diff --git a/src/attribute_value/attribute_list_non_resident.rs b/src/attribute_value/attribute_list_non_resident.rs new file mode 100644 index 0000000..f85bec6 --- /dev/null +++ b/src/attribute_value/attribute_list_non_resident.rs @@ -0,0 +1,296 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later +// +//! This module implements a reader for a non-resident attribute value that is part of an AttributeList. +//! Such values are not only split up into data runs, but may also be continued by connected attributes which are listed in the same AttributeList. +//! This reader provides one contiguous data stream for all data runs in all connected attributes. +// +// It is important to note that `NtfsAttributeListNonResidentAttributeValue` can't just encapsulate `NtfsNonResidentAttributeValue` and provide one +// layer on top to connect the attributes! +// Connected attributes are stored in a way that the first attribute reports the entire data size and all further attributes report a zero value length. +// We have to go down to the data run level to get trustable lengths again, and this is what `NtfsAttributeListNonResidentAttributeValue` does here. + +use super::{DataRunsState, NtfsDataRuns, StreamState}; +use crate::attribute::{NtfsAttribute, NtfsAttributeType}; +use crate::error::{NtfsError, Result}; +use crate::file::NtfsFile; +use crate::ntfs::Ntfs; +use crate::structured_values::{NtfsAttributeListEntries, NtfsAttributeListEntry}; +use crate::traits::NtfsReadSeek; +use binread::io::{Read, Seek, SeekFrom}; + +#[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. + connected_entries: AttributeListConnectedEntries<'n, 'f>, + /// File, location, and data runs iteration state of the current attribute. + attribute_state: Option>, + /// Iteration state of the current data run. + stream_state: StreamState, +} + +impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { + pub(crate) fn new( + ntfs: &'n Ntfs, + attribute_list_entries: NtfsAttributeListEntries<'n, 'f>, + instance: u16, + ty: NtfsAttributeType, + data_size: u64, + ) -> Self { + let connected_entries = + AttributeListConnectedEntries::new(attribute_list_entries.clone(), instance, ty); + + Self { + ntfs, + initial_attribute_list_entries: attribute_list_entries, + connected_entries, + attribute_state: None, + stream_state: StreamState::new(data_size), + } + } + + /// Returns the absolute current data seek position within the filesystem, in bytes. + /// This may be `None` if: + /// * The current seek position is outside the valid range, or + /// * The current data run is a "sparse" data run + pub fn data_position(&self) -> Option { + self.stream_state.data_position() + } + + pub fn len(&self) -> u64 { + self.stream_state.data_size() + } + + /// Returns whether we got another data run. + fn next_data_run(&mut self) -> Result { + // Do we have a file and a (non-resident) attribute to iterate through its data runs? + let attribute_state = match &mut self.attribute_state { + Some(attribute_state) => attribute_state, + None => return Ok(false), + }; + + // Get the state of the `NtfsDataRuns` iterator of that attribute. + let data_runs_state = match attribute_state.data_runs_state.take() { + Some(data_runs_state) => data_runs_state, + None => return Ok(false), + }; + + // Deserialize the state into an `NtfsDataRuns` iterator. + let attribute = NtfsAttribute::new( + &attribute_state.file, + attribute_state.attribute_offset, + None, + ); + let (data, position) = attribute.non_resident_value_data_and_position(); + let mut stream_data_runs = + NtfsDataRuns::from_state(self.ntfs, data, position, data_runs_state); + + // 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), + }; + 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). + attribute_state.data_runs_state = Some(stream_data_runs.into_state()); + + Ok(true) + } + + /// Returns whether we got another connected attribute. + fn next_attribute(&mut self, fs: &mut T) -> Result + where + T: Read + Seek, + { + // Do we have another connected attribute? + let entry = match self.connected_entries.next(fs) { + Some(entry) => entry, + None => return Ok(false), + }; + + // 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)?; + let attribute_offset = attribute.offset(); + + // Connected attributes must always be non-resident. Verify that. + if attribute.is_resident() { + return Err(NtfsError::UnexpectedResidentAttribute { + position: attribute.position(), + }); + } + + // Get an `NtfsDataRuns` iterator for iterating through the attribute value's data runs. + 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. + let stream_data_run = match stream_data_runs.next() { + Some(stream_data_run) => stream_data_run, + None => return Ok(false), + }; + let stream_data_run = stream_data_run?; + self.stream_state.set_stream_data_run(stream_data_run); + + // Store the `NtfsFile` and serialize the `NtfsDataRuns` state for a later iteration. + let data_runs_state = Some(stream_data_runs.into_state()); + self.attribute_state = Some(AttributeState { + file, + attribute_offset, + data_runs_state, + }); + + Ok(true) + } + + pub fn ntfs(&self) -> &'n Ntfs { + self.ntfs + } +} + +impl<'n, 'f> NtfsReadSeek for NtfsAttributeListNonResidentAttributeValue<'n, 'f> { + fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek, + { + let mut bytes_read = 0usize; + + while bytes_read < buf.len() { + // 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. + if self.next_data_run()? { + // We got another data run of the current attribute, so read again. + continue; + } + + // Move to the first data run of the next connected attribute. + if self.next_attribute(fs)? { + // We got another attribute, so read again. + continue; + } else { + // We read everything we could. + break; + } + } + + Ok(bytes_read) + } + + fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { + let pos = self.stream_state.optimize_seek(pos, self.len())?; + + let mut bytes_left_to_seek = match pos { + SeekFrom::Start(n) => { + // Rewind to the very beginning. + self.connected_entries.attribute_list_entries = + Some(self.initial_attribute_list_entries.clone()); + self.attribute_state = None; + self.stream_state = StreamState::new(self.len()); + n + } + SeekFrom::Current(n) if n >= 0 => n as u64, + _ => unreachable!(), + }; + + while bytes_left_to_seek > 0 { + // Seek inside the current data run if there is one. + if self + .stream_state + .seek_data_run(fs, pos, &mut bytes_left_to_seek)? + { + // We have reached our final seek position. + break; + } + + // 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. + continue; + } + + // 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; + } else { + // We seeked as far as we could. + break; + } + } + + match pos { + SeekFrom::Start(n) => self.stream_state.set_stream_position(n), + SeekFrom::Current(n) => self + .stream_state + .set_stream_position(self.stream_position() + n as u64), + _ => unreachable!(), + } + + Ok(self.stream_position()) + } + + fn stream_position(&self) -> u64 { + self.stream_state.stream_position() + } +} + +#[derive(Clone, Debug)] +struct AttributeListConnectedEntries<'n, 'f> { + attribute_list_entries: Option>, + instance: u16, + ty: NtfsAttributeType, +} + +impl<'n, 'f> AttributeListConnectedEntries<'n, 'f> { + fn new( + attribute_list_entries: NtfsAttributeListEntries<'n, 'f>, + instance: u16, + ty: NtfsAttributeType, + ) -> Self { + Self { + attribute_list_entries: Some(attribute_list_entries), + instance, + ty, + } + } + + fn next(&mut self, fs: &mut T) -> Option> + where + T: Read + Seek, + { + let attribute_list_entries = self.attribute_list_entries.as_mut()?; + + let entry = iter_try!(attribute_list_entries.next(fs)?); + if entry.instance() == self.instance && iter_try!(entry.ty()) == self.ty { + Some(Ok(entry)) + } else { + self.attribute_list_entries = None; + None + } + } +} + +#[derive(Clone, Debug)] +struct AttributeState<'n> { + file: NtfsFile<'n>, + attribute_offset: usize, + /// We cannot store an `NtfsDataRuns` here, because it has a reference to the `NtfsFile` that is also stored here. + /// This is why we have to go via `DataRunsState` in an `Option` to take() it and deserialize it into an `NtfsDataRuns` whenever necessary. + data_runs_state: Option, +} diff --git a/src/attribute_value/mod.rs b/src/attribute_value/mod.rs new file mode 100644 index 0000000..f7f359f --- /dev/null +++ b/src/attribute_value/mod.rs @@ -0,0 +1,158 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later + +mod attribute_list_non_resident; +mod non_resident; +mod resident; + +pub use attribute_list_non_resident::*; +pub use non_resident::*; +pub use resident::*; + +use binread::io; +use binread::io::{Read, Seek, SeekFrom}; + +use crate::error::{NtfsError, Result}; +use crate::traits::NtfsReadSeek; + +#[derive(Clone, Debug)] +pub enum NtfsAttributeValue<'n, 'f> { + Resident(NtfsResidentAttributeValue<'f>), + NonResident(NtfsNonResidentAttributeValue<'n, 'f>), + AttributeListNonResident(NtfsAttributeListNonResidentAttributeValue<'n, 'f>), +} + +impl<'n, 'f> NtfsAttributeValue<'n, 'f> { + pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributeValueAttached<'n, 'f, 'a, T> + where + T: Read + Seek, + { + NtfsAttributeValueAttached::new(fs, self) + } + + pub fn data_position(&self) -> Option { + match self { + Self::Resident(inner) => inner.data_position(), + Self::NonResident(inner) => inner.data_position(), + Self::AttributeListNonResident(inner) => inner.data_position(), + } + } + + pub fn len(&self) -> u64 { + match self { + Self::Resident(inner) => inner.len(), + Self::NonResident(inner) => inner.len(), + Self::AttributeListNonResident(inner) => inner.len(), + } + } +} + +impl<'n, 'f> NtfsReadSeek for NtfsAttributeValue<'n, 'f> { + fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek, + { + match self { + Self::Resident(inner) => inner.read(fs, buf), + Self::NonResident(inner) => inner.read(fs, buf), + Self::AttributeListNonResident(inner) => inner.read(fs, buf), + } + } + + fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { + match self { + Self::Resident(inner) => inner.seek(fs, pos), + Self::NonResident(inner) => inner.seek(fs, pos), + Self::AttributeListNonResident(inner) => inner.seek(fs, pos), + } + } + + fn stream_position(&self) -> u64 { + match self { + Self::Resident(inner) => inner.stream_position(), + Self::NonResident(inner) => inner.stream_position(), + Self::AttributeListNonResident(inner) => inner.stream_position(), + } + } +} + +pub struct NtfsAttributeValueAttached<'n, 'f, 'a, T: Read + Seek> { + fs: &'a mut T, + value: NtfsAttributeValue<'n, 'f>, +} + +impl<'n, 'f, 'a, T> NtfsAttributeValueAttached<'n, 'f, 'a, T> +where + T: Read + Seek, +{ + fn new(fs: &'a mut T, value: NtfsAttributeValue<'n, 'f>) -> Self { + Self { fs, value } + } + + pub fn data_position(&self) -> Option { + self.value.data_position() + } + + pub fn detach(self) -> NtfsAttributeValue<'n, 'f> { + self.value + } + + pub fn len(&self) -> u64 { + self.value.len() + } +} + +impl<'n, 'f, 'a, T> Read for NtfsAttributeValueAttached<'n, 'f, 'a, T> +where + T: Read + Seek, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.value.read(self.fs, buf).map_err(io::Error::from) + } +} + +impl<'n, 'f, 'a, T> Seek for NtfsAttributeValueAttached<'n, 'f, 'a, T> +where + T: Read + Seek, +{ + fn seek(&mut self, pos: SeekFrom) -> io::Result { + self.value.seek(self.fs, pos).map_err(io::Error::from) + } +} + +pub(crate) fn seek_contiguous( + stream_position: &mut u64, + length: u64, + pos: SeekFrom, +) -> Result { + // This implementation is taken from https://github.com/rust-lang/rust/blob/18c524fbae3ab1bf6ed9196168d8c68fc6aec61a/library/std/src/io/cursor.rs + // It handles all signed/unsigned arithmetics properly and outputs the known `io` error message. + let (base_pos, offset) = match pos { + SeekFrom::Start(n) => { + *stream_position = n; + return Ok(n); + } + SeekFrom::End(n) => (length, n), + SeekFrom::Current(n) => (*stream_position, n), + }; + + let new_pos = if offset >= 0 { + base_pos.checked_add(offset as u64) + } else { + base_pos.checked_sub(offset.wrapping_neg() as u64) + }; + + match new_pos { + Some(n) => { + *stream_position = n; + Ok(*stream_position) + } + None => Err(NtfsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to a negative or overflowing position", + ))), + } +} diff --git a/src/attribute_value/non_resident.rs b/src/attribute_value/non_resident.rs new file mode 100644 index 0000000..4639261 --- /dev/null +++ b/src/attribute_value/non_resident.rs @@ -0,0 +1,670 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later +// +//! This module implements a reader for a non-resident attribute value (that is not part of an AttributeList). +//! 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. + +use core::convert::TryFrom; +use core::iter::FusedIterator; +use core::mem; + +use binread::io; +use binread::io::Cursor; +use binread::io::{Read, Seek, SeekFrom}; +use binread::BinRead; + +use super::seek_contiguous; +use crate::error::{NtfsError, Result}; +use crate::ntfs::Ntfs; +use crate::traits::NtfsReadSeek; +use crate::types::{Lcn, Vcn}; + +#[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. + data: &'f [u8], + /// 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. + stream_state: StreamState, +} + +impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> { + pub(crate) fn new( + ntfs: &'n Ntfs, + data: &'f [u8], + position: u64, + data_size: u64, + ) -> Result { + 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. + 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); + } + + Ok(Self { + ntfs, + data, + position, + stream_data_runs, + stream_state, + }) + } + + pub fn attach<'a, T>( + self, + fs: &'a mut T, + ) -> NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> + where + T: Read + Seek, + { + NtfsNonResidentAttributeValueAttached::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 { + self.stream_state.data_position() + } + + pub fn data_runs(&self) -> NtfsDataRuns<'n, 'f> { + NtfsDataRuns::new(self.ntfs, self.data, self.position) + } + + pub fn len(&self) -> u64 { + self.stream_state.data_size() + } + + /// Returns whether we got another data run. + fn next_data_run(&mut self) -> Result { + let stream_data_run = match self.stream_data_runs.next() { + Some(stream_data_run) => stream_data_run, + None => return Ok(false), + }; + let stream_data_run = stream_data_run?; + self.stream_state.set_stream_data_run(stream_data_run); + + Ok(true) + } + + pub fn ntfs(&self) -> &'n Ntfs { + self.ntfs + } + + /// Returns the absolute position of the data run information within the filesystem, in bytes. + pub fn position(&self) -> u64 { + self.position + } +} + +impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> { + fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek, + { + let mut bytes_read = 0usize; + + while bytes_read < buf.len() { + // 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. + if self.next_data_run()? { + // We got another data run, so read again. + continue; + } else { + // We read everything we could. + break; + } + } + + Ok(bytes_read) + } + + fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { + let pos = self.stream_state.optimize_seek(pos, self.len())?; + + let mut bytes_left_to_seek = match pos { + SeekFrom::Start(n) => { + // Rewind to the very beginning. + self.stream_data_runs = self.data_runs(); + self.stream_state = StreamState::new(self.len()); + n + } + SeekFrom::Current(n) if n >= 0 => n as u64, + _ => unreachable!(), + }; + + while bytes_left_to_seek > 0 { + // Seek inside the current data run if there is one. + if self + .stream_state + .seek_data_run(fs, pos, &mut bytes_left_to_seek)? + { + // We have reached our final seek position. + break; + } + + // Move to the next data run. + if self.next_data_run()? { + // We got another data run, so seek some more. + continue; + } else { + // We seeked as far as we could. + break; + } + } + + match pos { + SeekFrom::Start(n) => self.stream_state.set_stream_position(n), + SeekFrom::Current(n) => self + .stream_state + .set_stream_position(self.stream_position() + n as u64), + _ => unreachable!(), + } + + Ok(self.stream_position()) + } + + fn stream_position(&self) -> u64 { + self.stream_state.stream_position() + } +} + +pub struct NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T: Read + Seek> { + fs: &'a mut T, + value: NtfsNonResidentAttributeValue<'n, 'f>, +} + +impl<'n, 'f, 'a, T> NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> +where + T: Read + Seek, +{ + fn new(fs: &'a mut T, value: NtfsNonResidentAttributeValue<'n, 'f>) -> Self { + Self { fs, value } + } + + pub fn data_position(&self) -> Option { + self.value.data_position() + } + + pub fn detach(self) -> NtfsNonResidentAttributeValue<'n, 'f> { + self.value + } + + pub fn len(&self) -> u64 { + self.value.len() + } +} + +impl<'n, 'f, 'a, T> Read for NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> +where + T: Read + Seek, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.value.read(self.fs, buf).map_err(io::Error::from) + } +} + +impl<'n, 'f, 'a, T> Seek for NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> +where + T: Read + Seek, +{ + fn seek(&mut self, pos: SeekFrom) -> io::Result { + self.value.seek(self.fs, pos).map_err(io::Error::from) + } +} + +#[derive(Clone, Debug)] +pub struct NtfsDataRuns<'n, 'f> { + ntfs: &'n Ntfs, + data: &'f [u8], + position: u64, + state: DataRunsState, +} + +impl<'n, 'f> NtfsDataRuns<'n, 'f> { + pub(crate) fn new(ntfs: &'n Ntfs, data: &'f [u8], position: u64) -> Self { + let state = DataRunsState { + offset: 0, + previous_lcn: Lcn::from(0), + }; + + Self { + ntfs, + data, + position, + state, + } + } + + pub(crate) fn from_state( + ntfs: &'n Ntfs, + data: &'f [u8], + position: u64, + state: DataRunsState, + ) -> Self { + Self { + ntfs, + data, + position, + state, + } + } + + pub(crate) fn into_state(self) -> DataRunsState { + self.state + } + + pub fn position(&self) -> u64 { + self.position + self.state.offset as u64 + } + + fn read_variable_length_bytes( + &self, + cursor: &mut Cursor<&[u8]>, + byte_count: u8, + ) -> Result<[u8; 8]> { + const MAX_BYTE_COUNT: u8 = mem::size_of::() as u8; + + if byte_count > MAX_BYTE_COUNT { + return Err(NtfsError::InvalidByteCountInDataRunHeader { + position: self.position(), + expected: byte_count, + actual: MAX_BYTE_COUNT, + }); + } + + let mut buf = [0u8; MAX_BYTE_COUNT as usize]; + cursor.read_exact(&mut buf[..byte_count as usize])?; + + Ok(buf) + } + + fn read_variable_length_signed_integer( + &self, + cursor: &mut Cursor<&[u8]>, + byte_count: u8, + ) -> Result { + let buf = self.read_variable_length_bytes(cursor, byte_count)?; + let mut integer = i64::from_le_bytes(buf); + + // We have read `byte_count` bytes into a zeroed buffer and just interpreted that as an `i64`. + // Sign-extend `integer` to make it replicate the proper value. + let unused_bits = (mem::size_of::() as u32 - byte_count as u32) * 8; + integer = integer.wrapping_shl(unused_bits).wrapping_shr(unused_bits); + + Ok(integer) + } + + fn read_variable_length_unsigned_integer( + &self, + cursor: &mut Cursor<&[u8]>, + byte_count: u8, + ) -> Result { + let buf = self.read_variable_length_bytes(cursor, byte_count)?; + let integer = u64::from_le_bytes(buf); + Ok(integer) + } +} + +impl<'n, 'f> Iterator for NtfsDataRuns<'n, 'f> { + type Item = Result; + + fn next(&mut self) -> Option> { + if self.state.offset >= self.data.len() { + return None; + } + + // Read the single header byte. + let mut cursor = Cursor::new(&self.data[self.state.offset..]); + let header = iter_try!(u8::read(&mut cursor)); + + // A zero byte marks the end of the data runs. + if header == 0 { + // Ensure that any further call uses the fast path above. + self.state.offset = self.data.len(); + return None; + } + + // The lower nibble indicates the length of the following cluster count variable length integer. + let cluster_count_byte_count = header & 0x0f; + let cluster_count = iter_try!( + self.read_variable_length_unsigned_integer(&mut cursor, cluster_count_byte_count) + ); + + // The upper nibble indicates the length of the following VCN variable length integer. + let vcn_byte_count = (header & 0xf0) >> 4; + let vcn = Vcn::from(iter_try!( + self.read_variable_length_signed_integer(&mut cursor, vcn_byte_count) + )); + + // Turn the read VCN into an absolute LCN. + let lcn = iter_try!(self.state.previous_lcn.checked_add(vcn).ok_or({ + NtfsError::InvalidVcnInDataRunHeader { + position: NtfsDataRuns::position(self), + vcn, + previous_lcn: self.state.previous_lcn, + } + })); + self.state.previous_lcn = lcn; + + // Only advance after having checked for success. + // In case of an error, a subsequent call shall output the same error again. + let bytes_to_advance = cursor.stream_position().unwrap() as usize; + self.state.offset += bytes_to_advance; + + let data_run = iter_try!(NtfsDataRun::new(self.ntfs, lcn, cluster_count)); + Some(Ok(data_run)) + } +} + +impl<'n, 'f> FusedIterator for NtfsDataRuns<'n, 'f> {} + +#[derive(Clone, Debug)] +pub(crate) struct DataRunsState { + offset: usize, + previous_lcn: Lcn, +} + +/// Describes 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. +/// 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. + 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. + allocated_size: u64, + /// Current relative position within the data run value, in bytes. + stream_position: u64, +} + +impl NtfsDataRun { + pub(crate) fn new(ntfs: &Ntfs, lcn: Lcn, cluster_count: u64) -> Result { + let position = lcn.position(ntfs)?; + let allocated_size = cluster_count + .checked_mul(ntfs.cluster_size() as u64) + .ok_or(NtfsError::InvalidClusterCount { cluster_count })?; + + Ok(Self { + position, + allocated_size, + stream_position: 0, + }) + } + + /// Returns the absolute current data seek position within the filesystem, in bytes. + /// This may be `None` if: + /// * The current seek position is outside the valid range, or + /// * The data run is a "sparse" data run + pub fn data_position(&self) -> Option { + if self.position > 0 && self.stream_position < self.len() { + Some(self.position + self.stream_position) + } else { + None + } + } + + pub fn len(&self) -> u64 { + self.allocated_size + } + + pub(crate) fn remaining_len(&self) -> u64 { + self.len().saturating_sub(self.stream_position) + } +} + +impl NtfsReadSeek for NtfsDataRun { + fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek, + { + if self.remaining_len() == 0 { + return Ok(0); + } + + let bytes_to_read = usize::min(buf.len(), self.remaining_len() as usize); + let work_slice = &mut buf[..bytes_to_read]; + + if self.position == 0 { + // This is a sparse data run. + work_slice.fill(0); + } else { + // 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)?; + } + + self.stream_position += bytes_to_read as u64; + Ok(bytes_to_read) + } + + fn seek(&mut self, _fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { + let length = self.len(); + seek_contiguous(&mut self.stream_position, length, pos) + } + + fn stream_position(&self) -> u64 { + self.stream_position + } +} + +#[derive(Clone, Debug)] +pub(crate) struct StreamState { + /// Current data run we are reading from. + stream_data_run: Option, + /// Current relative position within the entire value, in bytes. + stream_position: u64, + /// Total (used) data size, in bytes. + data_size: u64, +} + +impl StreamState { + pub(crate) const fn new(data_size: u64) -> Self { + Self { + stream_data_run: None, + stream_position: 0, + data_size, + } + } + + /// 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(crate) fn data_position(&self) -> Option { + let stream_data_run = self.stream_data_run.as_ref()?; + stream_data_run.data_position() + } + + /// Returns the total (used) data size of the value, in bytes. + pub(crate) fn data_size(&self) -> u64 { + self.data_size + } + + pub(crate) fn optimize_seek(&self, pos: SeekFrom, data_size: u64) -> Result { + let mut pos = self.simplify_seek(pos, data_size)?; + + // Translate `SeekFrom::Start(n)` into a more efficient `SeekFrom::Current` if n >= self.stream_position. + // We don't need to traverse data runs from the very beginning then. + if let SeekFrom::Start(n) = pos { + if let Some(n_from_current) = n.checked_sub(self.stream_position()) { + if let Ok(signed_n_from_current) = i64::try_from(n_from_current) { + pos = SeekFrom::Current(signed_n_from_current); + } + } + } + + Ok(pos) + } + + /// 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. + /// Hence, we can't efficiently move backwards. + fn simplify_seek(&self, pos: SeekFrom, data_size: u64) -> Result { + match pos { + SeekFrom::Start(n) => { + // Seek n bytes from the very beginning. + return Ok(SeekFrom::Start(n)); + } + SeekFrom::End(n) => { + if n >= 0 { + if let Some(bytes_to_seek) = data_size.checked_add(n as u64) { + // Seek data_size + n bytes from the very beginning. + return Ok(SeekFrom::Start(bytes_to_seek)); + } + } else { + if let Some(bytes_to_seek) = data_size.checked_sub(n.wrapping_neg() as u64) { + // Seek data_size + n bytes (with n being negative) from the very beginning. + return Ok(SeekFrom::Start(bytes_to_seek)); + } + } + } + SeekFrom::Current(n) => { + if n >= 0 { + if self.stream_position().checked_add(n as u64).is_some() { + // Seek n bytes from the current position. + // This is an optimization for the common case, as we don't need to traverse all + // data runs from the very beginning. + return Ok(SeekFrom::Current(n)); + } + } else { + if let Some(bytes_to_seek) = + self.stream_position().checked_sub(n.wrapping_neg() as u64) + { + // Seek stream_position + n bytes (with n being negative) from the very beginning. + return Ok(SeekFrom::Start(bytes_to_seek)); + } + } + } + } + + Err(NtfsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to a negative or overflowing position", + ))) + } + + /// Returns whether we read some bytes. + pub(crate) fn read_data_run( + &mut self, + fs: &mut T, + buf: &mut [u8], + bytes_read: &mut usize, + ) -> Result + where + T: Read + Seek, + { + // 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? + 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!) + let remaining_data_size = self.data_size.saturating_sub(self.stream_position); + if remaining_data_size == 0 { + return Ok(false); + } + + // Read up to the buffer length or up to the (used) data size, whatever comes first. + let start = *bytes_read; + let remaining_buf_len = buf.len() - start; + let end = start + usize::min(remaining_buf_len, remaining_data_size as usize); + + // Perform the actual read. + let bytes_read_in_data_run = data_run.read(fs, &mut buf[start..end])?; + *bytes_read += bytes_read_in_data_run; + self.stream_position += bytes_read_in_data_run as u64; + + Ok(true) + } + + /// 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. + pub(crate) fn seek_data_run( + &mut self, + fs: &mut T, + bytes_to_seek: SeekFrom, + bytes_left_to_seek: &mut u64, + ) -> Result + where + T: Read + Seek, + { + // 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. + // + // If we were called to seek from the very beginning, we can be sure that this + // 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 + // SeekFrom::Current and can only use the positive part of the i64 range. + // This is no problem though, as `bytes_left_to_seek` was also created from a + // positive i64 value in that case. + let pos = match bytes_to_seek { + SeekFrom::Start(_) => SeekFrom::Start(*bytes_left_to_seek), + SeekFrom::Current(_) => SeekFrom::Current(*bytes_left_to_seek as i64), + _ => unreachable!(), + }; + + data_run.seek(fs, pos)?; + Ok(true) + } else { + // We can skip the entire data run. + *bytes_left_to_seek -= data_run.remaining_len(); + Ok(false) + } + } + + pub(crate) fn set_stream_data_run(&mut self, stream_data_run: NtfsDataRun) { + self.stream_data_run = Some(stream_data_run); + } + + pub(crate) fn set_stream_position(&mut self, stream_position: u64) { + self.stream_position = stream_position; + } + + /// Returns the current relative position within the entire value, in bytes. + pub(crate) fn stream_position(&self) -> u64 { + self.stream_position + } +} diff --git a/src/attribute_value/resident.rs b/src/attribute_value/resident.rs new file mode 100644 index 0000000..a81fb8f --- /dev/null +++ b/src/attribute_value/resident.rs @@ -0,0 +1,86 @@ +// Copyright 2021 Colin Finck +// 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. +//! 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. + +use binread::io::{Read, Seek, SeekFrom}; + +use super::seek_contiguous; +use crate::error::Result; +use crate::traits::NtfsReadSeek; + +#[derive(Clone, Debug)] +pub struct NtfsResidentAttributeValue<'f> { + data: &'f [u8], + position: u64, + stream_position: u64, +} + +impl<'f> NtfsResidentAttributeValue<'f> { + pub(crate) fn new(data: &'f [u8], position: u64) -> Self { + Self { + data, + position, + stream_position: 0, + } + } + + pub fn data(&self) -> &'f [u8] { + self.data + } + + /// Returns the absolute current data seek position within the filesystem, in bytes. + /// This may be `None` if the current seek position is outside the valid range. + pub fn data_position(&self) -> Option { + if self.stream_position < self.len() { + Some(self.position + self.stream_position) + } else { + None + } + } + + pub fn len(&self) -> u64 { + self.data.len() as u64 + } + + fn remaining_len(&self) -> u64 { + self.len().saturating_sub(self.stream_position) + } +} + +impl<'f> NtfsReadSeek for NtfsResidentAttributeValue<'f> { + fn read(&mut self, _fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek, + { + if self.remaining_len() == 0 { + return Ok(0); + } + + let bytes_to_read = usize::min(buf.len(), self.remaining_len() as usize); + let work_slice = &mut buf[..bytes_to_read]; + + let start = self.stream_position as usize; + let end = start + bytes_to_read; + work_slice.copy_from_slice(&self.data[start..end]); + + self.stream_position += bytes_to_read as u64; + Ok(bytes_to_read) + } + + fn seek(&mut self, _fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { + let length = self.len(); + seek_contiguous(&mut self.stream_position, length, pos) + } + + fn stream_position(&self) -> u64 { + self.stream_position + } +} diff --git a/src/index_record.rs b/src/index_record.rs index 4c4978a..d73827d 100644 --- a/src/index_record.rs +++ b/src/index_record.rs @@ -1,6 +1,7 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +use crate::attribute_value::NtfsAttributeValue; use crate::error::{NtfsError, Result}; use crate::index_entry::{IndexNodeEntryRanges, NtfsIndexNodeEntries}; use crate::indexes::NtfsIndexEntryType; @@ -9,7 +10,6 @@ use crate::record::Record; use crate::record::RecordHeader; use crate::traits::NtfsReadSeek; use crate::types::Vcn; -use crate::value::NtfsValue; use binread::io::{Read, Seek}; use byteorder::{ByteOrder, LittleEndian}; use core::ops::Range; @@ -46,7 +46,7 @@ impl<'n> NtfsIndexRecord<'n> { pub(crate) fn new( ntfs: &'n Ntfs, fs: &mut T, - mut value: NtfsValue<'n, '_>, + mut value: NtfsAttributeValue<'n, '_>, index_record_size: u32, ) -> Result where diff --git a/src/lib.rs b/src/lib.rs index 7eef943..e32b5f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate alloc; mod helpers; mod attribute; +pub mod attribute_value; mod boot_sector; mod error; mod file; @@ -27,7 +28,6 @@ mod time; mod traits; mod types; mod upcase_table; -pub mod value; pub use crate::attribute::*; pub use crate::error::*; diff --git a/src/structured_values/attribute_list.rs b/src/structured_values/attribute_list.rs index 27b502f..e336e84 100644 --- a/src/structured_values/attribute_list.rs +++ b/src/structured_values/attribute_list.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::{NtfsAttribute, NtfsAttributeType}; +use crate::attribute_value::{NtfsAttributeValue, NtfsNonResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::file::NtfsFile; use crate::file_reference::NtfsFileReference; @@ -10,8 +11,6 @@ use crate::string::NtfsString; use crate::structured_values::NtfsStructuredValue; use crate::traits::NtfsReadSeek; use crate::types::Vcn; -use crate::value::non_resident_attribute::NtfsNonResidentAttributeValue; -use crate::value::NtfsValue; use arrayvec::ArrayVec; use binread::io::{Cursor, Read, Seek, SeekFrom}; use binread::{BinRead, BinReaderExt}; @@ -67,18 +66,18 @@ impl<'n, 'f> NtfsAttributeList<'n, 'f> { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsAttributeList<'n, 'f> { const TY: NtfsAttributeType = NtfsAttributeType::AttributeList; - fn from_value(_fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(_fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { match value { - NtfsValue::Slice(value) => { + NtfsAttributeValue::Resident(value) => { let slice = value.data(); let position = value.data_position().unwrap(); Ok(Self::Resident(slice, position)) } - NtfsValue::NonResidentAttribute(value) => Ok(Self::NonResident(value)), - NtfsValue::AttributeListNonResidentAttribute(value) => { + NtfsAttributeValue::NonResident(value) => Ok(Self::NonResident(value)), + NtfsAttributeValue::AttributeListNonResident(value) => { // Attribute Lists are never nested. // Hence, we must not create this attribute from an attribute that is already part of Attribute List. let position = value.data_position().unwrap(); diff --git a/src/structured_values/file_name.rs b/src/structured_values/file_name.rs index 3628495..056bfa3 100644 --- a/src/structured_values/file_name.rs +++ b/src/structured_values/file_name.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::NtfsAttributeValue; use crate::error::{NtfsError, Result}; use crate::file_reference::NtfsFileReference; use crate::indexes::NtfsIndexEntryKey; use crate::string::NtfsString; use crate::structured_values::{NtfsFileAttributeFlags, NtfsStructuredValue}; use crate::time::NtfsTime; -use crate::value::NtfsValue; use arrayvec::ArrayVec; use binread::io::{Cursor, Read, Seek}; use binread::{BinRead, BinReaderExt}; @@ -180,7 +180,7 @@ impl NtfsFileName { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsFileName { const TY: NtfsAttributeType = NtfsAttributeType::FileName; - fn from_value(fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { diff --git a/src/structured_values/index_allocation.rs b/src/structured_values/index_allocation.rs index 0f6742d..01da45d 100644 --- a/src/structured_values/index_allocation.rs +++ b/src/structured_values/index_allocation.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::NtfsAttributeValue; use crate::error::{NtfsError, Result}; use crate::index_record::NtfsIndexRecord; use crate::ntfs::Ntfs; @@ -9,14 +10,13 @@ use crate::structured_values::index_root::NtfsIndexRoot; use crate::structured_values::NtfsStructuredValue; use crate::traits::NtfsReadSeek; use crate::types::Vcn; -use crate::value::NtfsValue; use binread::io::{Read, Seek, SeekFrom}; use core::iter::FusedIterator; #[derive(Clone, Debug)] pub struct NtfsIndexAllocation<'n, 'f> { ntfs: &'n Ntfs, - value: NtfsValue<'n, 'f>, + value: NtfsAttributeValue<'n, 'f>, } impl<'n, 'f> NtfsIndexAllocation<'n, 'f> { @@ -66,14 +66,14 @@ impl<'n, 'f> NtfsIndexAllocation<'n, 'f> { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsIndexAllocation<'n, 'f> { const TY: NtfsAttributeType = NtfsAttributeType::IndexAllocation; - fn from_value(_fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(_fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { let ntfs = match &value { - NtfsValue::AttributeListNonResidentAttribute(value) => value.ntfs(), - NtfsValue::NonResidentAttribute(value) => value.ntfs(), - NtfsValue::Slice(_) => { + NtfsAttributeValue::AttributeListNonResident(value) => value.ntfs(), + NtfsAttributeValue::NonResident(value) => value.ntfs(), + NtfsAttributeValue::Resident(_) => { let position = value.data_position().unwrap(); return Err(NtfsError::UnexpectedResidentAttribute { position }); } diff --git a/src/structured_values/index_root.rs b/src/structured_values/index_root.rs index 9c1cb2f..86a12af 100644 --- a/src/structured_values/index_root.rs +++ b/src/structured_values/index_root.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::index_entry::{IndexNodeEntryRanges, NtfsIndexNodeEntries}; use crate::index_record::{IndexNodeHeader, INDEX_NODE_HEADER_SIZE}; @@ -9,8 +10,6 @@ use crate::indexes::NtfsIndexEntryType; use crate::structured_values::{ NtfsStructuredValue, NtfsStructuredValueFromResidentAttributeValue, }; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use binread::io::{Read, Seek}; use byteorder::{ByteOrder, LittleEndian}; use core::ops::Range; @@ -139,25 +138,25 @@ impl<'f> NtfsIndexRoot<'f> { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsIndexRoot<'f> { const TY: NtfsAttributeType = NtfsAttributeType::IndexRoot; - fn from_value(_fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(_fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { - let slice_value = match value { - NtfsValue::Slice(slice_value) => slice_value, + let resident_value = match value { + NtfsAttributeValue::Resident(resident_value) => resident_value, _ => { let position = value.data_position().unwrap(); return Err(NtfsError::UnexpectedNonResidentAttribute { position }); } }; - let position = slice_value.data_position().unwrap(); - Self::new(slice_value.data(), position) + let position = resident_value.data_position().unwrap(); + Self::new(resident_value.data(), position) } } impl<'n, 'f> NtfsStructuredValueFromResidentAttributeValue<'n, 'f> for NtfsIndexRoot<'f> { - fn from_resident_attribute_value(value: NtfsSliceValue<'f>) -> Result { + fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result { Self::new(value.data(), value.data_position().unwrap()) } } diff --git a/src/structured_values/mod.rs b/src/structured_values/mod.rs index c965516..d8fcc0d 100644 --- a/src/structured_values/mod.rs +++ b/src/structured_values/mod.rs @@ -22,9 +22,8 @@ pub use volume_information::*; pub use volume_name::*; use crate::attribute::NtfsAttributeType; +use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::Result; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use binread::io::{Read, Seek}; use bitflags::bitflags; @@ -50,8 +49,8 @@ bitflags! { pub trait NtfsStructuredValue<'n, 'f>: Sized { const TY: NtfsAttributeType; - /// Create a structured value from an arbitrary `NtfsValue`. - fn from_value(fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + /// Create a structured value from an arbitrary `NtfsAttributeValue`. + fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek; } @@ -61,5 +60,5 @@ pub trait NtfsStructuredValue<'n, 'f>: Sized { pub trait NtfsStructuredValueFromResidentAttributeValue<'n, 'f>: NtfsStructuredValue<'n, 'f> { - fn from_resident_attribute_value(value: NtfsSliceValue<'f>) -> Result; + fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result; } diff --git a/src/structured_values/object_id.rs b/src/structured_values/object_id.rs index 1efec3a..3f03cc6 100644 --- a/src/structured_values/object_id.rs +++ b/src/structured_values/object_id.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::guid::{NtfsGuid, GUID_SIZE}; use crate::structured_values::{ NtfsStructuredValue, NtfsStructuredValueFromResidentAttributeValue, }; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use binread::io::{Cursor, Read, Seek}; use binread::BinReaderExt; @@ -79,7 +78,7 @@ impl NtfsObjectId { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsObjectId { const TY: NtfsAttributeType = NtfsAttributeType::ObjectId; - fn from_value(fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { @@ -92,7 +91,7 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsObjectId { } impl<'n, 'f> NtfsStructuredValueFromResidentAttributeValue<'n, 'f> for NtfsObjectId { - fn from_resident_attribute_value(value: NtfsSliceValue<'f>) -> Result { + fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result { let position = value.data_position().unwrap(); let value_length = value.len(); diff --git a/src/structured_values/standard_information.rs b/src/structured_values/standard_information.rs index 01a1771..5ae8891 100644 --- a/src/structured_values/standard_information.rs +++ b/src/structured_values/standard_information.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::structured_values::{ NtfsFileAttributeFlags, NtfsStructuredValue, NtfsStructuredValueFromResidentAttributeValue, }; use crate::time::NtfsTime; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use binread::io::{Cursor, Read, Seek}; use binread::{BinRead, BinReaderExt}; @@ -123,7 +122,7 @@ impl NtfsStandardInformation { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsStandardInformation { const TY: NtfsAttributeType = NtfsAttributeType::StandardInformation; - fn from_value(fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { @@ -136,7 +135,7 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsStandardInformation { } impl<'n, 'f> NtfsStructuredValueFromResidentAttributeValue<'n, 'f> for NtfsStandardInformation { - fn from_resident_attribute_value(value: NtfsSliceValue<'f>) -> Result { + fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result { let position = value.data_position().unwrap(); let value_length = value.len(); diff --git a/src/structured_values/volume_information.rs b/src/structured_values/volume_information.rs index ea235ac..ff16018 100644 --- a/src/structured_values/volume_information.rs +++ b/src/structured_values/volume_information.rs @@ -2,12 +2,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::structured_values::{ NtfsStructuredValue, NtfsStructuredValueFromResidentAttributeValue, }; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use binread::io::{Cursor, Read, Seek}; use binread::{BinRead, BinReaderExt}; use bitflags::bitflags; @@ -77,7 +76,7 @@ impl NtfsVolumeInformation { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsVolumeInformation { const TY: NtfsAttributeType = NtfsAttributeType::VolumeInformation; - fn from_value(fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { @@ -90,7 +89,7 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsVolumeInformation { } impl<'n, 'f> NtfsStructuredValueFromResidentAttributeValue<'n, 'f> for NtfsVolumeInformation { - fn from_resident_attribute_value(value: NtfsSliceValue<'f>) -> Result { + fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result { let position = value.data_position().unwrap(); let value_length = value.len(); diff --git a/src/structured_values/volume_name.rs b/src/structured_values/volume_name.rs index ce51386..b2cc823 100644 --- a/src/structured_values/volume_name.rs +++ b/src/structured_values/volume_name.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; +use crate::attribute_value::{NtfsAttributeValue, NtfsResidentAttributeValue}; use crate::error::{NtfsError, Result}; use crate::string::NtfsString; use crate::structured_values::{ NtfsStructuredValue, NtfsStructuredValueFromResidentAttributeValue, }; -use crate::value::slice::NtfsSliceValue; -use crate::value::NtfsValue; use arrayvec::ArrayVec; use binread::io::{Cursor, Read, Seek}; use core::mem; @@ -70,7 +69,7 @@ impl NtfsVolumeName { impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsVolumeName { const TY: NtfsAttributeType = NtfsAttributeType::VolumeName; - fn from_value(fs: &mut T, value: NtfsValue<'n, 'f>) -> Result + fn from_attribute_value(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result where T: Read + Seek, { @@ -83,7 +82,7 @@ impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsVolumeName { } impl<'n, 'f> NtfsStructuredValueFromResidentAttributeValue<'n, 'f> for NtfsVolumeName { - fn from_resident_attribute_value(value: NtfsSliceValue<'f>) -> Result { + fn from_resident_attribute_value(value: NtfsResidentAttributeValue<'f>) -> Result { let position = value.data_position().unwrap(); let value_length = value.len(); diff --git a/src/value/attribute_list_non_resident_attribute.rs b/src/value/attribute_list_non_resident_attribute.rs deleted file mode 100644 index 8e75724..0000000 --- a/src/value/attribute_list_non_resident_attribute.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2021 Colin Finck -// SPDX-License-Identifier: GPL-2.0-or-later -// -//! This module implements a reader for a non-resident attribute value that is part of an AttributeList. -//! Such values are not only split up into data runs, but may also be continued by connected attributes which are listed in the same AttributeList. -//! This reader provides one contiguous data stream for all data runs in all connected attributes. -// -// It is important to note that `NtfsAttributeListNonResidentAttributeValue` can't just encapsulate `NtfsNonResidentAttributeValue` and provide one -// layer on top to connect the attributes! -// Connected attributes are stored in a way that the first attribute reports the entire data size and all further attributes report a zero value length. -// We have to go down to the data run level to get trustable lengths again, and this is what `NtfsAttributeListNonResidentAttributeValue` does here. - -use crate::attribute::{NtfsAttribute, NtfsAttributeType}; -use crate::error::{NtfsError, Result}; -use crate::file::NtfsFile; -use crate::ntfs::Ntfs; -use crate::structured_values::{NtfsAttributeListEntries, NtfsAttributeListEntry}; -use crate::traits::NtfsReadSeek; -use crate::value::non_resident_attribute::{DataRunsState, NtfsDataRuns, StreamState}; -use binread::io::{Read, Seek, SeekFrom}; - -#[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. - connected_entries: AttributeListConnectedEntries<'n, 'f>, - /// File, location, and data runs iteration state of the current attribute. - attribute_state: Option>, - /// Iteration state of the current data run. - stream_state: StreamState, -} - -impl<'n, 'f> NtfsAttributeListNonResidentAttributeValue<'n, 'f> { - pub(crate) fn new( - ntfs: &'n Ntfs, - attribute_list_entries: NtfsAttributeListEntries<'n, 'f>, - instance: u16, - ty: NtfsAttributeType, - data_size: u64, - ) -> Self { - let connected_entries = - AttributeListConnectedEntries::new(attribute_list_entries.clone(), instance, ty); - - Self { - ntfs, - initial_attribute_list_entries: attribute_list_entries, - connected_entries, - attribute_state: None, - stream_state: StreamState::new(data_size), - } - } - - /// Returns the absolute current data seek position within the filesystem, in bytes. - /// This may be `None` if: - /// * The current seek position is outside the valid range, or - /// * The current data run is a "sparse" data run - pub fn data_position(&self) -> Option { - self.stream_state.data_position() - } - - pub fn len(&self) -> u64 { - self.stream_state.data_size() - } - - /// Returns whether we got another data run. - fn next_data_run(&mut self) -> Result { - // Do we have a file and a (non-resident) attribute to iterate through its data runs? - let attribute_state = match &mut self.attribute_state { - Some(attribute_state) => attribute_state, - None => return Ok(false), - }; - - // Get the state of the `NtfsDataRuns` iterator of that attribute. - let data_runs_state = match attribute_state.data_runs_state.take() { - Some(data_runs_state) => data_runs_state, - None => return Ok(false), - }; - - // Deserialize the state into an `NtfsDataRuns` iterator. - let attribute = NtfsAttribute::new( - &attribute_state.file, - attribute_state.attribute_offset, - None, - ); - let (data, position) = attribute.non_resident_value_data_and_position(); - let mut stream_data_runs = - NtfsDataRuns::from_state(self.ntfs, data, position, data_runs_state); - - // 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), - }; - 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). - attribute_state.data_runs_state = Some(stream_data_runs.into_state()); - - Ok(true) - } - - /// Returns whether we got another connected attribute. - fn next_attribute(&mut self, fs: &mut T) -> Result - where - T: Read + Seek, - { - // Do we have another connected attribute? - let entry = match self.connected_entries.next(fs) { - Some(entry) => entry, - None => return Ok(false), - }; - - // 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)?; - let attribute_offset = attribute.offset(); - - // Connected attributes must always be non-resident. Verify that. - if attribute.is_resident() { - return Err(NtfsError::UnexpectedResidentAttribute { - position: attribute.position(), - }); - } - - // Get an `NtfsDataRuns` iterator for iterating through the attribute value's data runs. - 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. - let stream_data_run = match stream_data_runs.next() { - Some(stream_data_run) => stream_data_run, - None => return Ok(false), - }; - let stream_data_run = stream_data_run?; - self.stream_state.set_stream_data_run(stream_data_run); - - // Store the `NtfsFile` and serialize the `NtfsDataRuns` state for a later iteration. - let data_runs_state = Some(stream_data_runs.into_state()); - self.attribute_state = Some(AttributeState { - file, - attribute_offset, - data_runs_state, - }); - - Ok(true) - } - - pub fn ntfs(&self) -> &'n Ntfs { - self.ntfs - } -} - -impl<'n, 'f> NtfsReadSeek for NtfsAttributeListNonResidentAttributeValue<'n, 'f> { - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result - where - T: Read + Seek, - { - let mut bytes_read = 0usize; - - while bytes_read < buf.len() { - // 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. - if self.next_data_run()? { - // We got another data run of the current attribute, so read again. - continue; - } - - // Move to the first data run of the next connected attribute. - if self.next_attribute(fs)? { - // We got another attribute, so read again. - continue; - } else { - // We read everything we could. - break; - } - } - - Ok(bytes_read) - } - - fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result - where - T: Read + Seek, - { - let pos = self.stream_state.optimize_seek(pos, self.len())?; - - let mut bytes_left_to_seek = match pos { - SeekFrom::Start(n) => { - // Rewind to the very beginning. - self.connected_entries.attribute_list_entries = - Some(self.initial_attribute_list_entries.clone()); - self.attribute_state = None; - self.stream_state = StreamState::new(self.len()); - n - } - SeekFrom::Current(n) if n >= 0 => n as u64, - _ => unreachable!(), - }; - - while bytes_left_to_seek > 0 { - // Seek inside the current data run if there is one. - if self - .stream_state - .seek_data_run(fs, pos, &mut bytes_left_to_seek)? - { - // We have reached our final seek position. - break; - } - - // 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. - continue; - } - - // 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; - } else { - // We seeked as far as we could. - break; - } - } - - match pos { - SeekFrom::Start(n) => self.stream_state.set_stream_position(n), - SeekFrom::Current(n) => self - .stream_state - .set_stream_position(self.stream_position() + n as u64), - _ => unreachable!(), - } - - Ok(self.stream_position()) - } - - fn stream_position(&self) -> u64 { - self.stream_state.stream_position() - } -} - -#[derive(Clone, Debug)] -struct AttributeListConnectedEntries<'n, 'f> { - attribute_list_entries: Option>, - instance: u16, - ty: NtfsAttributeType, -} - -impl<'n, 'f> AttributeListConnectedEntries<'n, 'f> { - fn new( - attribute_list_entries: NtfsAttributeListEntries<'n, 'f>, - instance: u16, - ty: NtfsAttributeType, - ) -> Self { - Self { - attribute_list_entries: Some(attribute_list_entries), - instance, - ty, - } - } - - fn next(&mut self, fs: &mut T) -> Option> - where - T: Read + Seek, - { - let attribute_list_entries = self.attribute_list_entries.as_mut()?; - - let entry = iter_try!(attribute_list_entries.next(fs)?); - if entry.instance() == self.instance && iter_try!(entry.ty()) == self.ty { - Some(Ok(entry)) - } else { - self.attribute_list_entries = None; - None - } - } -} - -#[derive(Clone, Debug)] -struct AttributeState<'n> { - file: NtfsFile<'n>, - attribute_offset: usize, - /// We cannot store an `NtfsDataRuns` here, because it has a reference to the `NtfsFile` that is also stored here. - /// This is why we have to go via `DataRunsState` in an `Option` to take() it and deserialize it into an `NtfsDataRuns` whenever necessary. - data_runs_state: Option, -} diff --git a/src/value/mod.rs b/src/value/mod.rs deleted file mode 100644 index fb39a42..0000000 --- a/src/value/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2021 Colin Finck -// SPDX-License-Identifier: GPL-2.0-or-later - -pub(crate) mod attribute_list_non_resident_attribute; -pub(crate) mod non_resident_attribute; -pub(crate) mod slice; - -use binread::io; -use binread::io::{Read, Seek, SeekFrom}; - -use crate::error::{NtfsError, Result}; -use crate::traits::NtfsReadSeek; -use attribute_list_non_resident_attribute::NtfsAttributeListNonResidentAttributeValue; -use non_resident_attribute::NtfsNonResidentAttributeValue; -use slice::NtfsSliceValue; - -#[derive(Clone, Debug)] -pub enum NtfsValue<'n, 'f> { - Slice(NtfsSliceValue<'f>), - NonResidentAttribute(NtfsNonResidentAttributeValue<'n, 'f>), - AttributeListNonResidentAttribute(NtfsAttributeListNonResidentAttributeValue<'n, 'f>), -} - -impl<'n, 'f> NtfsValue<'n, 'f> { - pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsValueAttached<'n, 'f, 'a, T> - where - T: Read + Seek, - { - NtfsValueAttached::new(fs, self) - } - - pub fn data_position(&self) -> Option { - match self { - Self::Slice(inner) => inner.data_position(), - Self::NonResidentAttribute(inner) => inner.data_position(), - Self::AttributeListNonResidentAttribute(inner) => inner.data_position(), - } - } - - pub fn len(&self) -> u64 { - match self { - Self::Slice(inner) => inner.len(), - Self::NonResidentAttribute(inner) => inner.len(), - Self::AttributeListNonResidentAttribute(inner) => inner.len(), - } - } -} - -impl<'n, 'f> NtfsReadSeek for NtfsValue<'n, 'f> { - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result - where - T: Read + Seek, - { - match self { - Self::Slice(inner) => inner.read(fs, buf), - Self::NonResidentAttribute(inner) => inner.read(fs, buf), - Self::AttributeListNonResidentAttribute(inner) => inner.read(fs, buf), - } - } - - fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result - where - T: Read + Seek, - { - match self { - Self::Slice(inner) => inner.seek(fs, pos), - Self::NonResidentAttribute(inner) => inner.seek(fs, pos), - Self::AttributeListNonResidentAttribute(inner) => inner.seek(fs, pos), - } - } - - fn stream_position(&self) -> u64 { - match self { - Self::Slice(inner) => inner.stream_position(), - Self::NonResidentAttribute(inner) => inner.stream_position(), - Self::AttributeListNonResidentAttribute(inner) => inner.stream_position(), - } - } -} - -pub struct NtfsValueAttached<'n, 'f, 'a, T: Read + Seek> { - fs: &'a mut T, - value: NtfsValue<'n, 'f>, -} - -impl<'n, 'f, 'a, T> NtfsValueAttached<'n, 'f, 'a, T> -where - T: Read + Seek, -{ - fn new(fs: &'a mut T, value: NtfsValue<'n, 'f>) -> Self { - Self { fs, value } - } - - pub fn data_position(&self) -> Option { - self.value.data_position() - } - - pub fn detach(self) -> NtfsValue<'n, 'f> { - self.value - } - - pub fn len(&self) -> u64 { - self.value.len() - } -} - -impl<'n, 'f, 'a, T> Read for NtfsValueAttached<'n, 'f, 'a, T> -where - T: Read + Seek, -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.value.read(self.fs, buf).map_err(io::Error::from) - } -} - -impl<'n, 'f, 'a, T> Seek for NtfsValueAttached<'n, 'f, 'a, T> -where - T: Read + Seek, -{ - fn seek(&mut self, pos: SeekFrom) -> io::Result { - self.value.seek(self.fs, pos).map_err(io::Error::from) - } -} - -pub(crate) fn seek_contiguous( - stream_position: &mut u64, - length: u64, - pos: SeekFrom, -) -> Result { - // This implementation is taken from https://github.com/rust-lang/rust/blob/18c524fbae3ab1bf6ed9196168d8c68fc6aec61a/library/std/src/io/cursor.rs - // It handles all signed/unsigned arithmetics properly and outputs the known `io` error message. - let (base_pos, offset) = match pos { - SeekFrom::Start(n) => { - *stream_position = n; - return Ok(n); - } - SeekFrom::End(n) => (length, n), - SeekFrom::Current(n) => (*stream_position, n), - }; - - let new_pos = if offset >= 0 { - base_pos.checked_add(offset as u64) - } else { - base_pos.checked_sub(offset.wrapping_neg() as u64) - }; - - match new_pos { - Some(n) => { - *stream_position = n; - Ok(*stream_position) - } - None => Err(NtfsError::Io(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid seek to a negative or overflowing position", - ))), - } -} diff --git a/src/value/non_resident_attribute.rs b/src/value/non_resident_attribute.rs deleted file mode 100644 index 4639261..0000000 --- a/src/value/non_resident_attribute.rs +++ /dev/null @@ -1,670 +0,0 @@ -// Copyright 2021 Colin Finck -// SPDX-License-Identifier: GPL-2.0-or-later -// -//! This module implements a reader for a non-resident attribute value (that is not part of an AttributeList). -//! 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. - -use core::convert::TryFrom; -use core::iter::FusedIterator; -use core::mem; - -use binread::io; -use binread::io::Cursor; -use binread::io::{Read, Seek, SeekFrom}; -use binread::BinRead; - -use super::seek_contiguous; -use crate::error::{NtfsError, Result}; -use crate::ntfs::Ntfs; -use crate::traits::NtfsReadSeek; -use crate::types::{Lcn, Vcn}; - -#[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. - data: &'f [u8], - /// 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. - stream_state: StreamState, -} - -impl<'n, 'f> NtfsNonResidentAttributeValue<'n, 'f> { - pub(crate) fn new( - ntfs: &'n Ntfs, - data: &'f [u8], - position: u64, - data_size: u64, - ) -> Result { - 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. - 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); - } - - Ok(Self { - ntfs, - data, - position, - stream_data_runs, - stream_state, - }) - } - - pub fn attach<'a, T>( - self, - fs: &'a mut T, - ) -> NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> - where - T: Read + Seek, - { - NtfsNonResidentAttributeValueAttached::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 { - self.stream_state.data_position() - } - - pub fn data_runs(&self) -> NtfsDataRuns<'n, 'f> { - NtfsDataRuns::new(self.ntfs, self.data, self.position) - } - - pub fn len(&self) -> u64 { - self.stream_state.data_size() - } - - /// Returns whether we got another data run. - fn next_data_run(&mut self) -> Result { - let stream_data_run = match self.stream_data_runs.next() { - Some(stream_data_run) => stream_data_run, - None => return Ok(false), - }; - let stream_data_run = stream_data_run?; - self.stream_state.set_stream_data_run(stream_data_run); - - Ok(true) - } - - pub fn ntfs(&self) -> &'n Ntfs { - self.ntfs - } - - /// Returns the absolute position of the data run information within the filesystem, in bytes. - pub fn position(&self) -> u64 { - self.position - } -} - -impl<'n, 'f> NtfsReadSeek for NtfsNonResidentAttributeValue<'n, 'f> { - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result - where - T: Read + Seek, - { - let mut bytes_read = 0usize; - - while bytes_read < buf.len() { - // 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. - if self.next_data_run()? { - // We got another data run, so read again. - continue; - } else { - // We read everything we could. - break; - } - } - - Ok(bytes_read) - } - - fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result - where - T: Read + Seek, - { - let pos = self.stream_state.optimize_seek(pos, self.len())?; - - let mut bytes_left_to_seek = match pos { - SeekFrom::Start(n) => { - // Rewind to the very beginning. - self.stream_data_runs = self.data_runs(); - self.stream_state = StreamState::new(self.len()); - n - } - SeekFrom::Current(n) if n >= 0 => n as u64, - _ => unreachable!(), - }; - - while bytes_left_to_seek > 0 { - // Seek inside the current data run if there is one. - if self - .stream_state - .seek_data_run(fs, pos, &mut bytes_left_to_seek)? - { - // We have reached our final seek position. - break; - } - - // Move to the next data run. - if self.next_data_run()? { - // We got another data run, so seek some more. - continue; - } else { - // We seeked as far as we could. - break; - } - } - - match pos { - SeekFrom::Start(n) => self.stream_state.set_stream_position(n), - SeekFrom::Current(n) => self - .stream_state - .set_stream_position(self.stream_position() + n as u64), - _ => unreachable!(), - } - - Ok(self.stream_position()) - } - - fn stream_position(&self) -> u64 { - self.stream_state.stream_position() - } -} - -pub struct NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T: Read + Seek> { - fs: &'a mut T, - value: NtfsNonResidentAttributeValue<'n, 'f>, -} - -impl<'n, 'f, 'a, T> NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> -where - T: Read + Seek, -{ - fn new(fs: &'a mut T, value: NtfsNonResidentAttributeValue<'n, 'f>) -> Self { - Self { fs, value } - } - - pub fn data_position(&self) -> Option { - self.value.data_position() - } - - pub fn detach(self) -> NtfsNonResidentAttributeValue<'n, 'f> { - self.value - } - - pub fn len(&self) -> u64 { - self.value.len() - } -} - -impl<'n, 'f, 'a, T> Read for NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> -where - T: Read + Seek, -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.value.read(self.fs, buf).map_err(io::Error::from) - } -} - -impl<'n, 'f, 'a, T> Seek for NtfsNonResidentAttributeValueAttached<'n, 'f, 'a, T> -where - T: Read + Seek, -{ - fn seek(&mut self, pos: SeekFrom) -> io::Result { - self.value.seek(self.fs, pos).map_err(io::Error::from) - } -} - -#[derive(Clone, Debug)] -pub struct NtfsDataRuns<'n, 'f> { - ntfs: &'n Ntfs, - data: &'f [u8], - position: u64, - state: DataRunsState, -} - -impl<'n, 'f> NtfsDataRuns<'n, 'f> { - pub(crate) fn new(ntfs: &'n Ntfs, data: &'f [u8], position: u64) -> Self { - let state = DataRunsState { - offset: 0, - previous_lcn: Lcn::from(0), - }; - - Self { - ntfs, - data, - position, - state, - } - } - - pub(crate) fn from_state( - ntfs: &'n Ntfs, - data: &'f [u8], - position: u64, - state: DataRunsState, - ) -> Self { - Self { - ntfs, - data, - position, - state, - } - } - - pub(crate) fn into_state(self) -> DataRunsState { - self.state - } - - pub fn position(&self) -> u64 { - self.position + self.state.offset as u64 - } - - fn read_variable_length_bytes( - &self, - cursor: &mut Cursor<&[u8]>, - byte_count: u8, - ) -> Result<[u8; 8]> { - const MAX_BYTE_COUNT: u8 = mem::size_of::() as u8; - - if byte_count > MAX_BYTE_COUNT { - return Err(NtfsError::InvalidByteCountInDataRunHeader { - position: self.position(), - expected: byte_count, - actual: MAX_BYTE_COUNT, - }); - } - - let mut buf = [0u8; MAX_BYTE_COUNT as usize]; - cursor.read_exact(&mut buf[..byte_count as usize])?; - - Ok(buf) - } - - fn read_variable_length_signed_integer( - &self, - cursor: &mut Cursor<&[u8]>, - byte_count: u8, - ) -> Result { - let buf = self.read_variable_length_bytes(cursor, byte_count)?; - let mut integer = i64::from_le_bytes(buf); - - // We have read `byte_count` bytes into a zeroed buffer and just interpreted that as an `i64`. - // Sign-extend `integer` to make it replicate the proper value. - let unused_bits = (mem::size_of::() as u32 - byte_count as u32) * 8; - integer = integer.wrapping_shl(unused_bits).wrapping_shr(unused_bits); - - Ok(integer) - } - - fn read_variable_length_unsigned_integer( - &self, - cursor: &mut Cursor<&[u8]>, - byte_count: u8, - ) -> Result { - let buf = self.read_variable_length_bytes(cursor, byte_count)?; - let integer = u64::from_le_bytes(buf); - Ok(integer) - } -} - -impl<'n, 'f> Iterator for NtfsDataRuns<'n, 'f> { - type Item = Result; - - fn next(&mut self) -> Option> { - if self.state.offset >= self.data.len() { - return None; - } - - // Read the single header byte. - let mut cursor = Cursor::new(&self.data[self.state.offset..]); - let header = iter_try!(u8::read(&mut cursor)); - - // A zero byte marks the end of the data runs. - if header == 0 { - // Ensure that any further call uses the fast path above. - self.state.offset = self.data.len(); - return None; - } - - // The lower nibble indicates the length of the following cluster count variable length integer. - let cluster_count_byte_count = header & 0x0f; - let cluster_count = iter_try!( - self.read_variable_length_unsigned_integer(&mut cursor, cluster_count_byte_count) - ); - - // The upper nibble indicates the length of the following VCN variable length integer. - let vcn_byte_count = (header & 0xf0) >> 4; - let vcn = Vcn::from(iter_try!( - self.read_variable_length_signed_integer(&mut cursor, vcn_byte_count) - )); - - // Turn the read VCN into an absolute LCN. - let lcn = iter_try!(self.state.previous_lcn.checked_add(vcn).ok_or({ - NtfsError::InvalidVcnInDataRunHeader { - position: NtfsDataRuns::position(self), - vcn, - previous_lcn: self.state.previous_lcn, - } - })); - self.state.previous_lcn = lcn; - - // Only advance after having checked for success. - // In case of an error, a subsequent call shall output the same error again. - let bytes_to_advance = cursor.stream_position().unwrap() as usize; - self.state.offset += bytes_to_advance; - - let data_run = iter_try!(NtfsDataRun::new(self.ntfs, lcn, cluster_count)); - Some(Ok(data_run)) - } -} - -impl<'n, 'f> FusedIterator for NtfsDataRuns<'n, 'f> {} - -#[derive(Clone, Debug)] -pub(crate) struct DataRunsState { - offset: usize, - previous_lcn: Lcn, -} - -/// Describes 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. -/// 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. - 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. - allocated_size: u64, - /// Current relative position within the data run value, in bytes. - stream_position: u64, -} - -impl NtfsDataRun { - pub(crate) fn new(ntfs: &Ntfs, lcn: Lcn, cluster_count: u64) -> Result { - let position = lcn.position(ntfs)?; - let allocated_size = cluster_count - .checked_mul(ntfs.cluster_size() as u64) - .ok_or(NtfsError::InvalidClusterCount { cluster_count })?; - - Ok(Self { - position, - allocated_size, - stream_position: 0, - }) - } - - /// Returns the absolute current data seek position within the filesystem, in bytes. - /// This may be `None` if: - /// * The current seek position is outside the valid range, or - /// * The data run is a "sparse" data run - pub fn data_position(&self) -> Option { - if self.position > 0 && self.stream_position < self.len() { - Some(self.position + self.stream_position) - } else { - None - } - } - - pub fn len(&self) -> u64 { - self.allocated_size - } - - pub(crate) fn remaining_len(&self) -> u64 { - self.len().saturating_sub(self.stream_position) - } -} - -impl NtfsReadSeek for NtfsDataRun { - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result - where - T: Read + Seek, - { - if self.remaining_len() == 0 { - return Ok(0); - } - - let bytes_to_read = usize::min(buf.len(), self.remaining_len() as usize); - let work_slice = &mut buf[..bytes_to_read]; - - if self.position == 0 { - // This is a sparse data run. - work_slice.fill(0); - } else { - // 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)?; - } - - self.stream_position += bytes_to_read as u64; - Ok(bytes_to_read) - } - - fn seek(&mut self, _fs: &mut T, pos: SeekFrom) -> Result - where - T: Read + Seek, - { - let length = self.len(); - seek_contiguous(&mut self.stream_position, length, pos) - } - - fn stream_position(&self) -> u64 { - self.stream_position - } -} - -#[derive(Clone, Debug)] -pub(crate) struct StreamState { - /// Current data run we are reading from. - stream_data_run: Option, - /// Current relative position within the entire value, in bytes. - stream_position: u64, - /// Total (used) data size, in bytes. - data_size: u64, -} - -impl StreamState { - pub(crate) const fn new(data_size: u64) -> Self { - Self { - stream_data_run: None, - stream_position: 0, - data_size, - } - } - - /// 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(crate) fn data_position(&self) -> Option { - let stream_data_run = self.stream_data_run.as_ref()?; - stream_data_run.data_position() - } - - /// Returns the total (used) data size of the value, in bytes. - pub(crate) fn data_size(&self) -> u64 { - self.data_size - } - - pub(crate) fn optimize_seek(&self, pos: SeekFrom, data_size: u64) -> Result { - let mut pos = self.simplify_seek(pos, data_size)?; - - // Translate `SeekFrom::Start(n)` into a more efficient `SeekFrom::Current` if n >= self.stream_position. - // We don't need to traverse data runs from the very beginning then. - if let SeekFrom::Start(n) = pos { - if let Some(n_from_current) = n.checked_sub(self.stream_position()) { - if let Ok(signed_n_from_current) = i64::try_from(n_from_current) { - pos = SeekFrom::Current(signed_n_from_current); - } - } - } - - Ok(pos) - } - - /// 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. - /// Hence, we can't efficiently move backwards. - fn simplify_seek(&self, pos: SeekFrom, data_size: u64) -> Result { - match pos { - SeekFrom::Start(n) => { - // Seek n bytes from the very beginning. - return Ok(SeekFrom::Start(n)); - } - SeekFrom::End(n) => { - if n >= 0 { - if let Some(bytes_to_seek) = data_size.checked_add(n as u64) { - // Seek data_size + n bytes from the very beginning. - return Ok(SeekFrom::Start(bytes_to_seek)); - } - } else { - if let Some(bytes_to_seek) = data_size.checked_sub(n.wrapping_neg() as u64) { - // Seek data_size + n bytes (with n being negative) from the very beginning. - return Ok(SeekFrom::Start(bytes_to_seek)); - } - } - } - SeekFrom::Current(n) => { - if n >= 0 { - if self.stream_position().checked_add(n as u64).is_some() { - // Seek n bytes from the current position. - // This is an optimization for the common case, as we don't need to traverse all - // data runs from the very beginning. - return Ok(SeekFrom::Current(n)); - } - } else { - if let Some(bytes_to_seek) = - self.stream_position().checked_sub(n.wrapping_neg() as u64) - { - // Seek stream_position + n bytes (with n being negative) from the very beginning. - return Ok(SeekFrom::Start(bytes_to_seek)); - } - } - } - } - - Err(NtfsError::Io(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid seek to a negative or overflowing position", - ))) - } - - /// Returns whether we read some bytes. - pub(crate) fn read_data_run( - &mut self, - fs: &mut T, - buf: &mut [u8], - bytes_read: &mut usize, - ) -> Result - where - T: Read + Seek, - { - // 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? - 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!) - let remaining_data_size = self.data_size.saturating_sub(self.stream_position); - if remaining_data_size == 0 { - return Ok(false); - } - - // Read up to the buffer length or up to the (used) data size, whatever comes first. - let start = *bytes_read; - let remaining_buf_len = buf.len() - start; - let end = start + usize::min(remaining_buf_len, remaining_data_size as usize); - - // Perform the actual read. - let bytes_read_in_data_run = data_run.read(fs, &mut buf[start..end])?; - *bytes_read += bytes_read_in_data_run; - self.stream_position += bytes_read_in_data_run as u64; - - Ok(true) - } - - /// 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. - pub(crate) fn seek_data_run( - &mut self, - fs: &mut T, - bytes_to_seek: SeekFrom, - bytes_left_to_seek: &mut u64, - ) -> Result - where - T: Read + Seek, - { - // 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. - // - // If we were called to seek from the very beginning, we can be sure that this - // 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 - // SeekFrom::Current and can only use the positive part of the i64 range. - // This is no problem though, as `bytes_left_to_seek` was also created from a - // positive i64 value in that case. - let pos = match bytes_to_seek { - SeekFrom::Start(_) => SeekFrom::Start(*bytes_left_to_seek), - SeekFrom::Current(_) => SeekFrom::Current(*bytes_left_to_seek as i64), - _ => unreachable!(), - }; - - data_run.seek(fs, pos)?; - Ok(true) - } else { - // We can skip the entire data run. - *bytes_left_to_seek -= data_run.remaining_len(); - Ok(false) - } - } - - pub(crate) fn set_stream_data_run(&mut self, stream_data_run: NtfsDataRun) { - self.stream_data_run = Some(stream_data_run); - } - - pub(crate) fn set_stream_position(&mut self, stream_position: u64) { - self.stream_position = stream_position; - } - - /// Returns the current relative position within the entire value, in bytes. - pub(crate) fn stream_position(&self) -> u64 { - self.stream_position - } -} diff --git a/src/value/slice.rs b/src/value/slice.rs deleted file mode 100644 index abfb0f9..0000000 --- a/src/value/slice.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2021 Colin Finck -// 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. -//! 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. - -use binread::io::{Read, Seek, SeekFrom}; - -use super::seek_contiguous; -use crate::error::Result; -use crate::traits::NtfsReadSeek; - -#[derive(Clone, Debug)] -pub struct NtfsSliceValue<'f> { - data: &'f [u8], - position: u64, - stream_position: u64, -} - -impl<'f> NtfsSliceValue<'f> { - pub(crate) fn new(data: &'f [u8], position: u64) -> Self { - Self { - data, - position, - stream_position: 0, - } - } - - pub fn data(&self) -> &'f [u8] { - self.data - } - - /// Returns the absolute current data seek position within the filesystem, in bytes. - /// This may be `None` if the current seek position is outside the valid range. - pub fn data_position(&self) -> Option { - if self.stream_position < self.len() { - Some(self.position + self.stream_position) - } else { - None - } - } - - pub fn len(&self) -> u64 { - self.data.len() as u64 - } - - fn remaining_len(&self) -> u64 { - self.len().saturating_sub(self.stream_position) - } -} - -impl<'f> NtfsReadSeek for NtfsSliceValue<'f> { - fn read(&mut self, _fs: &mut T, buf: &mut [u8]) -> Result - where - T: Read + Seek, - { - if self.remaining_len() == 0 { - return Ok(0); - } - - let bytes_to_read = usize::min(buf.len(), self.remaining_len() as usize); - let work_slice = &mut buf[..bytes_to_read]; - - let start = self.stream_position as usize; - let end = start + bytes_to_read; - work_slice.copy_from_slice(&self.data[start..end]); - - self.stream_position += bytes_to_read as u64; - Ok(bytes_to_read) - } - - fn seek(&mut self, _fs: &mut T, pos: SeekFrom) -> Result - where - T: Read + Seek, - { - let length = self.len(); - seek_contiguous(&mut self.stream_position, length, pos) - } - - fn stream_position(&self) -> u64 { - self.stream_position - } -} -- cgit v1.2.3