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

github.com/windirstat/ntfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Finck <colin@reactos.org>2021-05-03 20:04:05 +0300
committerColin Finck <colin@reactos.org>2021-05-03 20:04:05 +0300
commit0402611794e8d86814e25eba8515d00e1685ce4b (patch)
tree73f25a41aff194f3f0ba12c06fd60f83be1238d3
parent9ff4d41d5a57c15f7f299f48ed8c2c75dd7d5fcc (diff)
Implement reading non-resident values, `NtfsIndexRoot`, clusters >64K, and refactor many affected parts.
-rw-r--r--Cargo.toml2
-rw-r--r--src/attribute.rs134
-rw-r--r--src/attribute_value.rs467
-rw-r--r--src/boot_sector.rs90
-rw-r--r--src/error.rs23
-rw-r--r--src/lib.rs2
-rw-r--r--src/ntfs.rs97
-rw-r--r--src/ntfs_file.rs21
-rw-r--r--src/structured_values/file_name.rs4
-rw-r--r--src/structured_values/index_root.rs58
-rw-r--r--src/structured_values/mod.rs3
-rw-r--r--src/structured_values/object_id.rs2
-rw-r--r--src/structured_values/standard_information.rs4
-rw-r--r--src/structured_values/volume_information.rs2
-rw-r--r--src/structured_values/volume_name.rs2
-rw-r--r--src/traits.rs43
16 files changed, 716 insertions, 238 deletions
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 <colin@reactos.org>
// 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<T>(fs: &mut T, position: u64) -> Result<Self>
+impl<'n> NtfsAttribute<'n> {
+ fn new<T>(ntfs: &'n Ntfs, fs: &mut T, position: u64) -> Result<Self>
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<u64>,
}
-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<NtfsAttribute>;
+ 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<Self::Item> {
+ pub(crate) fn find_first_by_ty<T>(
+ &mut self,
+ fs: &mut T,
+ ty: NtfsAttributeType,
+ ) -> Option<Result<NtfsAttribute<'n>>>
+ 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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsAttribute<'n>>>
+ 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::<u32>());
+ iter_try!(fs.seek(SeekFrom::Start(position)));
+ let ty = iter_try!(fs.read_le::<u32>());
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<NtfsAttribute<'n>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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 <colin@reactos.org>
// 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<T>
-where
- T: Read + Seek,
-{
- fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> io::Result<usize>;
-
- 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<T> NtfsAttributeRead<T> for NtfsAttributeValue
-where
- T: Read + Seek,
-{
- fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> io::Result<usize> {
+impl<'n> NtfsReadSeek for NtfsAttributeValue<'n> {
+ fn read<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
+ 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<u64> {
+ fn seek<T>(&mut self, fs: &mut T, pos: SeekFrom) -> Result<u64>
+ 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<u64> {
+ 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<usize> {
- 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<u64> {
- 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<T> NtfsAttributeRead<T> for NtfsAttributeResidentValue
-where
- T: Read + Seek,
-{
- fn read(&mut self, fs: &mut T, buf: &mut [u8]) -> io::Result<usize> {
- let bytes_left = (self.length as u64).saturating_sub(self.seek_position);
+impl NtfsReadSeek for NtfsDataRun {
+ fn read<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
+ 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<u64> {
+ fn seek<T>(&mut self, _fs: &mut T, pos: SeekFrom) -> Result<u64>
+ 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<u64> {
+ Ok(self.stream_position)
+ }
}
-pub struct NtfsAttributeNonResidentValue {
- // TODO
+#[derive(Clone, Debug)]
+pub struct NtfsDataRuns<'n> {
+ ntfs: &'n Ntfs,
+ data_runs_range: Range<u64>,
}
-impl NtfsAttributeNonResidentValue {
- pub fn position(&self) -> u64 {
- panic!("TODO")
+impl<'n> NtfsDataRuns<'n> {
+ fn new(ntfs: &'n Ntfs, data_runs_range: Range<u64>) -> 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<T>(&mut self, fs: &mut T) -> Option<Result<NtfsDataRun>>
+ 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::<u8>());
+ 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<T>(&self, fs: &mut T, byte_count: u8) -> Result<u64>
+ where
+ T: Read + Seek,
+ {
+ const MAX_BYTE_COUNT: u8 = mem::size_of::<u64>() 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<T> NtfsAttributeRead<T> 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<usize> {
- 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<u64> {
- panic!("TODO")
+impl<'n, 'a, T> Iterator for NtfsDataRunsAttached<'n, 'a, T>
+where
+ T: Read + Seek,
+{
+ type Item = Result<NtfsDataRun>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<u64>,
+ /// 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<NtfsDataRun>,
+ /// Total stream position, in bytes.
+ stream_position: u64,
+}
+
+impl<'n> NtfsAttributeNonResidentValue<'n> {
+ pub(crate) fn new(ntfs: &'n Ntfs, data_runs_range: Range<u64>, 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<T>(&mut self, fs: &mut T, bytes_to_seek: SeekFrom) -> Result<u64>
+ 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<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
+ 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<T>(&mut self, fs: &mut T, pos: SeekFrom) -> Result<u64>
+ 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<u64> {
+ 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<u32> {
+ /// 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<u32> {
+ 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<u32> {
- if size_info_value > 0 {
+ fn record_size(&self, size_info: i8) -> Result<u32> {
+ /// 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<binread::io::Error> 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<NtfsError> 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::<BootSector>()?;
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<T>(fs: &mut T, position: u64) -> Result<Self>
+impl<'n> NtfsFile<'n> {
+ pub(crate) fn new<T>(ntfs: &'n Ntfs, fs: &mut T, position: u64) -> Result<Self>
where
T: Read + Seek,
{
fs.seek(SeekFrom::Start(position))?;
let header = fs.read_le::<NtfsFileRecordHeader>()?;
- 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<T>(
attribute_position: u64,
- mut value_attached: NtfsAttributeValueAttached<'_, T>,
+ mut value_attached: NtfsAttributeValueAttached<'_, '_, T>,
value_length: u64,
) -> Result<Self>
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 <colin@reactos.org>
+// 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<T>(
+ attribute_position: u64,
+ mut value_attached: NtfsAttributeValueAttached<'_, '_, T>,
+ value_length: u64,
+ ) -> Result<Self>
+ 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::<IndexRootHeader>()?;
+
+ 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<T>(
attribute_position: u64,
- mut value_attached: NtfsAttributeValueAttached<'_, T>,
+ mut value_attached: NtfsAttributeValueAttached<'_, '_, T>,
value_length: u64,
) -> Result<Self>
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<T>(
attribute_position: u64,
- mut value_attached: NtfsAttributeValueAttached<'_, T>,
+ mut value_attached: NtfsAttributeValueAttached<'_, '_, T>,
value_length: u64,
) -> Result<Self>
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<T>(
attribute_position: u64,
- mut value_attached: NtfsAttributeValueAttached<'_, T>,
+ mut value_attached: NtfsAttributeValueAttached<'_, '_, T>,
value_length: u64,
) -> Result<Self>
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<T>(
attribute_position: u64,
- value_attached: NtfsAttributeValueAttached<'_, T>,
+ value_attached: NtfsAttributeValueAttached<'_, '_, T>,
value_length: u64,
) -> Result<Self>
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<T>(&mut self, fs: &mut T, buf: &mut [u8]) -> Result<usize>
+ where
+ T: Read + Seek;
+
+ fn read_exact<T>(&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<T>(&mut self, fs: &mut T, pos: SeekFrom) -> Result<u64>
+ where
+ T: Read + Seek;
+
+ /// See [`std::io::Seek::stream_position`].
+ fn stream_position(&mut self) -> Result<u64>;
+}