use anyhow::Result;
use likely_stable::unlikely;
use mft::attribute::x10::StandardInfoAttr;
use mft::attribute::x30::{FileNameAttr, FileNamespace};
use mft::attribute::{MftAttributeContent, MftAttributeType};
use mft::MftEntry;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::Write;
use winstructs::ntfs::mft_reference::MftReference;
struct CompleteMftEntry {
base_entry: MftReference,
file_name_attribute: Option<FileNameAttr>,
standard_info_attribute: Option<StandardInfoAttr>,
full_path: RefCell<String>,
is_allocated: bool,
}
impl CompleteMftEntry {
pub fn from_base_entry(entry_reference: MftReference, entry: MftEntry) -> Self {
let mut c = Self {
base_entry: entry_reference,
file_name_attribute: None,
standard_info_attribute: None,
full_path: RefCell::new(String::new()),
is_allocated: entry.is_allocated(),
};
c.update_attributes(&entry);
return c;
}
pub fn from_nonbase_entry(_entry_ref: MftReference, entry: MftEntry) -> Self {
let mut c = Self {
base_entry: entry.header.base_reference,
file_name_attribute: None,
standard_info_attribute: None,
full_path: RefCell::new(String::new()),
is_allocated: false,
};
c.add_nonbase_entry(entry);
return c;
}
pub fn base_entry(&self) -> &MftReference {
&self.base_entry
}
pub fn set_base_entry(&mut self, entry_ref: MftReference, entry: MftEntry) {
assert_eq!(self.base_entry, entry_ref);
self.update_attributes(&entry);
self.is_allocated = entry.is_allocated();
}
fn add_nonbase_entry(&mut self, e: MftEntry) {
self.update_attributes(&e);
}
fn update_attributes(&mut self, entry: &MftEntry) {
let my_attribute_types = vec![
MftAttributeType::FileName,
MftAttributeType::StandardInformation,
];
for attr_result in entry
.iter_attributes_matching(Some(my_attribute_types))
.filter_map(Result::ok)
{
match attr_result.data {
MftAttributeContent::AttrX10(standard_info_attribute) => {
if self.standard_info_attribute.is_none() {
self.standard_info_attribute = Some(standard_info_attribute);
} else {
panic!("multiple standard information attributes found")
}
}
MftAttributeContent::AttrX30(file_name_attribute) => {
match self.file_name_attribute {
None => self.file_name_attribute = Some(file_name_attribute),
Some(ref mut name_attr) => match file_name_attribute.namespace {
FileNamespace::Win32AndDos => *name_attr = file_name_attribute,
FileNamespace::Win32 => {
if name_attr.namespace != FileNamespace::Win32AndDos {
*name_attr = file_name_attribute
}
}
FileNamespace::POSIX => {
if name_attr.namespace == FileNamespace::DOS {
*name_attr = file_name_attribute
}
}
FileNamespace::DOS => {}
},
}
}
_ => panic!("filter for iter_attributes_matching() isn't working"),
}
}
}
pub fn parent(&self) -> Option<MftReference> {
match self.file_name_attribute {
None => None,
Some(ref fn_attr) => Some(fn_attr.parent),
}
}
pub fn get_full_path(&self, mft: &PreprocessedMft) -> String {
if unlikely(self.full_path.borrow().is_empty()) {
if self.base_entry.entry == 5
{
*self.full_path.borrow_mut() = String::from("");
return self.full_path.borrow().clone();
}
match self.file_name_attribute() {
Some(name) => {
match self.parent() {
None => *self.full_path.borrow_mut() = name.name.to_string(),
Some(p) => {
assert_ne!(p, self.base_entry);
let parent_path = mft.get_full_path(&p);
let mut fp = self.full_path.borrow_mut();
*fp = parent_path;
fp.push('/');
fp.push_str(&name.name);
}
}
}
None => {
*self.full_path.borrow_mut() = format!(
"unnamed_{}_{}",
self.base_entry.entry, self.base_entry.sequence
);
}
}
}
self.full_path.borrow().to_string()
}
pub fn filesize(&self) -> u64 {
match self.file_name_attribute {
Some(ref fn_attr) => fn_attr.logical_size,
None => 0,
}
}
fn format(
&self,
display_name: &str,
atime: i64,
mtime: i64,
ctime: i64,
crtime: i64,
) -> String {
let mode = String::from("0");
let uid = String::from("0");
let gid = String::from("0");
let status = if self.is_allocated { "" } else { " (deleted)" };
let filesize = self.filesize();
format!(
"0|{}{}|{}|{}|{}|{}|{}|{}|{}|{}|{}\n",
display_name,
status,
&self.base_entry().entry.to_string(),
mode,
uid,
gid,
filesize,
atime,
mtime,
ctime,
crtime
)
}
pub fn format_fn(&self, mft: &PreprocessedMft) -> Option<String> {
match self.file_name_attribute {
Some(ref fn_attr) => {
let display_name = format!("{} ($FILENAME)", self.get_full_path(mft));
Some(self.format(
&display_name,
fn_attr.accessed.timestamp(),
fn_attr.mft_modified.timestamp(),
fn_attr.modified.timestamp(),
fn_attr.created.timestamp(),
))
}
None => {
None
}
}
}
pub fn format_si(&self, mft: &PreprocessedMft) -> String {
match self.standard_info_attribute {
Some(ref standard_info_attribute) => self.format(
&self.get_full_path(mft),
standard_info_attribute.accessed.timestamp(),
standard_info_attribute.mft_modified.timestamp(),
standard_info_attribute.modified.timestamp(),
standard_info_attribute.created.timestamp(),
),
None => panic!("missing standard information"),
}
}
pub fn file_name_attribute(&self) -> &Option<FileNameAttr> {
if self.file_name_attribute.is_none() {
if self.is_allocated {
#[cfg(debug_assertions)]
panic!(
"no $FILE_NAME attribute found for $MFT entry {}-{}",
self.base_entry().entry,
self.base_entry().sequence
);
#[cfg(not(debug_assertions))]
log::fatal!(
"no $FILE_NAME attribute found for $MFT entry {}-{}. This is fatal because this is not a deleted file",
self.base_entry().entry,
self.base_entry().sequence
);
} else {
log::warn!(
"no $FILE_NAME attribute found for $MFT entry {}-{}, but this is a deleted file",
self.base_entry().entry,
self.base_entry().sequence
);
}
}
return &self.file_name_attribute;
}
}
pub struct PreprocessedMft {
complete_entries: HashMap<MftReference, CompleteMftEntry>,
}
impl PreprocessedMft {
pub fn new() -> Self {
Self {
complete_entries: HashMap::new(),
}
}
pub fn add_entry(&mut self, entry: MftEntry) {
let reference = MftReference::new(entry.header.record_number, entry.header.sequence);
if PreprocessedMft::is_base_entry(&entry) {
match self.complete_entries.get_mut(&reference) {
Some(e) => e.set_base_entry(reference, entry),
None => {
let ce = CompleteMftEntry::from_base_entry(reference, entry);
let _ = self.complete_entries.insert(reference, ce);
}
}
} else
{
if entry.is_allocated() {
let base_reference = entry.header.base_reference;
match self.complete_entries.get_mut(&base_reference) {
Some(e) => {
e.add_nonbase_entry(entry);
}
None => {
let ce = CompleteMftEntry::from_nonbase_entry(reference, entry);
let _ = self.complete_entries.insert(base_reference, ce);
}
}
}
}
}
pub fn is_base_entry(entry: &MftEntry) -> bool {
entry.header.base_reference.entry == 0 && entry.header.base_reference.sequence == 0
}
pub fn get_full_path(&self, reference: &MftReference) -> String {
match self.complete_entries.get(&reference) {
None => format!("deleted_parent_{}_{}", reference.entry, reference.sequence),
Some(entry) => entry.get_full_path(self),
}
}
pub fn print_entries(&self) {
let stdout = std::io::stdout();
let mut stdout_lock = stdout.lock();
for entry in self.complete_entries.values() {
stdout_lock
.write_all(entry.format_si(self).as_bytes())
.unwrap();
if let Some(fn_info) = entry.format_fn(self) {
stdout_lock.write_all(fn_info.as_bytes()).unwrap();
}
}
}
}