use crate::intern::{PreprocessedMft, ParentFolderName};
use crate::{FilenameInfo, TimestampTuple};
use anyhow::Result;
use likely_stable::unlikely;
use mft::attribute::{MftAttributeContent, MftAttributeType};
use mft::MftEntry;
use std::cell::RefCell;
use winstructs::ntfs::mft_reference::MftReference;
use usnjrnl::{CommonUsnRecord, UsnRecordData};
pub struct CompleteMftEntry {
base_entry: MftReference,
file_name_attribute: Option<FilenameInfo>,
standard_info_timestamps: Option<TimestampTuple>,
full_path: RefCell<String>,
is_allocated: bool,
deletion_status: RefCell<&'static str>,
usnjrnl_records: Vec<CommonUsnRecord>,
}
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_timestamps: None,
full_path: RefCell::new(String::new()),
is_allocated: entry.is_allocated(),
usnjrnl_records: Vec::new(),
deletion_status: RefCell::new(if entry.is_allocated() {""} else {" (deleted)"} )
};
c.update_attributes(
&entry,
vec![
MftAttributeType::StandardInformation,
MftAttributeType::FileName,
],
);
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_timestamps: None,
full_path: RefCell::new(String::new()),
is_allocated: false,
usnjrnl_records: Vec::new(),
deletion_status: RefCell::new(" (deleted)")
};
c.add_nonbase_entry(entry);
c
}
pub fn from_usnjrnl_records(_entry_ref: MftReference, records: Vec<CommonUsnRecord>) -> Self {
let mut records = records;
records.sort_by(
|a, b| a.data.timestamp().partial_cmp(b.data.timestamp()).unwrap());
Self {
base_entry: _entry_ref,
file_name_attribute: None,
standard_info_timestamps: None,
full_path: RefCell::new(String::new()),
is_allocated: false,
usnjrnl_records: records,
deletion_status: RefCell::new(" (deleted)")
}
}
pub fn base_entry(&self) -> &MftReference {
&self.base_entry
}
pub fn is_allocated(&self) -> bool {
self.is_allocated
}
pub fn set_base_entry(&mut self, entry_ref: MftReference, entry: MftEntry) {
assert_eq!(self.base_entry, entry_ref);
self.update_attributes(
&entry,
vec![
MftAttributeType::StandardInformation,
MftAttributeType::FileName,
],
);
self.is_allocated = entry.is_allocated();
}
pub fn add_nonbase_entry(&mut self, e: MftEntry) {
self.update_attributes(&e, vec![MftAttributeType::FileName]);
}
pub fn add_usnjrnl_records(&mut self, records: Vec<CommonUsnRecord>) {let mut records = records;
records.sort_by(
|a, b| a.data.timestamp().partial_cmp(b.data.timestamp()).unwrap());
if self.usnjrnl_records.is_empty() {
self.usnjrnl_records = records;
} else {
self.usnjrnl_records.extend(records);
}
}
fn update_attributes(&mut self, entry: &MftEntry, attribute_types: Vec<MftAttributeType>) {
if self.standard_info_timestamps.is_some() {
if let Some(filename_info) = &self.file_name_attribute {
if filename_info.is_final() {
return;
}
}
}
for attr_result in entry
.iter_attributes_matching(Some(attribute_types))
.filter_map(Result::ok)
{
match attr_result.data {
MftAttributeContent::AttrX10(standard_info_attribute) => {
if self.standard_info_timestamps.is_none() {
self.standard_info_timestamps =
Some(TimestampTuple::from(&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(FilenameInfo::from(&file_name_attribute))
}
Some(ref mut name_attr) => name_attr.update(&file_name_attribute),
}
if let Some(file_name_attribute) = &self.file_name_attribute {
if file_name_attribute.is_final() {
return;
}
}
}
_ => 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()),
}
}
fn set_folder_name(&self, mft: &PreprocessedMft, parent: &MftReference, my_name: &str) {
assert_ne!(parent, &self.base_entry);
let mut fp = self.full_path.borrow_mut();
let parent_path = match mft.get_full_path(parent) {
ParentFolderName::MatchingSequenceNumber(s) => s,
ParentFolderName::IncrementedSequenceNumber(s) => {
assert!(! self.is_allocated());
*(self.deletion_status.borrow_mut()) = " (deleted)";
s
}
ParentFolderName::NoMatchFound(s) => s
};
*fp = parent_path;
if ! &fp.ends_with('/') {
fp.push('/');
}
fp.push_str(my_name);
}
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.filename_info() {
Some(name) => {
match self.parent() {
None => *self.full_path.borrow_mut() = name.filename().clone(),
Some(p) => self.set_folder_name(mft, p, name.filename())
}
}
None => {
let my_name = match self.filename_from_usnjrnl() {
Some(name) => name.to_owned(),
None => format!(
"unnamed_{}_{}",
self.base_entry.entry, self.base_entry.sequence
)
};
match self.parent_from_usnjrnl() {
None => *self.full_path.borrow_mut() = my_name,
Some(p) => self.set_folder_name(mft, &p, &my_name)
};
}
}
}
self.full_path.borrow().to_string()
}
fn filename_from_usnjrnl(&self) -> Option<&str> {
self.usnjrnl_records.last().map(|r| r.data.filename())
}
fn parent_from_usnjrnl(&self) -> Option<MftReference> {
self.usnjrnl_records.last().and_then(|r| match &r.data {
UsnRecordData::V2(data) => Some(data.ParentFileReferenceNumber),
#[allow(unreachable_patterns)]
_ => None
}
)
}
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 = self.deletion_status.borrow();
let filesize = self.filesize();
format!(
"0|{}{}|{}|{}|{}|{}|{}|{}|{}|{}|{}\n",
display_name,
status,
&self.base_entry().entry.to_string(),
mode,
uid,
gid,
filesize,
atime,
mtime,
ctime,
crtime
)
}
fn format_fn(&self, mft: &PreprocessedMft) -> Option<String> {
match self.file_name_attribute {
Some(ref fn_attr) => {
let display_name = format!("{} ($FILE_NAME)", self.get_full_path(mft));
Some(self.format(
&display_name,
fn_attr.timestamps().accessed(),
fn_attr.timestamps().mft_modified(),
fn_attr.timestamps().modified(),
fn_attr.timestamps().created(),
))
}
None => None,
}
}
fn format_si(&self, mft: &PreprocessedMft) -> Option<String> {
self.standard_info_timestamps.as_ref().map(|si| self.format(
&self.get_full_path(mft),
si.accessed(),
si.mft_modified(),
si.modified(),
si.created()))
}
fn mft_filename(&self) -> Option<&String> {
match &self.file_name_attribute {
Some(fni) => Some(fni.filename()),
None => None
}
}
fn format_usnjrnl(&self, mft: &PreprocessedMft, record: &CommonUsnRecord, usnjrnl_longflags: bool) -> String {
match &record.data {
UsnRecordData::V2(data) => {
let filename_info = match self.mft_filename() {
None => format!(" filename={}", data.FileName),
Some(f) => if f != &data.FileName {
format!(" filename={}", data.FileName)
} else {
"".to_owned()
}
};
let reason_info = if usnjrnl_longflags {
format!(" reason={:+}", data.Reason)
} else {
format!(" reason={}", data.Reason)
};
let parent_name = match mft.get_full_path(&data.ParentFileReferenceNumber) {
ParentFolderName::MatchingSequenceNumber(s) => s,
ParentFolderName::IncrementedSequenceNumber(s) => s,
ParentFolderName::NoMatchFound(s) => s,
};
let parent_info = match &self.file_name_attribute {
Some(fni) => {
if fni.parent() != &data.ParentFileReferenceNumber {
format!(" parent='{}'", parent_name)
} else {
"".to_owned()
}
}
None => format!(" parent='{}'", parent_name)
};
let display_name = format!("{} ($UsnJrnl{}{}{})",
self.get_full_path(mft),
filename_info,
parent_info,
reason_info);
let timestamp = data.TimeStamp.timestamp();
self.format(&display_name, timestamp, timestamp, timestamp, timestamp)
}
}
}
pub fn filename_info(&self) -> &Option<FilenameInfo> {
if self.file_name_attribute.is_none() && 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::error!(
"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
);
}
&self.file_name_attribute
}
pub fn bodyfile_lines(&self, mft: &PreprocessedMft, usnjrnl_longflags: bool) -> BodyfileLines {
BodyfileLines {
standard_info: self.format_si(mft),
filename_info: self.format_fn(mft),
usnjrnl_records: self.usnjrnl_records
.iter()
.map(|r| self.format_usnjrnl(mft, r, usnjrnl_longflags))
.collect()
}
}
pub fn bodyfile_lines_count(&self) -> usize {
(match &self.standard_info_timestamps {
Some(_) => 1,
None => 0,
}
+
match &self.file_name_attribute {
Some(_) => 1,
None => 0,
}
+
self.usnjrnl_records.len())
}
}
pub struct BodyfileLines {
standard_info: Option<String>,
filename_info: Option<String>,
usnjrnl_records: Vec<String>
}
impl Iterator for BodyfileLines {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
if self.standard_info.is_some() {
return self.standard_info.take();
}
if self.filename_info.is_some() {
return self.filename_info.take();
}
self.usnjrnl_records.pop()
}
}