use crate::mft::mft_record_attribute_iter::MftRecordAttributeIter;
use crate::mft::mft_record_flags::MftRecordFlags;
use crate::mft::mft_record_location::MftRecordLocationOnDisk;
use crate::mft::mft_record_number::MftRecordNumber;
use crate::mft::mft_record_size::MftRecordSize;
use bytes::Bytes;
use eyre::bail;
use std::ops::Deref;
use teamy_windows::storage::HandleReadExt;
use tracing::instrument;
use uom::si::information::byte;
pub struct MftRecord {
data: Bytes,
}
impl Deref for MftRecord {
type Target = Bytes;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl std::fmt::Debug for MftRecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MftRecord")
.field("signature", &self.get_signature())
.field("record_number", &self.get_record_number())
.field("used_size", &self.get_used_size())
.field("allocated_size", &self.get_allocated_size())
.finish()
}
}
impl MftRecord {
#[inline]
pub fn from_bytes_unchecked(bytes: Bytes) -> Self {
Self { data: bytes }
}
const OFFSET_FOR_SIGNATURE: usize = 0x00;
const OFFSET_FOR_UPDATE_SEQUENCE_ARRAY_OFFSET: usize = 0x04; const OFFSET_FOR_UPDATE_SEQUENCE_ARRAY_SIZE: usize = 0x06; const OFFSET_FOR_LOGFILE_SEQUENCE_NUMBER: usize = 0x08; const OFFSET_FOR_SEQUENCE: usize = 0x10; const OFFSET_FOR_HARDLINKS: usize = 0x12; const OFFSET_FOR_FIRST_ATTR: usize = 0x14; const OFFSET_FOR_FLAGS: usize = 0x16; const OFFSET_FOR_USED_SIZE: usize = 0x18; const OFFSET_FOR_ALLOC_SIZE: usize = 0x1C; const OFFSET_FOR_BASE_REF: usize = 0x20; const OFFSET_FOR_NEXT_ATTR_ID: usize = 0x28; const OFFSET_FOR_RECORD_NUMBER: usize = 0x2C;
#[instrument(skip_all)]
pub fn try_from_handle(
drive_handle: &impl HandleReadExt,
mft_record_location: MftRecordLocationOnDisk,
mft_record_size: MftRecordSize,
) -> eyre::Result<Self> {
let mut data = vec![0u8; mft_record_size.get::<byte>()];
let offset_usize = mft_record_location.get::<byte>();
let offset_i64 = i64::try_from(offset_usize)
.map_err(|e| eyre::eyre!("MFT record location doesn't fit in i64: {e}"))?;
drive_handle.try_read_exact(offset_i64, data.as_mut_slice())?;
if data.len() < 4 || &data[0..4] != b"FILE" {
bail!(
"Invalid MFT record signature: expected 'FILE', got {:?}",
String::from_utf8_lossy(&data[0..4])
);
}
Ok(Self {
data: Bytes::from(data),
})
}
pub fn get_signature(&self) -> &[u8; 4] {
unsafe {
&*self
.data
.as_ptr()
.add(Self::OFFSET_FOR_SIGNATURE)
.cast::<[u8; 4]>()
}
}
#[inline]
fn read_u16(&self, offset: usize) -> u16 {
unsafe {
u16::from_le(std::ptr::read_unaligned(
self.data.as_ptr().add(offset).cast::<u16>(),
))
}
}
#[inline]
fn read_u32(&self, offset: usize) -> u32 {
unsafe {
u32::from_le(std::ptr::read_unaligned(
self.data.as_ptr().add(offset).cast::<u32>(),
))
}
}
#[inline]
fn read_u64(&self, offset: usize) -> u64 {
unsafe {
u64::from_le(std::ptr::read_unaligned(
self.data.as_ptr().add(offset).cast::<u64>(),
))
}
}
#[inline]
pub fn get_update_sequence_array_offset(&self) -> u16 {
self.read_u16(Self::OFFSET_FOR_UPDATE_SEQUENCE_ARRAY_OFFSET)
}
#[inline]
pub fn get_update_sequence_array_size_words(&self) -> u16 {
self.read_u16(Self::OFFSET_FOR_UPDATE_SEQUENCE_ARRAY_SIZE)
}
#[inline]
pub fn get_dollar_log_file(&self) -> u64 {
self.read_u64(Self::OFFSET_FOR_LOGFILE_SEQUENCE_NUMBER)
}
#[inline]
pub fn get_sequence_number(&self) -> u16 {
self.read_u16(Self::OFFSET_FOR_SEQUENCE)
}
#[inline]
pub fn get_hard_link_count(&self) -> u16 {
self.read_u16(Self::OFFSET_FOR_HARDLINKS)
}
#[inline]
pub fn get_first_attribute_offset(&self) -> u16 {
self.read_u16(Self::OFFSET_FOR_FIRST_ATTR)
}
#[inline]
pub fn flags(&self) -> MftRecordFlags {
MftRecordFlags::from(self.read_u16(Self::OFFSET_FOR_FLAGS))
}
#[inline]
#[must_use]
pub fn flags_is_in_use(flags: MftRecordFlags) -> bool {
flags.is_in_use()
}
#[inline]
#[must_use]
pub fn flags_is_deleted(flags: MftRecordFlags) -> bool {
flags.is_deleted()
}
#[inline]
#[must_use]
pub fn is_in_use(&self) -> bool {
Self::flags_is_in_use(self.flags())
}
#[inline]
#[must_use]
pub fn is_deleted(&self) -> bool {
Self::flags_is_deleted(self.flags())
}
#[inline]
pub fn get_used_size(&self) -> u32 {
self.read_u32(Self::OFFSET_FOR_USED_SIZE)
}
#[inline]
pub fn get_allocated_size(&self) -> u32 {
self.read_u32(Self::OFFSET_FOR_ALLOC_SIZE)
}
#[inline]
pub fn get_base_reference_raw(&self) -> u64 {
self.read_u64(Self::OFFSET_FOR_BASE_REF)
}
#[inline]
pub fn get_next_attribute_id(&self) -> u16 {
self.read_u16(Self::OFFSET_FOR_NEXT_ATTR_ID)
}
#[inline]
pub fn get_record_number(&self) -> MftRecordNumber {
self.read_u32(Self::OFFSET_FOR_RECORD_NUMBER).into()
}
pub fn iter_attributes(&self) -> MftRecordAttributeIter<'_> {
MftRecordAttributeIter::new(self)
}
}