use crate::err::{self, Result};
use crate::impl_serialize_for_bitflags;
use log::trace;
use snafu::{ensure, ResultExt};
use winstructs::ntfs::mft_reference::MftReference;
use byteorder::{LittleEndian, ReadBytesExt};
use bitflags::bitflags;
use serde::ser::{self, SerializeStruct, Serializer};
use serde::Serialize;
use crate::attribute::header::{MftAttributeHeader, ResidentialHeader};
use crate::attribute::x30::{FileNameAttr, FileNamespace};
use crate::attribute::{MftAttribute, MftAttributeContent, MftAttributeType};
use std::io::Read;
use std::io::SeekFrom;
use std::io::{Cursor, Seek};
const SEQUENCE_NUMBER_STRIDE: usize = 512;
#[derive(Debug, Clone)]
pub struct MftEntry {
pub header: EntryHeader,
pub data: Vec<u8>,
}
impl ser::Serialize for MftEntry {
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Color", 2)?;
let attributes: Vec<MftAttribute> = self.iter_attributes().filter_map(Result::ok).collect();
state.serialize_field("header", &self.header)?;
state.serialize_field("attributes", &attributes)?;
state.end()
}
}
#[derive(Serialize, Debug, Clone)]
pub struct EntryHeader {
pub signature: [u8; 4],
pub usa_offset: u16,
pub usa_size: u16,
pub metadata_transaction_journal: u64,
pub sequence: u16,
pub hard_link_count: u16,
pub first_attribute_record_offset: u16,
pub flags: EntryFlags,
pub used_entry_size: u32,
pub total_entry_size: u32,
pub base_reference: MftReference,
pub first_attribute_id: u16,
pub record_number: u64,
}
bitflags! {
pub struct EntryFlags: u16 {
const ALLOCATED = 0x01;
const INDEX_PRESENT = 0x02;
const UNKNOWN_1 = 0x04;
const UNKNOWN_2 = 0x08;
}
}
impl_serialize_for_bitflags! {EntryFlags}
impl EntryHeader {
pub fn from_reader<R: Read>(reader: &mut R, entry_id: u64) -> Result<EntryHeader> {
let mut signature = [0; 4];
reader.read_exact(&mut signature)?;
ensure!(
&signature != b"\x00\x00\x00\x00",
err::InvalidEntrySignature {
bad_sig: signature.to_vec()
}
);
let usa_offset = reader.read_u16::<LittleEndian>()?;
let usa_size = reader.read_u16::<LittleEndian>()?;
let logfile_sequence_number = reader.read_u64::<LittleEndian>()?;
let sequence = reader.read_u16::<LittleEndian>()?;
let hard_link_count = reader.read_u16::<LittleEndian>()?;
let first_attribute_offset = reader.read_u16::<LittleEndian>()?;
let flags = EntryFlags::from_bits_truncate(reader.read_u16::<LittleEndian>()?);
let entry_size_real = reader.read_u32::<LittleEndian>()?;
let entry_size_allocated = reader.read_u32::<LittleEndian>()?;
let base_reference =
MftReference::from_reader(reader).context(err::FailedToReadMftReference)?;
let first_attribute_id = reader.read_u16::<LittleEndian>()?;
Ok(EntryHeader {
signature,
usa_offset,
usa_size,
metadata_transaction_journal: logfile_sequence_number,
sequence,
hard_link_count,
first_attribute_record_offset: first_attribute_offset,
flags,
used_entry_size: entry_size_real,
total_entry_size: entry_size_allocated,
base_reference,
first_attribute_id,
record_number: entry_id,
})
}
}
impl MftEntry {
pub fn from_buffer(mut buffer: Vec<u8>, entry_number: u64) -> Result<MftEntry> {
let mut cursor = Cursor::new(&buffer);
let entry_header = EntryHeader::from_reader(&mut cursor, entry_number)?;
trace!("Number of sectors: {:#?}", entry_header);
Self::apply_fixups(&entry_header, &mut buffer)?;
Ok(MftEntry {
header: entry_header,
data: buffer,
})
}
pub fn find_best_name_attribute(&self) -> Option<FileNameAttr> {
let file_name_attributes: Vec<FileNameAttr> = self
.iter_attributes()
.filter_map(Result::ok)
.filter_map(|a| a.data.into_file_name())
.collect();
let win32_filename = file_name_attributes
.iter()
.find(|a| [FileNamespace::Win32, FileNamespace::Win32AndDos].contains(&a.namespace));
match win32_filename {
Some(filename) => Some(filename.clone()),
None => {
match file_name_attributes.iter().next() {
Some(filename) => Some(filename.clone()),
None => None,
}
}
}
}
#[must_use]
fn apply_fixups(header: &EntryHeader, buffer: &mut [u8]) -> Result<()> {
let number_of_fixups = u32::from(header.usa_size - 1);
trace!("Number of fixups: {}", number_of_fixups);
let fixups_start_offset = header.usa_offset as usize;
let fixups_end_offset = fixups_start_offset + (header.usa_size * 2) as usize;
let fixups = buffer[fixups_start_offset..fixups_end_offset].to_vec();
let mut fixups = fixups.chunks(2);
let update_sequence = fixups.next().unwrap_or(&[0, 0]);
for (stride_number, fixup_bytes) in (0_usize..number_of_fixups as usize).zip(fixups) {
let sector_start_offset = stride_number * SEQUENCE_NUMBER_STRIDE;
let end_of_sector_bytes_end_offset = sector_start_offset + SEQUENCE_NUMBER_STRIDE;
let end_of_sector_bytes_start_offset = end_of_sector_bytes_end_offset - 2;
let end_of_sector_bytes =
&mut buffer[end_of_sector_bytes_start_offset..end_of_sector_bytes_end_offset];
ensure!(
end_of_sector_bytes == update_sequence,
err::FailedToApplyFixup {
stride_number,
end_of_sector_bytes: end_of_sector_bytes.to_vec(),
fixup_bytes: fixup_bytes.to_vec()
}
);
end_of_sector_bytes.copy_from_slice(&fixup_bytes);
}
Ok(())
}
pub fn is_allocated(&self) -> bool {
self.header.flags.bits() & 0x01 != 0
}
pub fn is_dir(&self) -> bool {
self.header.flags.bits() & 0x02 != 0
}
pub fn iter_attributes(&self) -> impl Iterator<Item = Result<MftAttribute>> + '_ {
self.iter_attributes_matching(None)
}
pub fn iter_attributes_matching(
&self,
types: Option<Vec<MftAttributeType>>,
) -> impl Iterator<Item = Result<MftAttribute>> + '_ {
let mut cursor = Cursor::new(&self.data);
let mut offset = u64::from(self.header.first_attribute_record_offset);
let mut exhausted = false;
std::iter::from_fn(move || {
loop {
if exhausted {
return None;
}
match cursor.seek(SeekFrom::Start(offset)).context(err::IoError) {
Ok(_) => {}
Err(e) => {
exhausted = true;
return Some(Err(e.into()));
}
};
let header = MftAttributeHeader::from_stream(&mut cursor);
let header = match header {
Ok(h) => h,
Err(e) => {
exhausted = true;
return Some(Err(e));
}
};
let header = match header {
Some(attribute_header) => attribute_header,
None => return None,
};
offset += u64::from(header.record_length);
if let Some(filter) = &types {
if !filter.contains(&header.type_code) {
continue;
}
}
let attribute_content = match header.residential_header {
ResidentialHeader::Resident(ref resident) => {
match MftAttributeContent::from_stream_resident(
&mut cursor,
&header,
resident,
) {
Ok(content) => content,
Err(e) => return Some(Err(e)),
}
}
ResidentialHeader::NonResident(_) => MftAttributeContent::None,
};
return Some(Ok(MftAttribute {
header,
data: attribute_content,
}));
}
})
}
}
#[cfg(test)]
mod tests {
use super::EntryHeader;
use std::io::Cursor;
#[test]
fn mft_header_test_01() {
let header_buffer: &[u8] = &[
0x46, 0x49, 0x4C, 0x45, 0x30, 0x00, 0x03, 0x00, 0xCC, 0xB3, 0x7D, 0x84, 0x0C, 0x00,
0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x38, 0x00, 0x05, 0x00, 0x48, 0x03, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
0x00, 0x00, 0xD5, 0x95, 0x00, 0x00, 0x53, 0x57, 0x81, 0x37, 0x00, 0x00, 0x00, 0x00,
];
let entry_header =
EntryHeader::from_reader(&mut Cursor::new(header_buffer), 38357).unwrap();
assert_eq!(&entry_header.signature, b"FILE");
assert_eq!(entry_header.usa_offset, 48);
assert_eq!(entry_header.usa_size, 3);
assert_eq!(entry_header.metadata_transaction_journal, 53_762_438_092);
assert_eq!(entry_header.sequence, 5);
assert_eq!(entry_header.hard_link_count, 1);
assert_eq!(entry_header.first_attribute_record_offset, 56);
assert_eq!(entry_header.flags.bits(), 5);
assert_eq!(entry_header.used_entry_size, 840);
assert_eq!(entry_header.total_entry_size, 1024);
assert_eq!(entry_header.base_reference.entry, 0);
assert_eq!(entry_header.first_attribute_id, 6);
assert_eq!(entry_header.record_number, 38357);
}
}