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

github.com/windirstat/mft.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOmer Ben-Amram <omerbenamram@gmail.com>2019-05-12 17:15:39 +0300
committerOmer Ben-Amram <omerbenamram@gmail.com>2019-05-12 17:15:39 +0300
commit0720cb220035316fcf5bfb384b11cfbb748c9dc7 (patch)
treee5a95b17be01ab0a0ce4584cf8f20028c190f0bb /src
parente8f6804dca46eecd819aeeb61213da5a024a2e9b (diff)
MFT attributes are now lazy
Diffstat (limited to 'src')
-rw-r--r--src/attribute.rs258
-rw-r--r--src/attribute/header.rs243
-rw-r--r--src/attribute/mod.rs83
-rw-r--r--src/attribute/raw.rs15
-rw-r--r--src/attribute/x10.rs (renamed from src/attr_x10.rs)8
-rw-r--r--src/attribute/x30.rs (renamed from src/attr_x30.rs)2
-rw-r--r--src/bin/mft_dump.rs4
-rw-r--r--src/entry.rs147
-rw-r--r--src/err.rs14
-rw-r--r--src/lib.rs7
-rw-r--r--src/mft.rs23
11 files changed, 447 insertions, 357 deletions
diff --git a/src/attribute.rs b/src/attribute.rs
deleted file mode 100644
index 17faad2..0000000
--- a/src/attribute.rs
+++ /dev/null
@@ -1,258 +0,0 @@
-use crate::attr_x10::StandardInfoAttr;
-use crate::attr_x30::FileNameAttr;
-use crate::err::{self, Result};
-use crate::utils::read_utf16_string;
-use crate::{utils, ReadSeek};
-
-
-use bitflags::bitflags;
-use byteorder::{LittleEndian, ReadBytesExt};
-
-use serde::{ser, Serialize};
-use std::io::Read;
-
-#[derive(Clone, Debug)]
-pub struct RawAttribute(pub Vec<u8>);
-
-impl ser::Serialize for RawAttribute {
- fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
- where
- S: ser::Serializer,
- {
- serializer.serialize_str(&utils::to_hex_string(&self.0).to_string())
- }
-}
-
-#[derive(Serialize, Clone, Debug)]
-#[serde(untagged)]
-pub enum AttributeContent {
- Raw(RawAttribute),
- AttrX10(StandardInfoAttr),
- AttrX30(FileNameAttr),
- None,
-}
-
-bitflags! {
- #[derive(Default)]
- pub struct AttributeDataFlags: u16 {
- const IS_COMPRESSED = 0x0001;
- const COMPRESSION_MASK = 0x00FF;
- const ENCRYPTED = 0x4000;
- const SPARSE = 0x8000;
- }
-}
-
-pub fn serialize_attr_data_flags<S>(
- item: &AttributeDataFlags,
- serializer: S,
-) -> ::std::result::Result<S::Ok, S::Error>
-where
- S: ser::Serializer,
-{
- serializer.serialize_str(&format!("{:?}", item))
-}
-
-#[derive(Serialize, Clone, Debug, Default)]
-pub struct AttributeHeader {
- pub attribute_type: u32,
- #[serde(skip_serializing)]
- pub attribute_size: u32,
- pub resident_flag: u8, // 0 -> resident; 1 -> non-resident
- #[serde(skip_serializing)]
- pub name_size: u8,
- #[serde(skip_serializing)]
- pub name_offset: u16,
- #[serde(serialize_with = "serialize_attr_data_flags")]
- pub data_flags: AttributeDataFlags,
- pub id: u16,
- pub name: String,
- // 16
- pub residential_header: ResidentialHeader,
-}
-
-impl AttributeHeader {
- pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<AttributeHeader> {
- let current_offset = stream.tell()?;
-
- let attribute_type = stream.read_u32::<LittleEndian>()?;
- // The attribute list is terminated with 0xFFFFFFFF ($END).
- if attribute_type == 0xFFFF_FFFF {
- return Ok(AttributeHeader {
- attribute_type: 0xFFFF_FFFF,
- ..Default::default()
- });
- }
-
- let attribute_size = stream.read_u32::<LittleEndian>()?;
- let resident_flag = stream.read_u8()?;
- let name_size = stream.read_u8()?;
- let name_offset = stream.read_u16::<LittleEndian>()?;
- let data_flags = AttributeDataFlags::from_bits_truncate(stream.read_u16::<LittleEndian>()?);
- let id = stream.read_u16::<LittleEndian>()?;
-
- let residential_header = match resident_flag {
- 0 => ResidentialHeader::Resident(ResidentHeader::new(stream)?),
- 1 => ResidentialHeader::NonResident(NonResidentHeader::new(stream)?),
- _ => {
- return err::UnhandledResidentFlag {
- flag: resident_flag,
- offset: current_offset,
- }
- .fail();
- }
- };
-
- let name = if name_size > 0 {
- read_utf16_string(stream, Some(name_size as usize))?
- } else {
- String::new()
- };
-
- Ok(AttributeHeader {
- attribute_type,
- attribute_size,
- resident_flag,
- name_size,
- name_offset,
- data_flags,
- id,
- name,
- residential_header,
- })
- }
-}
-
-#[derive(Serialize, Clone, Debug)]
-#[serde(untagged)]
-pub enum ResidentialHeader {
- None,
- Resident(ResidentHeader),
- NonResident(NonResidentHeader),
-}
-
-impl Default for ResidentialHeader {
- fn default() -> Self {
- ResidentialHeader::None
- }
-}
-
-#[derive(Serialize, Clone, Debug)]
-pub struct ResidentHeader {
- #[serde(skip_serializing)]
- pub data_size: u32,
- #[serde(skip_serializing)]
- pub data_offset: u16,
- pub index_flag: u8,
- #[serde(skip_serializing)]
- pub padding: u8,
-}
-
-impl ResidentHeader {
- pub fn new<R: Read>(reader: &mut R) -> Result<ResidentHeader> {
- Ok(ResidentHeader {
- data_size: reader.read_u32::<LittleEndian>()?,
- data_offset: reader.read_u16::<LittleEndian>()?,
- index_flag: reader.read_u8()?,
- padding: reader.read_u8()?,
- })
- }
-}
-
-#[derive(Serialize, Clone, Debug)]
-pub struct NonResidentHeader {
- pub vnc_first: u64,
- pub vnc_last: u64,
- #[serde(skip_serializing)]
- pub datarun_offset: u16,
- pub unit_compression_size: u16,
- #[serde(skip_serializing)]
- pub padding: u32,
- pub size_allocated: u64,
- pub size_real: u64,
- pub size_compressed: u64,
- // pub size_total_allocated: Option<u64>
-}
-impl NonResidentHeader {
- pub fn new<R: Read>(reader: &mut R) -> Result<NonResidentHeader> {
- let vnc_first = reader.read_u64::<LittleEndian>()?;
- let vnc_last = reader.read_u64::<LittleEndian>()?;
- let datarun_offset = reader.read_u16::<LittleEndian>()?;
- let unit_compression_size = reader.read_u16::<LittleEndian>()?;
- let padding = reader.read_u32::<LittleEndian>()?;
- let size_allocated = reader.read_u64::<LittleEndian>()?;
- let size_real = reader.read_u64::<LittleEndian>()?;
- let size_compressed = reader.read_u64::<LittleEndian>()?;
-
- // if residential_header.unit_compression_size > 0 {
- // residential_header.size_total_allocated = Some(reader.read_u64::<LittleEndian>()?);
- // }
-
- Ok(NonResidentHeader {
- vnc_first,
- vnc_last,
- datarun_offset,
- unit_compression_size,
- padding,
- size_allocated,
- size_real,
- size_compressed,
- })
- }
-}
-
-#[derive(Serialize, Clone, Debug)]
-pub struct MftAttribute {
- pub header: AttributeHeader,
- pub content: AttributeContent,
-}
-
-#[cfg(test)]
-mod tests {
- use super::AttributeHeader;
- use std::io::Cursor;
-
- #[test]
- fn attribute_test_01_resident() {
- let raw: &[u8] = &[
- 0x10, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- ];
- let mut cursor = Cursor::new(raw);
-
- let attribute_header = match AttributeHeader::from_stream(&mut cursor) {
- Ok(attribute_header) => attribute_header,
- Err(error) => panic!(error),
- };
-
- assert_eq!(attribute_header.attribute_type, 16);
- assert_eq!(attribute_header.attribute_size, 96);
- assert_eq!(attribute_header.resident_flag, 0);
- assert_eq!(attribute_header.name_size, 0);
- assert_eq!(attribute_header.name_offset, 0);
- }
-
- #[test]
- fn attribute_test_01_nonresident() {
- let raw: &[u8] = &[
- 0x80, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x1E, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x33, 0x20, 0xC8, 0x00, 0x00, 0x00,
- 0x0C, 0x32, 0xA0, 0x56, 0xE3, 0xE6, 0x24, 0x00, 0xFF, 0xFF,
- ];
-
- let mut cursor = Cursor::new(raw);
-
- let attribute_header = match AttributeHeader::from_stream(&mut cursor) {
- Ok(attribute_header) => attribute_header,
- Err(error) => panic!(error),
- };
-
- assert_eq!(attribute_header.attribute_type, 128);
- assert_eq!(attribute_header.attribute_size, 80);
- assert_eq!(attribute_header.resident_flag, 1);
- assert_eq!(attribute_header.name_size, 0);
- assert_eq!(attribute_header.name_offset, 64);
- }
-}
diff --git a/src/attribute/header.rs b/src/attribute/header.rs
new file mode 100644
index 0000000..13126d4
--- /dev/null
+++ b/src/attribute/header.rs
@@ -0,0 +1,243 @@
+use crate::attribute::{serialize_attr_data_flags, AttributeDataFlags, AttributeType};
+use crate::err::{self, Result};
+use crate::utils::read_utf16_string;
+use crate::ReadSeek;
+
+use byteorder::{LittleEndian, ReadBytesExt};
+use num_traits::FromPrimitive;
+use serde::Serialize;
+use std::io::Read;
+
+/// Represents the union defined in
+/// https://docs.microsoft.com/en-us/windows/desktop/devnotes/attribute-record-header
+#[derive(Serialize, Clone, Debug)]
+pub struct AttributeHeader {
+ pub type_code: AttributeType,
+ /// The size of the attribute record, in bytes.
+ /// This value reflects the required size for the record variant and is always rounded to the nearest quadword boundary.
+ pub record_length: u32,
+ /// If the FormCode member is RESIDENT_FORM (0x00), the union is a Resident structure.
+ /// If FormCode is NONRESIDENT_FORM (0x01), the union is a Nonresident structure.
+ pub form_code: u8,
+ pub residential_header: ResidentialHeader,
+ /// The size of the optional attribute name, in characters, or 0 if there is no attribute name.
+ /// The maximum attribute name length is 255 characters.
+ pub name_size: u8,
+ /// The offset of the attribute name from the start of the attribute record, in bytes.
+ /// If the NameLength member is 0, this member is undefined.
+ pub name_offset: Option<u16>,
+ #[serde(serialize_with = "serialize_attr_data_flags")]
+ pub data_flags: AttributeDataFlags,
+ /// The unique instance for this attribute in the file record.
+ pub instance: u16,
+ pub name: String,
+}
+
+#[derive(Serialize, Clone, Debug)]
+#[serde(untagged)]
+pub enum ResidentialHeader {
+ Resident(ResidentHeader),
+ NonResident(NonResidentHeader),
+}
+
+impl AttributeHeader {
+ /// Tries to read an AttributeHeader from the stream.
+ /// Will return `None` if the type code is $END.
+ pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<Option<AttributeHeader>> {
+ let type_code_value = stream.read_u32::<LittleEndian>()?;
+
+ if type_code_value == 0xFFFF_FFFF {
+ return Ok(None);
+ }
+
+ let type_code = match AttributeType::from_u32(type_code_value) {
+ Some(attribute_type) => attribute_type,
+ None => {
+ return err::UnknownAttributeType {
+ attribute_type: type_code_value,
+ }
+ .fail()
+ }
+ };
+
+ let attribute_size = stream.read_u32::<LittleEndian>()?;
+ let resident_flag = stream.read_u8()?;
+ let name_size = stream.read_u8()?;
+ let name_offset = {
+ // We always read the two bytes to advance the stream.
+ let value = stream.read_u16::<LittleEndian>()?;
+ if name_size > 0 {
+ Some(value)
+ } else {
+ None
+ }
+ };
+
+ let data_flags = AttributeDataFlags::from_bits_truncate(stream.read_u16::<LittleEndian>()?);
+ let id = stream.read_u16::<LittleEndian>()?;
+
+ let residential_header = match resident_flag {
+ 0 => ResidentialHeader::Resident(ResidentHeader::new(stream)?),
+ 1 => ResidentialHeader::NonResident(NonResidentHeader::new(stream)?),
+ _ => {
+ return err::UnhandledResidentFlag {
+ flag: resident_flag,
+ offset: stream.tell()?,
+ }
+ .fail();
+ }
+ };
+
+ let name = if name_size > 0 {
+ read_utf16_string(stream, Some(name_size as usize))?
+ } else {
+ String::new()
+ };
+
+ Ok(Some(AttributeHeader {
+ type_code,
+ record_length: attribute_size,
+ form_code: resident_flag,
+ name_size,
+ name_offset,
+ data_flags,
+ instance: id,
+ name,
+ residential_header,
+ }))
+ }
+}
+
+#[derive(Serialize, Clone, Debug)]
+pub struct ResidentHeader {
+ #[serde(skip_serializing)]
+ /// The size of the attribute value, in bytes.
+ pub data_size: u32,
+ #[serde(skip_serializing)]
+ /// The offset to the value from the start of the attribute record, in bytes.
+ pub data_offset: u16,
+ pub index_flag: u8,
+ #[serde(skip_serializing)]
+ pub padding: u8,
+}
+
+impl ResidentHeader {
+ pub fn new<R: Read>(reader: &mut R) -> Result<ResidentHeader> {
+ Ok(ResidentHeader {
+ data_size: reader.read_u32::<LittleEndian>()?,
+ data_offset: reader.read_u16::<LittleEndian>()?,
+ index_flag: reader.read_u8()?,
+ padding: reader.read_u8()?,
+ })
+ }
+}
+
+#[derive(Serialize, Clone, Debug)]
+pub struct NonResidentHeader {
+ /// The lowest virtual cluster number (VCN) covered by this attribute record.
+ pub vnc_first: u64,
+ /// The highest VCN covered by this attribute record.
+ pub vnc_last: u64,
+ #[serde(skip_serializing)]
+ /// The offset to the mapping pairs array from the start of the attribute record, in bytes. For more information, see Remarks.
+ pub datarun_offset: u16,
+ /// Reserved UCHAR[6]
+ pub unit_compression_size: u16,
+ #[serde(skip_serializing)]
+ pub padding: u32,
+
+ /// The allocated size of the file, in bytes.
+ /// This value is an even multiple of the cluster size.
+ /// This member is not valid if the LowestVcn member is nonzero.
+ pub allocated_length: u64,
+ pub file_size: u64,
+ /// Contains the valid data size in number of bytes.
+ /// This value is not valid if the first VCN is nonzero.
+ pub valid_data_length: u64,
+ pub total_allocated: Option<u64>,
+}
+
+impl NonResidentHeader {
+ pub fn new<R: Read>(reader: &mut R) -> Result<NonResidentHeader> {
+ let vnc_first = reader.read_u64::<LittleEndian>()?;
+ let vnc_last = reader.read_u64::<LittleEndian>()?;
+ let datarun_offset = reader.read_u16::<LittleEndian>()?;
+ let unit_compression_size = reader.read_u16::<LittleEndian>()?;
+ let padding = reader.read_u32::<LittleEndian>()?;
+ let allocated_length = reader.read_u64::<LittleEndian>()?;
+ let file_size = reader.read_u64::<LittleEndian>()?;
+ let valid_data_length = reader.read_u64::<LittleEndian>()?;
+
+ let total_allocated = if unit_compression_size > 0 {
+ Some(reader.read_u64::<LittleEndian>()?)
+ } else {
+ None
+ };
+
+ Ok(NonResidentHeader {
+ vnc_first,
+ vnc_last,
+ datarun_offset,
+ unit_compression_size,
+ padding,
+ allocated_length,
+ file_size,
+ valid_data_length,
+ total_allocated,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::AttributeHeader;
+ use crate::attribute::AttributeType;
+ use std::io::Cursor;
+
+ #[test]
+ fn attribute_test_01_resident() {
+ let raw: &[u8] = &[
+ 0x10, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ ];
+
+ let mut cursor = Cursor::new(raw);
+
+ let attribute_header = AttributeHeader::from_stream(&mut cursor)
+ .expect("Should not be $End")
+ .expect("Shold parse correctly");
+
+ assert_eq!(
+ attribute_header.type_code,
+ AttributeType::StandardInformation
+ );
+ assert_eq!(attribute_header.record_length, 96);
+ assert_eq!(attribute_header.form_code, 0);
+ assert_eq!(attribute_header.name_size, 0);
+ assert_eq!(attribute_header.name_offset, None);
+ }
+
+ #[test]
+ fn attribute_test_01_nonresident() {
+ let raw: &[u8] = &[
+ 0x80, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x1E, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x33, 0x20, 0xC8, 0x00, 0x00, 0x00,
+ 0x0C, 0x32, 0xA0, 0x56, 0xE3, 0xE6, 0x24, 0x00, 0xFF, 0xFF,
+ ];
+
+ let mut cursor = Cursor::new(raw);
+
+ let attribute_header = AttributeHeader::from_stream(&mut cursor)
+ .expect("Should not be $End")
+ .expect("Shold parse correctly");
+
+ assert_eq!(attribute_header.type_code, AttributeType::DATA);
+ assert_eq!(attribute_header.record_length, 80);
+ assert_eq!(attribute_header.form_code, 1);
+ assert_eq!(attribute_header.name_size, 0);
+ assert_eq!(attribute_header.name_offset, None);
+ }
+}
diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs
new file mode 100644
index 0000000..fab7f87
--- /dev/null
+++ b/src/attribute/mod.rs
@@ -0,0 +1,83 @@
+pub mod header;
+pub mod raw;
+pub mod x10;
+pub mod x30;
+
+use crate::err::{self, Result};
+use bitflags::bitflags;
+use num_traits::FromPrimitive;
+
+use crate::attribute::raw::RawAttribute;
+use crate::attribute::x10::StandardInfoAttr;
+use crate::attribute::x30::FileNameAttr;
+
+use crate::attribute::header::AttributeHeader;
+use crate::ReadSeek;
+use serde::{ser, Serialize};
+use std::io::{Cursor, Read, Seek};
+
+#[derive(Serialize, Clone, Debug)]
+pub struct Attribute {
+ pub header: AttributeHeader,
+ pub data: MftAttributeContent,
+}
+
+#[derive(Serialize, Clone, Debug)]
+#[serde(untagged)]
+pub enum MftAttributeContent {
+ Raw(RawAttribute),
+ AttrX10(StandardInfoAttr),
+ AttrX30(FileNameAttr),
+ /// Empty - used when data is non resident.
+ None,
+}
+
+/// MFT Possible attribute types, from https://docs.microsoft.com/en-us/windows/desktop/devnotes/attribute-list-entry
+#[derive(Serialize, Debug, Clone, FromPrimitive, PartialOrd, PartialEq)]
+#[repr(u32)]
+pub enum AttributeType {
+ /// File attributes (such as read-only and archive), time stamps (such as file creation and last modified), and the hard link count.
+ StandardInformation = 0x10_u32,
+ /// A list of attributes that make up the file and the file reference of the MFT file record in which each attribute is located.
+ AttributeList = 0x20_u32,
+ /// The name of the file, in Unicode characters.
+ FileName = 0x30_u32,
+ /// An 16-byte object identifier assigned by the link-tracking service.
+ ObjectId = 0x40_u32,
+ /// The volume label.
+ /// Present in the $Volume file.
+ VolumeName = 0x60_u32,
+ /// The volume information.
+ /// Present in the $Volume file.
+ VolumeInformation = 0x70_u32,
+ /// The contents of the file.
+ DATA = 0x80_u32,
+ /// Used to implement filename allocation for large directories.
+ IndexRoot = 0x90_u32,
+ /// Used to implement filename allocation for large directories.
+ IndexAllocation = 0xA0_u32,
+ /// A bitmap index for a large directory.
+ BITMAP = 0xB0_u32,
+ /// The reparse point data.
+ ReparsePoint = 0xC0_u32,
+}
+
+bitflags! {
+ #[derive(Default)]
+ pub struct AttributeDataFlags: u16 {
+ const IS_COMPRESSED = 0x0001;
+ const COMPRESSION_MASK = 0x00FF;
+ const ENCRYPTED = 0x4000;
+ const SPARSE = 0x8000;
+ }
+}
+
+pub fn serialize_attr_data_flags<S>(
+ item: &AttributeDataFlags,
+ serializer: S,
+) -> ::std::result::Result<S::Ok, S::Error>
+where
+ S: ser::Serializer,
+{
+ serializer.serialize_str(&format!("{:?}", item))
+}
diff --git a/src/attribute/raw.rs b/src/attribute/raw.rs
new file mode 100644
index 0000000..bfc7fac
--- /dev/null
+++ b/src/attribute/raw.rs
@@ -0,0 +1,15 @@
+use crate::utils;
+use serde::ser;
+
+/// Placeholder attribute for currently unparsed attributes.
+#[derive(Clone, Debug)]
+pub struct RawAttribute(pub Vec<u8>);
+
+impl ser::Serialize for RawAttribute {
+ fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ serializer.serialize_str(&utils::to_hex_string(&self.0).to_string())
+ }
+}
diff --git a/src/attr_x10.rs b/src/attribute/x10.rs
index 3366714..3b0f1fa 100644
--- a/src/attr_x10.rs
+++ b/src/attribute/x10.rs
@@ -1,12 +1,12 @@
use crate::err::{self, Result};
+use crate::ReadSeek;
use byteorder::{LittleEndian, ReadBytesExt};
use chrono::{DateTime, Utc};
use log::trace;
use serde::Serialize;
-use std::io::Read;
-use winstructs::timestamp::WinTimestamp;
use snafu::ResultExt;
+use winstructs::timestamp::WinTimestamp;
#[derive(Serialize, Debug, Clone)]
pub struct StandardInfoAttr {
@@ -32,7 +32,7 @@ impl StandardInfoAttr {
/// Parse a raw buffer.
///
/// ```
- /// use mft::attr_x10::StandardInfoAttr;
+ /// use mft::attribute::x10::StandardInfoAttr;
/// # use std::io::Cursor;
/// # fn test_standard_information() {
/// let attribute_buffer: &[u8] = &[
@@ -58,7 +58,7 @@ impl StandardInfoAttr {
/// assert_eq!(attribute.usn, 8768215144);
/// # }
/// ```
- pub fn from_reader<R: Read>(reader: &mut R) -> Result<StandardInfoAttr> {
+ pub fn from_reader<S: ReadSeek>(reader: &mut S) -> Result<StandardInfoAttr> {
trace!("StandardInfoAttr");
let created = WinTimestamp::from_reader(reader)
.context(err::FailedToReadWindowsTime)?
diff --git a/src/attr_x30.rs b/src/attribute/x30.rs
index 63564e7..9ab6069 100644
--- a/src/attr_x30.rs
+++ b/src/attribute/x30.rs
@@ -39,7 +39,7 @@ impl FileNameAttr {
/// Parse a raw buffer.
///
/// ```
- /// use mft::attr_x30::FileNameAttr;
+ /// use mft::attribute::x30::FileNameAttr;
/// # use std::io::Cursor;
/// # fn test_filename_attribute() {
/// let attribute_buffer: &[u8] = &[
diff --git a/src/bin/mft_dump.rs b/src/bin/mft_dump.rs
index 3f7037d..b516f76 100644
--- a/src/bin/mft_dump.rs
+++ b/src/bin/mft_dump.rs
@@ -1,11 +1,11 @@
use clap::{App, Arg};
use env_logger;
use log::{info, warn};
-use mft::mft::MftHandler;
+use mft::mft::MftParser;
fn process_file(filename: &str, indent: bool) -> bool {
info!("Opening file {}", filename);
- let mut mft_handler = match MftHandler::from_path(filename) {
+ let mut mft_handler = match MftParser::from_path(filename) {
Ok(mft_handler) => mft_handler,
Err(error) => {
warn!("Could not parse file: {} [error: {}]", filename, error);
diff --git a/src/entry.rs b/src/entry.rs
index 6f45b5c..4ae44f3 100644
--- a/src/entry.rs
+++ b/src/entry.rs
@@ -1,8 +1,5 @@
-use crate::err::{self, Result};
-
-use crate::attr_x10::StandardInfoAttr;
-use crate::attr_x30::FileNameAttr;
use crate::enumerator::PathMapping;
+use crate::err::{self, Result};
use crate::{attribute, ReadSeek};
use log::debug;
@@ -13,12 +10,17 @@ use winstructs::ntfs::mft_reference::MftReference;
use byteorder::{LittleEndian, ReadBytesExt};
use bitflags::bitflags;
-use serde::{ser, Serialize};
+use serde::{ser, Serialize, Serializer};
-use crate::attribute::MftAttribute;
-use std::io::Cursor;
+use crate::attribute::header::{AttributeHeader, ResidentialHeader};
+use crate::attribute::x10::StandardInfoAttr;
+use crate::attribute::{Attribute, AttributeType, MftAttributeContent};
+
+use crate::attribute::raw::RawAttribute;
+use crate::attribute::x30::FileNameAttr;
use std::io::Read;
use std::io::SeekFrom;
+use std::io::{Cursor, Seek};
//https://github.com/libyal/libfsntfs/blob/master/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#5-the-master-file-table-mft
@@ -120,10 +122,11 @@ impl EntryHeader {
}
}
+// TODO: manually implement serialize to dump attributes.
#[derive(Serialize, Debug)]
pub struct MftEntry {
pub header: EntryHeader,
- pub attributes: Vec<MftAttribute>,
+ pub data: Vec<u8>,
}
impl MftEntry {
@@ -133,11 +136,9 @@ impl MftEntry {
// Get Header
let entry_header = EntryHeader::from_reader(&mut cursor, entry)?;
- let attributes = Self::read_attributes(&entry_header, &mut cursor)?;
-
Ok(MftEntry {
header: entry_header,
- attributes,
+ data: buffer,
})
}
@@ -150,12 +151,12 @@ impl MftEntry {
}
pub fn get_pathmap(&self) -> Option<PathMapping> {
- for attribute in self.attributes.iter() {
- if let attribute::AttributeContent::AttrX30(ref attrib) = attribute.content {
+ for attribute in self.iter_attributes().filter_map(|a| a.ok()) {
+ if let attribute::MftAttributeContent::AttrX30(ref attrib) = attribute.data {
if attrib.namespace != 2 {
return Some(PathMapping {
name: attrib.name.clone(),
- parent: attrib.parent.clone(),
+ parent: attrib.parent,
});
}
}
@@ -196,66 +197,68 @@ impl MftEntry {
// }
// }
- fn read_attributes<S: ReadSeek>(
- header: &EntryHeader,
- buffer: &mut S,
- ) -> Result<Vec<MftAttribute>> {
- let mut current_offset = buffer.seek(SeekFrom::Start(u64::from(header.fst_attr_offset)))?;
-
- let mut attributes = vec![];
-
- loop {
- let attribute_header = attribute::AttributeHeader::from_stream(buffer)?;
-
- // The attribute list is terminated with 0xFFFFFFFF ($END).
- if attribute_header.attribute_type == 0xFFFF_FFFF {
- break;
- }
-
- match attribute_header.residential_header {
- attribute::ResidentialHeader::Resident(ref header) => {
- // Create attribute content to parse buffer into
- // Get attribute contents
- let attr_content = match attribute_header.attribute_type {
- 0x10 => attribute::AttributeContent::AttrX10(
- StandardInfoAttr::from_reader(buffer)?,
- ),
- 0x30 => {
- attribute::AttributeContent::AttrX30(FileNameAttr::from_reader(buffer)?)
- }
- _ => {
- let mut content_buffer = vec![0; header.data_size as usize];
- buffer.read_exact(&mut content_buffer)?;
-
- attribute::AttributeContent::Raw(attribute::RawAttribute(
- content_buffer,
- ))
+ fn iter_attributes(&self) -> impl Iterator<Item = Result<Attribute>> + '_ {
+ let mut cursor = Cursor::new(&self.data);
+ let start_offset = u64::from(self.header.fst_attr_offset);
+
+ std::iter::from_fn(move || {
+ match cursor
+ .seek(SeekFrom::Start(start_offset))
+ .eager_context(err::IoError)
+ {
+ Ok(_) => {}
+ Err(e) => return Some(Err(e)),
+ };
+
+ match AttributeHeader::from_stream(&mut cursor) {
+ Ok(maybe_header) => match maybe_header {
+ Some(header) => {
+ // Check if the header is resident, and if it is, read the attribute content.
+ match header.residential_header {
+ ResidentialHeader::Resident(ref resident) => match header.type_code {
+ AttributeType::StandardInformation => {
+ match StandardInfoAttr::from_reader(&mut cursor) {
+ Ok(content) => Some(Ok(Attribute {
+ header,
+ data: MftAttributeContent::AttrX10(content),
+ })),
+ Err(e) => Some(Err(e)),
+ }
+ }
+ AttributeType::FileName => {
+ match FileNameAttr::from_reader(&mut cursor) {
+ Ok(content) => Some(Ok(Attribute {
+ header,
+ data: MftAttributeContent::AttrX30(content),
+ })),
+ Err(e) => Some(Err(e)),
+ }
+ }
+ _ => {
+ let mut data = vec![0_u8; resident.data_size as usize];
+
+ match cursor.read_exact(&mut data).eager_context(err::IoError) {
+ Ok(_) => {}
+ Err(err) => return Some(Err(err)),
+ };
+
+ Some(Ok(Attribute {
+ header,
+ data: MftAttributeContent::Raw(RawAttribute(data)),
+ }))
+ }
+ },
+ ResidentialHeader::NonResident(_) => Some(Ok(Attribute {
+ header,
+ data: MftAttributeContent::None,
+ })),
}
- };
-
- attributes.push(attribute::MftAttribute {
- header: attribute_header.clone(),
- content: attr_content,
- });
- }
- attribute::ResidentialHeader::NonResident(_) => {
- // No content, so push header into attributes
- attributes.push(attribute::MftAttribute {
- header: attribute_header.clone(),
- content: attribute::AttributeContent::None,
- });
- }
- attribute::ResidentialHeader::None => {
- // Not sure about this...
- }
+ }
+ None => None,
+ },
+ Err(e) => Some(Err(e)),
}
-
- current_offset = buffer.seek(SeekFrom::Start(
- current_offset + u64::from(attribute_header.attribute_size),
- ))?;
- }
-
- Ok(attributes)
+ })
}
}
diff --git a/src/err.rs b/src/err.rs
index 0ab083b..ca0135f 100644
--- a/src/err.rs
+++ b/src/err.rs
@@ -1,4 +1,4 @@
-use snafu::Snafu;
+use snafu::{Backtrace, Snafu};
use std::path::PathBuf;
use std::{io, result};
@@ -8,7 +8,10 @@ pub type Result<T> = result::Result<T, Error>;
#[snafu(visibility(pub(crate)))]
pub enum Error {
#[snafu(display("An I/O error has occurred: {}", "source"))]
- IoError { source: std::io::Error },
+ IoError {
+ source: std::io::Error,
+ backtrace: Backtrace,
+ },
#[snafu(display("Failed to open file {}: {}", path.display(), source))]
FailedToOpenFile {
path: PathBuf,
@@ -18,6 +21,8 @@ pub enum Error {
InvalidFilename,
#[snafu(display("Bad signature: {:04X}", bad_sig))]
InvalidEntrySignature { bad_sig: u32 },
+ #[snafu(display("Unknown `AttributeType`: {:04X}", attribute_type))]
+ UnknownAttributeType { attribute_type: u32 },
#[snafu(display("Unhandled resident flag: {} (offset: {})", flag, offset))]
UnhandledResidentFlag { flag: u8, offset: u64 },
#[snafu(display("Expected usa_offset `{}` to equal 48", offset))]
@@ -32,6 +37,9 @@ pub enum Error {
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
- Error::IoError { source: err }
+ Error::IoError {
+ source: err,
+ backtrace: Backtrace::new(),
+ }
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 4d3d4fe..d9e3383 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,11 +1,12 @@
+#[macro_use]
+extern crate num_derive;
+
use std::io::{self, Read, Seek, SeekFrom};
-pub mod err;
-pub mod attr_x10;
-pub mod attr_x30;
pub mod attribute;
pub mod entry;
pub mod enumerator;
+pub mod err;
pub mod mft;
pub(crate) mod utils;
diff --git a/src/mft.rs b/src/mft.rs
index 9569cc9..7ab6e66 100644
--- a/src/mft.rs
+++ b/src/mft.rs
@@ -4,12 +4,12 @@ use crate::err::{self, Result};
use log::debug;
use snafu::ResultExt;
-use std::fs::File;
+use std::fs::{self, File};
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;
use winstructs::ntfs::mft_reference::MftReference;
-pub struct MftHandler {
+pub struct MftParser {
file: BufReader<File>,
path_enumerator: PathEnumerator,
entry_size: u32,
@@ -17,22 +17,17 @@ pub struct MftHandler {
size: u64,
}
-impl MftHandler {
- pub fn from_path(filename: impl AsRef<Path>) -> Result<MftHandler> {
+impl MftParser {
+ pub fn from_path(filename: impl AsRef<Path>) -> Result<MftParser> {
let f = filename.as_ref();
- let mut mft_fh = File::open(f).context(err::FailedToOpenFile { path: f.to_owned() })?;
+ let mft_fh = File::open(f).context(err::FailedToOpenFile { path: f.to_owned() })?;
+ let size = fs::metadata(f)?.len();
- // TODO: remove this, and find a better way
- let size = match mft_fh.seek(SeekFrom::End(0)) {
- Err(e) => panic!("Error: {}", e),
- Ok(size) => size,
- };
+ let file = BufReader::with_capacity(4096, mft_fh);
- let filehandle = BufReader::with_capacity(4096, mft_fh);
-
- Ok(MftHandler {
- file: filehandle,
+ Ok(MftParser {
+ file,
path_enumerator: PathEnumerator::new(),
entry_size: 1024,
offset: 0,