From 0402611794e8d86814e25eba8515d00e1685ce4b Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Mon, 3 May 2021 19:04:05 +0200 Subject: Implement reading non-resident values, `NtfsIndexRoot`, clusters >64K, and refactor many affected parts. --- Cargo.toml | 2 +- src/attribute.rs | 134 ++++++-- src/attribute_value.rs | 467 ++++++++++++++++++++------ src/boot_sector.rs | 90 +++-- src/error.rs | 23 +- src/lib.rs | 2 + src/ntfs.rs | 97 ++---- src/ntfs_file.rs | 21 +- src/structured_values/file_name.rs | 4 +- src/structured_values/index_root.rs | 58 ++++ src/structured_values/mod.rs | 3 + src/structured_values/object_id.rs | 2 +- src/structured_values/standard_information.rs | 4 +- src/structured_values/volume_information.rs | 2 +- src/structured_values/volume_name.rs | 2 +- src/traits.rs | 43 +++ 16 files changed, 716 insertions(+), 238 deletions(-) create mode 100644 src/structured_values/index_root.rs create mode 100644 src/traits.rs diff --git a/Cargo.toml b/Cargo.toml index 8e4da25..3dd9447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" [dependencies] binread = { path = "../binread/binread", features = ["const_generics"], default-features = false } -chrono = { version = "0.4.19", optional = true } bitflags = "1.2.1" +chrono = { version = "0.4.19", optional = true } displaydoc = { version = "0.1.7", default-features = false } enumn = "0.1.3" memoffset = "0.6.1" diff --git a/src/attribute.rs b/src/attribute.rs index 6536e39..684ddd9 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,12 +1,13 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later -use crate::attribute_value::{NtfsAttributeResidentValue, NtfsAttributeValue}; +use crate::attribute_value::{NtfsAttributeNonResidentValue, NtfsAttributeValue, NtfsDataRun}; use crate::error::{NtfsError, Result}; +use crate::ntfs::Ntfs; use crate::ntfs_file::NtfsFile; use crate::string::NtfsString; use crate::structured_values::{ - NtfsFileName, NtfsObjectId, NtfsStandardInformation, NtfsStructuredValue, + NtfsFileName, NtfsIndexRoot, NtfsObjectId, NtfsStandardInformation, NtfsStructuredValue, NtfsVolumeInformation, NtfsVolumeName, }; use binread::io::{Read, Seek, SeekFrom}; @@ -19,7 +20,7 @@ use enumn::N; /// On-disk structure of the generic header of an NTFS attribute. #[allow(unused)] -#[derive(BinRead)] +#[derive(BinRead, Debug)] struct NtfsAttributeHeader { /// Type of the attribute, known types are in [`NtfsAttributeType`]. ty: u32, @@ -56,7 +57,7 @@ bitflags! { /// On-disk structure of the extra header of an NTFS attribute that has a resident value. #[allow(unused)] -#[derive(BinRead)] +#[derive(BinRead, Debug)] struct NtfsAttributeResidentHeader { /// Length of the value, in bytes. value_length: u32, @@ -68,7 +69,7 @@ struct NtfsAttributeResidentHeader { /// On-disk structure of the extra header of an NTFS attribute that has a non-resident value. #[allow(unused)] -#[derive(BinRead)] +#[derive(BinRead, Debug)] struct NtfsAttributeNonResidentHeader { /// Lower boundary of Virtual Cluster Numbers (VCNs) referenced by this attribute. /// This becomes relevant when file data is split over multiple attributes. @@ -88,13 +89,13 @@ struct NtfsAttributeNonResidentHeader { reserved: [u8; 5], /// Allocated space for the attribute value, in bytes. This is always a multiple of the cluster size. /// For compressed files, this is always a multiple of the compression unit size. - allocated_size: i64, + allocated_size: u64, /// Size of the attribute value, in bytes. /// This can be larger than `allocated_size` if the value is compressed or stored sparsely. - data_size: i64, + data_size: u64, /// Size of the initialized part of the attribute value, in bytes. /// This is usually the same as `data_size`. - initialized_size: i64, + initialized_size: u64, } #[derive(Clone, Copy, Debug, Eq, N, PartialEq)] @@ -119,6 +120,7 @@ pub enum NtfsAttributeType { End = 0xFFFF_FFFF, } +#[derive(Debug)] enum NtfsAttributeExtraHeader { Resident(NtfsAttributeResidentHeader), NonResident(NtfsAttributeNonResidentHeader), @@ -141,14 +143,16 @@ impl NtfsAttributeExtraHeader { } } -pub struct NtfsAttribute { +#[derive(Debug)] +pub struct NtfsAttribute<'n> { + ntfs: &'n Ntfs, position: u64, header: NtfsAttributeHeader, extra_header: NtfsAttributeExtraHeader, } -impl NtfsAttribute { - fn new(fs: &mut T, position: u64) -> Result +impl<'n> NtfsAttribute<'n> { + fn new(ntfs: &'n Ntfs, fs: &mut T, position: u64) -> Result where T: Read + Seek, { @@ -164,6 +168,7 @@ impl NtfsAttribute { let extra_header = NtfsAttributeExtraHeader::new(fs, &header)?; let attribute = Self { + ntfs, position, header, extra_header, @@ -253,6 +258,10 @@ impl NtfsAttribute { NtfsVolumeInformation::new(self.position, attached_value, self.value_length())?; Ok(NtfsStructuredValue::VolumeInformation(inner)) } + NtfsAttributeType::IndexRoot => { + let inner = NtfsIndexRoot::new(self.position, attached_value, self.value_length())?; + Ok(NtfsStructuredValue::IndexRoot(inner)) + } ty => Err(NtfsError::UnsupportedStructuredValue { position: self.position, ty, @@ -274,12 +283,19 @@ impl NtfsAttribute { match &self.extra_header { NtfsAttributeExtraHeader::Resident(resident_header) => { let value_position = self.position + resident_header.value_offset as u64; - let value_length = resident_header.value_length; - let value = NtfsAttributeResidentValue::new(value_position, value_length); + let value_length = resident_header.value_length as u64; + let value = NtfsDataRun::from_byte_info(value_position, value_length); NtfsAttributeValue::Resident(value) } - NtfsAttributeExtraHeader::NonResident(_non_resident_header) => { - panic!("TODO") + NtfsAttributeExtraHeader::NonResident(non_resident_header) => { + let start = self.position + non_resident_header.data_runs_offset as u64; + let end = self.position + self.header.length as u64; + let value = NtfsAttributeNonResidentValue::new( + &self.ntfs, + start..end, + non_resident_header.data_size, + ); + NtfsAttributeValue::NonResident(value) } } } @@ -291,37 +307,56 @@ impl NtfsAttribute { resident_header.value_length as u64 } NtfsAttributeExtraHeader::NonResident(non_resident_header) => { - non_resident_header.data_size as u64 + non_resident_header.data_size } } } } -pub struct NtfsAttributes<'a, T: Read + Seek> { - fs: &'a mut T, +pub struct NtfsAttributes<'n> { + ntfs: &'n Ntfs, items_range: Range, } -impl<'a, T> NtfsAttributes<'a, T> -where - T: Read + Seek, -{ - pub(crate) fn new(fs: &'a mut T, file: &NtfsFile) -> Self { +impl<'n> NtfsAttributes<'n> { + pub(crate) fn new(ntfs: &'n Ntfs, file: &NtfsFile) -> Self { let start = file.position() + file.first_attribute_offset() as u64; let end = file.position() + file.used_size() as u64; let items_range = start..end; - Self { fs, items_range } + Self { ntfs, items_range } } -} -impl<'a, T> Iterator for NtfsAttributes<'a, T> -where - T: Read + Seek, -{ - type Item = Result; + pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributesAttached<'n, 'a, T> + where + T: Read + Seek, + { + NtfsAttributesAttached::new(fs, self) + } - fn next(&mut self) -> Option { + pub(crate) fn find_first_by_ty( + &mut self, + fs: &mut T, + ty: NtfsAttributeType, + ) -> Option>> + where + T: Read + Seek, + { + while let Some(attribute) = self.next(fs) { + let attribute = iter_try!(attribute); + let attribute_ty = iter_try!(attribute.ty()); + if attribute_ty == ty { + return Some(Ok(attribute)); + } + } + + None + } + + pub fn next(&mut self, fs: &mut T) -> Option>> + where + T: Read + Seek, + { if self.items_range.is_empty() { return None; } @@ -329,18 +364,47 @@ where // This may be an entire attribute or just the 4-byte end marker. // Check if this marks the end of the attribute list. let position = self.items_range.start; - iter_try!(self.fs.seek(SeekFrom::Start(position))); - let ty = iter_try!(self.fs.read_le::()); + iter_try!(fs.seek(SeekFrom::Start(position))); + let ty = iter_try!(fs.read_le::()); if ty == NtfsAttributeType::End as u32 { return None; } // It's a real attribute. - let attribute = iter_try!(NtfsAttribute::new(&mut self.fs, position)); + let attribute = iter_try!(NtfsAttribute::new(self.ntfs, fs, position)); self.items_range.start += attribute.attribute_length() as u64; Some(Ok(attribute)) } } -impl<'a, T> FusedIterator for NtfsAttributes<'a, T> where T: Read + Seek {} +pub struct NtfsAttributesAttached<'n, 'a, T: Read + Seek> { + fs: &'a mut T, + attributes: NtfsAttributes<'n>, +} + +impl<'n, 'a, T> NtfsAttributesAttached<'n, 'a, T> +where + T: Read + Seek, +{ + fn new(fs: &'a mut T, attributes: NtfsAttributes<'n>) -> Self { + Self { fs, attributes } + } + + pub fn detach(self) -> NtfsAttributes<'n> { + self.attributes + } +} + +impl<'n, 'a, T> Iterator for NtfsAttributesAttached<'n, 'a, T> +where + T: Read + Seek, +{ + type Item = Result>; + + fn next(&mut self) -> Option { + self.attributes.next(self.fs) + } +} + +impl<'n, 'a, T> FusedIterator for NtfsAttributesAttached<'n, 'a, T> where T: Read + Seek {} diff --git a/src/attribute_value.rs b/src/attribute_value.rs index 20593a7..b49c684 100644 --- a/src/attribute_value.rs +++ b/src/attribute_value.rs @@ -1,52 +1,28 @@ // Copyright 2021 Colin Finck // SPDX-License-Identifier: GPL-2.0-or-later +use crate::error::{NtfsError, Result}; +use crate::ntfs::Ntfs; +use crate::traits::NtfsReadSeek; use binread::io; -use binread::io::{Error, ErrorKind, Read, Seek, SeekFrom}; -use core::cmp; +use binread::io::{Read, Seek, SeekFrom}; +use binread::BinReaderExt; +use core::iter::FusedIterator; +use core::ops::Range; +use core::{cmp, mem}; -pub trait NtfsAttributeRead -where - T: Read + Seek, -{ - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> io::Result; - - fn read_exact(&mut self, fs: &mut T, mut buf: &mut [u8]) -> io::Result<()> { - // This implementation is taken from https://github.com/rust-lang/rust/blob/5662d9343f0696efcc38a1264656737c9f22d427/library/std/src/io/mod.rs - // It handles all corner cases properly and outputs the known `io` error messages. - while !buf.is_empty() { - match self.read(fs, buf) { - Ok(0) => break, - Ok(n) => { - buf = &mut buf[n..]; - } - Err(ref e) if e.kind() == ErrorKind::Interrupted => {} - Err(e) => return Err(e), - } - } - - if !buf.is_empty() { - Err(Error::new( - ErrorKind::UnexpectedEof, - "failed to fill whole buffer", - )) - } else { - Ok(()) - } - } +#[derive(Clone, Debug)] +pub enum NtfsAttributeValue<'n> { + Resident(NtfsDataRun), + NonResident(NtfsAttributeNonResidentValue<'n>), } -pub enum NtfsAttributeValue { - Resident(NtfsAttributeResidentValue), - NonResident(NtfsAttributeNonResidentValue), -} - -impl NtfsAttributeValue { - pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributeValueAttached<'a, T> +impl<'n> NtfsAttributeValue<'n> { + pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsAttributeValueAttached<'n, 'a, T> where T: Read + Seek, { - NtfsAttributeValueAttached { fs, value: self } + NtfsAttributeValueAttached::new(fs, self) } pub(crate) fn position(&self) -> u64 { @@ -57,37 +33,49 @@ impl NtfsAttributeValue { } } -impl NtfsAttributeRead for NtfsAttributeValue -where - T: Read + Seek, -{ - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> io::Result { +impl<'n> NtfsReadSeek for NtfsAttributeValue<'n> { + 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), } } -} -impl Seek for NtfsAttributeValue { - fn seek(&mut self, pos: SeekFrom) -> io::Result { + fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { match self { - Self::Resident(inner) => inner.seek(pos), - Self::NonResident(inner) => inner.seek(pos), + Self::Resident(inner) => inner.seek(fs, pos), + Self::NonResident(inner) => inner.seek(fs, pos), + } + } + + fn stream_position(&mut self) -> Result { + match self { + Self::Resident(inner) => inner.stream_position(), + Self::NonResident(inner) => inner.stream_position(), } } } -pub struct NtfsAttributeValueAttached<'a, T: Read + Seek> { +pub struct NtfsAttributeValueAttached<'n, 'a, T: Read + Seek> { fs: &'a mut T, - value: NtfsAttributeValue, + value: NtfsAttributeValue<'n>, } -impl<'a, T> NtfsAttributeValueAttached<'a, T> +impl<'n, 'a, T> NtfsAttributeValueAttached<'n, 'a, T> where T: Read + Seek, { - pub fn detach(self) -> NtfsAttributeValue { + fn new(fs: &'a mut T, value: NtfsAttributeValue<'n>) -> Self { + Self { fs, value } + } + + pub fn detach(self) -> NtfsAttributeValue<'n> { self.value } @@ -96,78 +84,97 @@ where } } -impl<'a, T> Read for NtfsAttributeValueAttached<'a, T> +impl<'n, 'a, T> Read for NtfsAttributeValueAttached<'n, 'a, T> where T: Read + Seek, { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.value.read(self.fs, buf) + self.value.read(self.fs, buf).map_err(io::Error::from) } } -impl<'a, T> Seek for NtfsAttributeValueAttached<'a, T> +impl<'n, 'a, T> Seek for NtfsAttributeValueAttached<'n, 'a, T> where T: Read + Seek, { fn seek(&mut self, pos: SeekFrom) -> io::Result { - self.value.seek(pos) + self.value.seek(self.fs, pos).map_err(io::Error::from) } } -pub struct NtfsAttributeResidentValue { +#[derive(Clone, Debug)] +pub struct NtfsDataRun { /// Absolute position of the attribute's value within the filesystem, in bytes. position: u64, /// Total length of the attribute's value, in bytes. - length: u32, - /// Current relative seek position within the value, in bytes. - seek_position: u64, + length: u64, + /// Current relative position within the value, in bytes. + stream_position: u64, } -impl NtfsAttributeResidentValue { - pub(crate) fn new(position: u64, length: u32) -> Self { +impl NtfsDataRun { + pub(crate) fn from_byte_info(position: u64, length: u64) -> Self { Self { position, length, - seek_position: 0, + stream_position: 0, } } + pub(crate) fn from_lcn_info(ntfs: &Ntfs, first_lcn: u64, lcn_count: u64) -> Self { + let position = first_lcn * ntfs.cluster_size() as u64; + let length = lcn_count * ntfs.cluster_size() as u64; + Self::from_byte_info(position, length) + } + + pub fn len(&self) -> u64 { + self.length + } + pub fn position(&self) -> u64 { self.position } } -impl NtfsAttributeRead for NtfsAttributeResidentValue -where - T: Read + Seek, -{ - fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> io::Result { - let bytes_left = (self.length as u64).saturating_sub(self.seek_position); +impl NtfsReadSeek for NtfsDataRun { + fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek, + { + let bytes_left = self.length.saturating_sub(self.stream_position); if bytes_left == 0 { return Ok(0); } let bytes_to_read = cmp::min(buf.len(), bytes_left as usize); + let work_slice = &mut buf[..bytes_to_read]; - fs.seek(SeekFrom::Start(self.position + self.seek_position))?; - fs.read(&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. + fs.seek(SeekFrom::Start(self.position + self.stream_position))?; + fs.read(work_slice)?; + } - self.seek_position += bytes_to_read as u64; + self.stream_position += bytes_to_read as u64; Ok(bytes_to_read) } -} -impl Seek for NtfsAttributeResidentValue { - fn seek(&mut self, pos: SeekFrom) -> io::Result { + fn seek(&mut self, _fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { // 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 messages. + // It handles all signed/unsigned arithmetics properly and outputs the known `io` error message. let (base_pos, offset) = match pos { SeekFrom::Start(n) => { - self.seek_position = n; + self.stream_position = n; return Ok(n); } - SeekFrom::End(n) => (self.length as u64, n), - SeekFrom::Current(n) => (self.seek_position, n), + SeekFrom::End(n) => (self.length, n), + SeekFrom::Current(n) => (self.stream_position, n), }; let new_pos = if offset >= 0 { @@ -178,38 +185,306 @@ impl Seek for NtfsAttributeResidentValue { match new_pos { Some(n) => { - self.seek_position = n; - Ok(self.seek_position) + self.stream_position = n; + Ok(self.stream_position) } - None => Err(Error::new( - ErrorKind::InvalidInput, + None => Err(NtfsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, "invalid seek to a negative or overflowing position", - )), + ))), } } + + fn stream_position(&mut self) -> Result { + Ok(self.stream_position) + } } -pub struct NtfsAttributeNonResidentValue { - // TODO +#[derive(Clone, Debug)] +pub struct NtfsDataRuns<'n> { + ntfs: &'n Ntfs, + data_runs_range: Range, } -impl NtfsAttributeNonResidentValue { - pub fn position(&self) -> u64 { - panic!("TODO") +impl<'n> NtfsDataRuns<'n> { + fn new(ntfs: &'n Ntfs, data_runs_range: Range) -> Self { + Self { + ntfs, + data_runs_range, + } + } + + pub fn attach<'a, T>(self, fs: &'a mut T) -> NtfsDataRunsAttached<'n, 'a, T> + where + T: Read + Seek, + { + NtfsDataRunsAttached::new(fs, self) + } + + pub fn next(&mut self, fs: &mut T) -> Option> + where + T: Read + Seek, + { + if self.data_runs_range.is_empty() { + return None; + } + + // Read the single header byte. + iter_try!(fs.seek(SeekFrom::Start(self.data_runs_range.start))); + let header = iter_try!(fs.read_le::()); + let mut size = 1u64; + + // A zero byte marks the end of the data runs. + if header == 0 { + // Ensure `self.data_runs_range.is_empty` returns true, so any further call uses the fast path above. + self.data_runs_range.start = self.data_runs_range.end; + return None; + } + + // The lower nibble indicates how many bytes the following variable length integer about the LCN count takes. + let lcn_count_byte_count = header & 0x0f; + let lcn_count = iter_try!(self.read_variable_length_integer(fs, lcn_count_byte_count)); + size += lcn_count_byte_count as u64; + + // The upper nibble indicates how many bytes the following variable length integer about the first LCN takes. + let first_lcn_byte_count = (header & 0xf0) >> 4; + let first_lcn = iter_try!(self.read_variable_length_integer(fs, first_lcn_byte_count)); + size += first_lcn_byte_count as u64; + + // Only increment `self.data_runs_range.start` after having checked for success. + // In case of an error, a subsequent call shall output the same error again. + self.data_runs_range.start += size; + + let data_run = NtfsDataRun::from_lcn_info(self.ntfs, first_lcn, lcn_count); + Some(Ok(data_run)) + } + + fn read_variable_length_integer(&self, fs: &mut T, byte_count: u8) -> Result + where + T: Read + Seek, + { + const MAX_BYTE_COUNT: u8 = mem::size_of::() as u8; + + if byte_count > MAX_BYTE_COUNT { + return Err(NtfsError::InvalidByteCountInDataRunHeader { + position: self.data_runs_range.start, + expected: MAX_BYTE_COUNT, + actual: byte_count, + }); + } + + let mut buf = [0u8; MAX_BYTE_COUNT as usize]; + fs.read_exact(&mut buf[..byte_count as usize])?; + + Ok(u64::from_le_bytes(buf)) } } -impl NtfsAttributeRead for NtfsAttributeNonResidentValue +#[derive(Debug)] +pub struct NtfsDataRunsAttached<'n, 'a, T: Read + Seek> { + fs: &'a mut T, + data_runs: NtfsDataRuns<'n>, +} + +impl<'n, 'a, T> NtfsDataRunsAttached<'n, 'a, T> where T: Read + Seek, { - fn read(&mut self, _fs: &mut T, _buf: &mut [u8]) -> io::Result { - panic!("TODO") + fn new(fs: &'a mut T, data_runs: NtfsDataRuns<'n>) -> Self { + Self { fs, data_runs } + } + + pub fn detach(self) -> NtfsDataRuns<'n> { + self.data_runs } } -impl Seek for NtfsAttributeNonResidentValue { - fn seek(&mut self, _pos: SeekFrom) -> io::Result { - panic!("TODO") +impl<'n, 'a, T> Iterator for NtfsDataRunsAttached<'n, 'a, T> +where + T: Read + Seek, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.data_runs.next(self.fs) + } +} + +impl<'n, 'a, T> FusedIterator for NtfsDataRunsAttached<'n, 'a, T> where T: Read + Seek {} + +#[derive(Clone, Debug)] +pub struct NtfsAttributeNonResidentValue<'n> { + /// Reference to the base `Ntfs` object of this filesystem. + ntfs: &'n Ntfs, + /// Byte range where the data run information of this non-resident value is stored on the filesystem. + data_runs_range: Range, + /// Total size of the data spread among all data runs, in bytes. + data_size: u64, + /// Iterator of data runs used for reading/seeking. + stream_data_runs: NtfsDataRuns<'n>, + /// Current data run we are reading from. + stream_data_run: Option, + /// Total stream position, in bytes. + stream_position: u64, +} + +impl<'n> NtfsAttributeNonResidentValue<'n> { + pub(crate) fn new(ntfs: &'n Ntfs, data_runs_range: Range, data_size: u64) -> Self { + let stream_data_runs = NtfsDataRuns::new(ntfs, data_runs_range.clone()); + + Self { + ntfs, + data_runs_range, + data_size, + stream_data_runs, + stream_data_run: None, + stream_position: 0, + } + } + + pub fn data_runs(&self) -> NtfsDataRuns<'n> { + NtfsDataRuns::new(self.ntfs, self.data_runs_range.clone()) + } + + pub fn position(&self) -> u64 { + self.data_runs_range.start + } + + fn do_seek(&mut self, fs: &mut T, bytes_to_seek: SeekFrom) -> Result + where + T: Read + Seek, + { + let mut bytes_left_to_seek = match bytes_to_seek { + SeekFrom::Start(n) => n, + SeekFrom::Current(n) if n >= 0 => n as u64, + _ => panic!("do_seek only accepts positive seeks from Start or Current!"), + }; + + while bytes_left_to_seek > 0 { + if let Some(data_run) = &mut self.stream_data_run { + let bytes_left_in_data_run = + data_run.len().saturating_sub(data_run.stream_position()?); + + if bytes_left_to_seek < bytes_left_in_data_run { + // We have found the right data run, now we have to seek inside the data run. + // + // If we were called to seek from the very beginning, we can be sure that this + // data run is also seeked from the beginning. + // 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)?; + break; + } else { + // We can skip the entire data run. + bytes_left_to_seek -= data_run.len(); + } + } + + match self.stream_data_runs.next(fs) { + Some(Ok(data_run)) => self.stream_data_run = Some(data_run), + Some(Err(e)) => return Err(e), + None => break, + } + } + + match bytes_to_seek { + SeekFrom::Start(n) => self.stream_position = n, + SeekFrom::Current(n) => self.stream_position += n as u64, + _ => unreachable!(), + } + + Ok(self.stream_position) + } +} + +impl<'n> NtfsReadSeek for NtfsAttributeNonResidentValue<'n> { + 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() { + if let Some(data_run) = &mut self.stream_data_run { + if data_run.stream_position()? < data_run.len() { + let bytes_read_in_data_run = data_run.read(fs, &mut buf[bytes_read..])?; + bytes_read += bytes_read_in_data_run; + self.stream_position += bytes_read_in_data_run as u64; + continue; + } + } + + // We still have bytes to read, but no data run or the previous data run has been read to its end. + // Get the next data run and try again. + match self.stream_data_runs.next(fs) { + Some(Ok(data_run)) => self.stream_data_run = Some(data_run), + Some(Err(e)) => return Err(e), + None => break, + } + } + + Ok(bytes_read) + } + + fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek, + { + match pos { + SeekFrom::Start(n) => { + // Seek n bytes from the very beginning. + return self.do_seek(fs, SeekFrom::Start(n)); + } + SeekFrom::End(n) => { + if n >= 0 { + if let Some(bytes_to_seek) = self.data_size.checked_add(n as u64) { + // Seek data_size + n bytes from the very beginning. + return self.do_seek(fs, SeekFrom::Start(bytes_to_seek)); + } + } else { + if let Some(bytes_to_seek) = self.data_size.checked_sub(n.wrapping_neg() as u64) + { + // Seek data_size + n bytes (with n being negative) from the very beginning. + return self.do_seek(fs, 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 self.do_seek(fs, 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 self.do_seek(fs, SeekFrom::Start(bytes_to_seek)); + } + } + } + } + + Err(NtfsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to a negative or overflowing position", + ))) + } + + fn stream_position(&mut self) -> Result { + Ok(self.stream_position) } } diff --git a/src/boot_sector.rs b/src/boot_sector.rs index 0ab2f64..e9bf0b9 100644 --- a/src/boot_sector.rs +++ b/src/boot_sector.rs @@ -5,11 +5,6 @@ use crate::error::{NtfsError, Result}; use binread::BinRead; use memoffset::offset_of; -/// The usual exponent of `BiosParameterBlock::file_record_size_info` is 10 (2^10 = 1024 bytes). -/// Exponents > 10 would come as a surprise, but our code should still be able to handle those. -/// Exponents > 32 (2^32 = 4 GiB) would make no sense, exceed a u32, and must be outright denied. -const MAXIMUM_SIZE_INFO_EXPONENT: u32 = 32; - // Sources: // - https://en.wikipedia.org/wiki/NTFS#Partition_Boot_Sector_(VBR) // - https://en.wikipedia.org/wiki/BIOS_parameter_block#NTFS @@ -18,8 +13,8 @@ const MAXIMUM_SIZE_INFO_EXPONENT: u32 = 32; #[allow(unused)] #[derive(BinRead)] pub(crate) struct BiosParameterBlock { - pub(crate) bytes_per_sector: u16, - pub(crate) sectors_per_cluster: u8, + sector_size: u16, + sectors_per_cluster: u8, zeros_1: [u8; 7], media: u8, zeros_2: [u8; 2], @@ -31,37 +26,86 @@ pub(crate) struct BiosParameterBlock { flags: u8, extended_boot_signature: u8, reserved: u8, - pub(crate) total_sectors: u64, - /// Logical Cluster Number (LCN) to the beginning of the Master File Table (MFT). - pub(crate) mft_lcn: u64, + total_sectors: u64, + mft_lcn: u64, mft_mirror_lcn: u64, - pub(crate) file_record_size_info: i8, + file_record_size_info: i8, zeros_4: [u8; 3], index_record_size_info: i8, zeros_5: [u8; 3], - pub(crate) serial_number: u64, + serial_number: u64, checksum: u32, } impl BiosParameterBlock { + /// Returns the size of a single cluster, in bytes. + pub(crate) fn cluster_size(&self) -> Result { + /// The maximum cluster size supported by Windows is 2 MiB. + /// Source: https://en.wikipedia.org/wiki/NTFS + const MAXIMUM_CLUSTER_SIZE: u32 = 2097152; + + let cluster_size = self.sectors_per_cluster as u32 * self.sector_size as u32; + if cluster_size > MAXIMUM_CLUSTER_SIZE { + return Err(NtfsError::UnsupportedClusterSize { + expected: MAXIMUM_CLUSTER_SIZE, + actual: cluster_size, + }); + } + + Ok(cluster_size) + } + + pub(crate) fn file_record_size(&self) -> Result { + self.record_size(self.file_record_size_info) + } + + /// Returns the Logical Cluster Number (LCN) to the beginning of the Master File Table (MFT). + pub(crate) fn mft_lcn(&self) -> u64 { + self.mft_lcn + } + /// Source: https://en.wikipedia.org/wiki/NTFS#Partition_Boot_Sector_(VBR) - pub(crate) fn record_size(size_info_value: i8, bytes_per_cluster: u32) -> Result { - if size_info_value > 0 { + fn record_size(&self, size_info: i8) -> Result { + /// The usual exponent of `BiosParameterBlock::file_record_size_info` is 10 (2^10 = 1024 bytes). + /// Exponents > 10 would come as a surprise, but our code should still be able to handle those. + /// Exponents > 31 (2^31 = 2 GiB) would make no sense, exceed a u32, and must be outright denied. + const MAXIMUM_SIZE_INFO_EXPONENT: u32 = 31; + + let cluster_size = self.cluster_size()?; + + if size_info > 0 { // The size field denotes a cluster count. - Ok(size_info_value as u32 * bytes_per_cluster as u32) + cluster_size + .checked_mul(size_info as u32) + .ok_or(NtfsError::InvalidRecordSizeInfo { + size_info, + cluster_size, + }) } else { // The size field denotes a binary exponent after negation. - let exponent = (-size_info_value) as u32; - if exponent > MAXIMUM_SIZE_INFO_EXPONENT { - return Err(NtfsError::InvalidRecordSizeExponent { - expected: MAXIMUM_SIZE_INFO_EXPONENT, - actual: exponent, + let exponent = (-size_info) as u32; + if exponent >= MAXIMUM_SIZE_INFO_EXPONENT { + return Err(NtfsError::InvalidRecordSizeInfo { + size_info, + cluster_size, }); } Ok(1 << exponent) } } + + pub(crate) fn sector_size(&self) -> u16 { + self.sector_size + } + + pub(crate) fn serial_number(&self) -> u64 { + self.serial_number + } + + pub(crate) fn total_sectors(&self) -> u64 { + self.total_sectors + } } #[allow(unused)] @@ -69,12 +113,16 @@ impl BiosParameterBlock { pub(crate) struct BootSector { bootjmp: [u8; 3], oem_name: [u8; 8], - pub(crate) bpb: BiosParameterBlock, + bpb: BiosParameterBlock, boot_code: [u8; 426], signature: [u8; 2], } impl BootSector { + pub(crate) fn bpb(&self) -> &BiosParameterBlock { + &self.bpb + } + pub(crate) fn validate(&self) -> Result<()> { // Validate the infamous [0x55, 0xAA] signature at the end of the boot sector. let expected_signature = &[0x55, 0xAA]; diff --git a/src/error.rs b/src/error.rs index 78b1ea1..232fb4e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,13 @@ pub enum NtfsError { expected: u64, actual: u64, }, + /// The header of an NTFS data run should indicate a maximum byte count of {expected}, + /// but the header at byte position {position:#010x} indicates a byte count of {actual} + InvalidByteCountInDataRunHeader { + position: u64, + expected: u8, + actual: u8, + }, /// The requested NTFS file {n} is invalid InvalidNtfsFile { n: u64 }, /// The NTFS file at byte position {position:#010x} should have signature {expected:?}, but it has signature {actual:?} @@ -34,8 +41,8 @@ pub enum NtfsError { }, /// The given time can't be represented as an NtfsTime InvalidNtfsTime, - /// A record size field in the BIOS Parameter Block denotes the exponent {actual}, but the maximum valid one is {expected} - InvalidRecordSizeExponent { expected: u32, actual: u32 }, + /// A record size field in the BIOS Parameter Block denotes {size_info}, which is invalid considering the cluster size of {cluster_size} bytes + InvalidRecordSizeInfo { size_info: i8, cluster_size: u32 }, /// The 2-byte signature field at byte position {position:#010x} should contain {expected:?}, but it contains {actual:?} InvalidTwoByteSignature { position: u64, @@ -74,5 +81,17 @@ impl From for NtfsError { } } +// To stay compatible with standardized interfaces (e.g. io::Read, io::Seek), +// we sometimes need to convert from NtfsError to io::Error. +impl From for binread::io::Error { + fn from(error: NtfsError) -> Self { + if let NtfsError::Io(io_error) = error { + io_error + } else { + binread::io::Error::new(binread::io::ErrorKind::Other, error) + } + } +} + #[cfg(feature = "std")] impl std::error::Error for NtfsError {} diff --git a/src/lib.rs b/src/lib.rs index f52feef..0b7ee02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ mod ntfs_file; mod string; pub mod structured_values; mod time; +mod traits; pub use crate::attribute::*; pub use crate::attribute_value::*; @@ -28,3 +29,4 @@ pub use crate::ntfs::*; pub use crate::ntfs_file::*; pub use crate::string::*; pub use crate::time::*; +pub use crate::traits::*; diff --git a/src/ntfs.rs b/src/ntfs.rs index aa1beb8..1453182 100644 --- a/src/ntfs.rs +++ b/src/ntfs.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use crate::attribute::NtfsAttributeType; -use crate::boot_sector::{BiosParameterBlock, BootSector}; +use crate::boot_sector::BootSector; //use crate::dir::Dir; use crate::error::{NtfsError, Result}; use crate::ntfs_file::{KnownNtfsFile, NtfsFile}; @@ -10,21 +10,18 @@ use crate::structured_values::{NtfsStructuredValue, NtfsVolumeInformation, NtfsV use binread::io::{Read, Seek, SeekFrom}; use binread::BinReaderExt; -/// The maximum cluster size supported by Windows. -/// Source: https://support.microsoft.com/en-us/topic/default-cluster-size-for-ntfs-fat-and-exfat-9772e6f1-e31a-00d7-e18f-73169155af95 -const MAXIMUM_CLUSTER_SIZE: u32 = 65536; - +#[derive(Debug)] pub struct Ntfs { - /// How many bytes a sector occupies. This is usually 512. - bytes_per_sector: u16, - /// How many sectors a cluster occupies. This is usually 8. - sectors_per_cluster: u8, + /// The size of a single cluster, in bytes. This is usually 4096. + cluster_size: u32, + /// The size of a single sector, in bytes. This is usually 512. + sector_size: u16, /// Size of the filesystem, in bytes. size: u64, /// Absolute position of the Master File Table (MFT), in bytes. mft_position: u64, /// Size of a single file record, in bytes. - pub(crate) file_record_size: u32, + file_record_size: u32, /// Serial number of the NTFS volume. serial_number: u64, } @@ -39,27 +36,17 @@ impl Ntfs { let boot_sector = fs.read_le::()?; boot_sector.validate()?; - let bytes_per_sector = boot_sector.bpb.bytes_per_sector; - let sectors_per_cluster = boot_sector.bpb.sectors_per_cluster; - let bytes_per_cluster = sectors_per_cluster as u32 * bytes_per_sector as u32; - if bytes_per_cluster > MAXIMUM_CLUSTER_SIZE { - return Err(NtfsError::UnsupportedClusterSize { - expected: MAXIMUM_CLUSTER_SIZE, - actual: bytes_per_cluster, - }); - } - - let size = boot_sector.bpb.total_sectors * bytes_per_sector as u64; - let mft_position = boot_sector.bpb.mft_lcn * bytes_per_cluster as u64; - let file_record_size = BiosParameterBlock::record_size( - boot_sector.bpb.file_record_size_info, - bytes_per_cluster, - )?; - let serial_number = boot_sector.bpb.serial_number; + let bpb = boot_sector.bpb(); + let cluster_size = bpb.cluster_size()?; + let sector_size = bpb.sector_size(); + let size = bpb.total_sectors() * sector_size as u64; + let mft_position = bpb.mft_lcn() * cluster_size as u64; + let file_record_size = bpb.file_record_size()?; + let serial_number = bpb.serial_number(); Ok(Self { - bytes_per_sector, - sectors_per_cluster, + cluster_size, + sector_size, size, mft_position, file_record_size, @@ -68,8 +55,8 @@ impl Ntfs { } /// Returns the size of a single cluster, in bytes. - pub fn cluster_size(&self) -> u16 { - self.bytes_per_sector * self.sectors_per_cluster as u16 + pub fn cluster_size(&self) -> u32 { + self.cluster_size } /// Returns the [`NtfsFile`] for the `n`-th NTFS file record. @@ -92,7 +79,7 @@ impl Ntfs { .mft_position .checked_add(offset) .ok_or(NtfsError::InvalidNtfsFile { n })?; - NtfsFile::new(fs, position) + NtfsFile::new(&self, fs, position) } /// Returns the root [`Dir`] of this NTFS volume. @@ -102,7 +89,7 @@ impl Ntfs { /// Returns the size of a single sector in bytes. pub fn sector_size(&self) -> u16 { - self.bytes_per_sector + self.sector_size } /// Returns the 64-bit serial number of this NTFS volume. @@ -122,25 +109,13 @@ impl Ntfs { T: Read + Seek, { let volume_file = self.ntfs_file(fs, KnownNtfsFile::Volume as u64)?; - - // TODO: Replace by Iterator::try_find once stabilized. - let attribute = volume_file.attributes(fs).find(|attribute| { - let attribute = match attribute { - Ok(attribute) => attribute, - Err(_) => return true, - }; - let ty = match attribute.ty() { - Ok(ty) => ty, - Err(_) => return true, - }; - - ty == NtfsAttributeType::VolumeInformation - }); - let attribute = attribute.ok_or(NtfsError::AttributeNotFound { - position: volume_file.position(), - ty: NtfsAttributeType::VolumeName, - })??; - + let attribute = volume_file + .attributes() + .find_first_by_ty(fs, NtfsAttributeType::VolumeInformation) + .ok_or(NtfsError::AttributeNotFound { + position: volume_file.position(), + ty: NtfsAttributeType::VolumeName, + })??; let value = attribute.structured_value(fs)?; let volume_info = match value { NtfsStructuredValue::VolumeInformation(volume_info) => volume_info, @@ -162,21 +137,9 @@ impl Ntfs { T: Read + Seek, { let volume_file = iter_try!(self.ntfs_file(fs, KnownNtfsFile::Volume as u64)); - - // TODO: Replace by Iterator::try_find once stabilized. - let attribute = iter_try!(volume_file.attributes(fs).find(|attribute| { - let attribute = match attribute { - Ok(attribute) => attribute, - Err(_) => return true, - }; - let ty = match attribute.ty() { - Ok(ty) => ty, - Err(_) => return true, - }; - - ty == NtfsAttributeType::VolumeName - })?); - + let attribute = iter_try!(volume_file + .attributes() + .find_first_by_ty(fs, NtfsAttributeType::VolumeName)?); let value = iter_try!(attribute.structured_value(fs)); let volume_name = match value { NtfsStructuredValue::VolumeName(volume_name) => volume_name, diff --git a/src/ntfs_file.rs b/src/ntfs_file.rs index 4a180b4..4caea85 100644 --- a/src/ntfs_file.rs +++ b/src/ntfs_file.rs @@ -3,6 +3,7 @@ use crate::attribute::NtfsAttributes; use crate::error::{NtfsError, Result}; +use crate::ntfs::Ntfs; use binread::io::{Read, Seek, SeekFrom}; use binread::{BinRead, BinReaderExt}; use bitflags::bitflags; @@ -55,20 +56,25 @@ bitflags! { } } -pub struct NtfsFile { +pub struct NtfsFile<'n> { + ntfs: &'n Ntfs, header: NtfsFileRecordHeader, position: u64, } -impl NtfsFile { - pub(crate) fn new(fs: &mut T, position: u64) -> Result +impl<'n> NtfsFile<'n> { + pub(crate) fn new(ntfs: &'n Ntfs, fs: &mut T, position: u64) -> Result where T: Read + Seek, { fs.seek(SeekFrom::Start(position))?; let header = fs.read_le::()?; - let file = Self { header, position }; + let file = Self { + ntfs, + header, + position, + }; file.validate_signature()?; Ok(file) @@ -78,11 +84,8 @@ impl NtfsFile { self.header.allocated_size } - pub fn attributes<'a, T>(&self, fs: &'a mut T) -> NtfsAttributes<'a, T> - where - T: Read + Seek, - { - NtfsAttributes::new(fs, &self) + pub fn attributes(&self) -> NtfsAttributes<'n> { + NtfsAttributes::new(self.ntfs, &self) } pub(crate) fn first_attribute_offset(&self) -> u16 { diff --git a/src/structured_values/file_name.rs b/src/structured_values/file_name.rs index 71b0fd8..ce7e376 100644 --- a/src/structured_values/file_name.rs +++ b/src/structured_values/file_name.rs @@ -52,7 +52,7 @@ pub struct NtfsFileName { impl NtfsFileName { pub(crate) fn new( attribute_position: u64, - mut value_attached: NtfsAttributeValueAttached<'_, T>, + mut value_attached: NtfsAttributeValueAttached<'_, '_, T>, value_length: u64, ) -> Result where @@ -145,7 +145,7 @@ mod tests { let mft = ntfs .ntfs_file(&mut testfs1, KnownNtfsFile::MFT as u64) .unwrap(); - let mut mft_attributes = mft.attributes(&mut testfs1); + let mut mft_attributes = mft.attributes().attach(&mut testfs1); // Check the FileName attribute of the MFT. let attribute = mft_attributes.nth(1).unwrap().unwrap(); diff --git a/src/structured_values/index_root.rs b/src/structured_values/index_root.rs new file mode 100644 index 0000000..a8c1d44 --- /dev/null +++ b/src/structured_values/index_root.rs @@ -0,0 +1,58 @@ +// Copyright 2021 Colin Finck +// SPDX-License-Identifier: GPL-2.0-or-later + +use crate::attribute::NtfsAttributeType; +use crate::attribute_value::NtfsAttributeValueAttached; +use crate::error::{NtfsError, Result}; +use binread::io::{Read, Seek}; +use binread::{BinRead, BinReaderExt}; + +/// Size of all [`IndexRootHeader`] fields plus some reserved bytes. +const INDEX_ROOT_HEADER_SIZE: u64 = 32; + +#[derive(BinRead, Clone, Debug)] +struct IndexHeader { + entries_offset: u32, + index_size: u32, + allocated_size: u32, + flags: u8, +} + +#[derive(BinRead, Clone, Debug)] +struct IndexRootHeader { + ty: u32, + collation_rule: u32, + index_block_size: u32, + clusters_per_index_block: i8, + reserved: [u8; 3], + index: IndexHeader, +} + +#[derive(Clone, Debug)] +pub struct NtfsIndexRoot { + header: IndexRootHeader, +} + +impl NtfsIndexRoot { + pub(crate) fn new( + attribute_position: u64, + mut value_attached: NtfsAttributeValueAttached<'_, '_, T>, + value_length: u64, + ) -> Result + where + T: Read + Seek, + { + if value_length < INDEX_ROOT_HEADER_SIZE { + return Err(NtfsError::InvalidAttributeSize { + position: attribute_position, + ty: NtfsAttributeType::IndexRoot, + expected: INDEX_ROOT_HEADER_SIZE, + actual: value_length, + }); + } + + let header = value_attached.read_le::()?; + + Ok(Self { header }) + } +} diff --git a/src/structured_values/mod.rs b/src/structured_values/mod.rs index 99b3cee..bc089bb 100644 --- a/src/structured_values/mod.rs +++ b/src/structured_values/mod.rs @@ -3,6 +3,7 @@ mod attribute_list; mod file_name; +mod index_root; mod object_id; mod security_descriptor; mod standard_information; @@ -11,6 +12,7 @@ mod volume_name; pub use attribute_list::*; pub use file_name::*; +pub use index_root::*; pub use object_id::*; pub use security_descriptor::*; pub use standard_information::*; @@ -44,4 +46,5 @@ pub enum NtfsStructuredValue { ObjectId(NtfsObjectId), VolumeInformation(NtfsVolumeInformation), VolumeName(NtfsVolumeName), + IndexRoot(NtfsIndexRoot), } diff --git a/src/structured_values/object_id.rs b/src/structured_values/object_id.rs index adc96c8..db165e4 100644 --- a/src/structured_values/object_id.rs +++ b/src/structured_values/object_id.rs @@ -19,7 +19,7 @@ pub struct NtfsObjectId { impl NtfsObjectId { pub(crate) fn new( attribute_position: u64, - mut value_attached: NtfsAttributeValueAttached<'_, T>, + mut value_attached: NtfsAttributeValueAttached<'_, '_, T>, value_length: u64, ) -> Result where diff --git a/src/structured_values/standard_information.rs b/src/structured_values/standard_information.rs index 1be0b3b..dc68d2e 100644 --- a/src/structured_values/standard_information.rs +++ b/src/structured_values/standard_information.rs @@ -44,7 +44,7 @@ pub struct NtfsStandardInformation { impl NtfsStandardInformation { pub(crate) fn new( attribute_position: u64, - mut value_attached: NtfsAttributeValueAttached<'_, T>, + mut value_attached: NtfsAttributeValueAttached<'_, '_, T>, value_length: u64, ) -> Result where @@ -132,7 +132,7 @@ mod tests { let mft = ntfs .ntfs_file(&mut testfs1, KnownNtfsFile::MFT as u64) .unwrap(); - let mut mft_attributes = mft.attributes(&mut testfs1); + let mut mft_attributes = mft.attributes().attach(&mut testfs1); // Check the StandardInformation attribute of the MFT. let attribute = mft_attributes.nth(0).unwrap().unwrap(); diff --git a/src/structured_values/volume_information.rs b/src/structured_values/volume_information.rs index 57243ce..8b075fa 100644 --- a/src/structured_values/volume_information.rs +++ b/src/structured_values/volume_information.rs @@ -41,7 +41,7 @@ pub struct NtfsVolumeInformation { impl NtfsVolumeInformation { pub(crate) fn new( attribute_position: u64, - mut value_attached: NtfsAttributeValueAttached<'_, T>, + mut value_attached: NtfsAttributeValueAttached<'_, '_, T>, value_length: u64, ) -> Result where diff --git a/src/structured_values/volume_name.rs b/src/structured_values/volume_name.rs index 2f70ea7..eafeaab 100644 --- a/src/structured_values/volume_name.rs +++ b/src/structured_values/volume_name.rs @@ -23,7 +23,7 @@ pub struct NtfsVolumeName { impl NtfsVolumeName { pub(crate) fn new( attribute_position: u64, - value_attached: NtfsAttributeValueAttached<'_, T>, + value_attached: NtfsAttributeValueAttached<'_, '_, T>, value_length: u64, ) -> Result where diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..01708b8 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,43 @@ +use crate::error::{NtfsError, Result}; +use binread::io; +use binread::io::{Read, Seek, SeekFrom}; + +pub trait NtfsReadSeek { + fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> Result + where + T: Read + Seek; + + fn read_exact(&mut self, fs: &mut T, mut buf: &mut [u8]) -> Result<()> + where + T: Read + Seek, + { + // This implementation is taken from https://github.com/rust-lang/rust/blob/5662d9343f0696efcc38a1264656737c9f22d427/library/std/src/io/mod.rs + // It handles all corner cases properly and outputs the known `io` error messages. + while !buf.is_empty() { + match self.read(fs, buf) { + Ok(0) => break, + Ok(n) => { + buf = &mut buf[n..]; + } + Err(NtfsError::Io(e)) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + + if !buf.is_empty() { + Err(NtfsError::Io(io::Error::new( + io::ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + ))) + } else { + Ok(()) + } + } + + fn seek(&mut self, fs: &mut T, pos: SeekFrom) -> Result + where + T: Read + Seek; + + /// See [`std::io::Seek::stream_position`]. + fn stream_position(&mut self) -> Result; +} -- cgit v1.2.3