1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
use crate::attribute::header::ResidentialHeader;
use crate::attribute::{FileAttributeFlags, MftAttributeType};
use crate::entry::EntryFlags;
use crate::{MftAttribute, MftEntry, MftParser, ReadSeek};
use serde::Serialize;
use chrono::{DateTime, Utc};
use std::path::PathBuf;
/// Used for CSV output
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct FlatMftEntryWithName {
pub signature: String,
pub entry_id: u64,
pub sequence: u16,
pub base_entry_id: u64,
pub base_entry_sequence: u16,
pub hard_link_count: u16,
pub flags: EntryFlags,
/// The size of the file, in bytes.
pub used_entry_size: u32,
pub total_entry_size: u32,
/// The size of the file, if available, from the X80 attribute.
/// Will be 0 if no $DATA attribute is found.
pub file_size: u64,
/// Indicates whether the record is a directory.
pub is_a_directory: bool,
/// Indicates whether the record has the `ALLOCATED` bit turned off.
pub is_deleted: bool,
/// Indicates whether the record has alternate data streams.
pub has_alternate_data_streams: bool,
/// All of these fields are present for entries that have an 0x10 attribute.
pub standard_info_flags: Option<FileAttributeFlags>,
pub standard_info_last_modified: Option<DateTime<Utc>>,
pub standard_info_last_access: Option<DateTime<Utc>>,
pub standard_info_created: Option<DateTime<Utc>>,
/// All of these fields are present for entries that have an 0x30 attribute.
pub file_name_flags: Option<FileAttributeFlags>,
pub file_name_last_modified: Option<DateTime<Utc>>,
pub file_name_last_access: Option<DateTime<Utc>>,
pub file_name_created: Option<DateTime<Utc>>,
pub full_path: PathBuf,
}
impl FlatMftEntryWithName {
pub fn from_entry(
entry: &MftEntry,
parser: &mut MftParser<impl ReadSeek>,
) -> FlatMftEntryWithName {
let entry_attributes: Vec<MftAttribute> = entry
.iter_attributes_matching(Some(vec![
MftAttributeType::FileName,
MftAttributeType::StandardInformation,
MftAttributeType::DATA,
]))
.filter_map(Result::ok)
.collect();
let file_name = entry_attributes
.iter()
.find(|a| a.header.type_code == MftAttributeType::FileName)
.and_then(|a| a.data.clone().into_file_name());
let standard_info = entry_attributes
.iter()
.find(|a| a.header.type_code == MftAttributeType::StandardInformation)
.and_then(|a| a.data.clone().into_standard_info());
let data_attr = entry_attributes
.iter()
.find(|a| a.header.type_code == MftAttributeType::DATA);
let file_size = match data_attr {
Some(attr) => match &attr.header.residential_header {
ResidentialHeader::Resident(r) => u64::from(r.data_size),
ResidentialHeader::NonResident(nr) => nr.file_size,
},
_ => 0,
};
let has_ads = entry_attributes
.iter()
.any(|a| {
a.header.type_code == MftAttributeType::DATA && a.header.name.len() > 0
});
FlatMftEntryWithName {
entry_id: entry.header.record_number,
signature: String::from_utf8(entry.header.signature.to_ascii_uppercase())
.expect("It should be either FILE or BAAD (valid utf-8)"),
sequence: entry.header.sequence,
hard_link_count: entry.header.hard_link_count,
flags: entry.header.flags,
used_entry_size: entry.header.used_entry_size,
total_entry_size: entry.header.total_entry_size,
base_entry_id: entry.header.base_reference.entry,
base_entry_sequence: entry.header.base_reference.sequence,
is_a_directory: entry.is_dir(),
is_deleted: !entry.header.flags.contains(EntryFlags::ALLOCATED),
has_alternate_data_streams: has_ads,
standard_info_flags: standard_info.as_ref().and_then(|i| Some(i.file_flags)),
standard_info_last_modified: standard_info.as_ref().and_then(|i| Some(i.modified)),
standard_info_last_access: standard_info.as_ref().and_then(|i| Some(i.accessed)),
standard_info_created: standard_info.as_ref().and_then(|i| Some(i.created)),
file_name_flags: file_name.as_ref().and_then(|i| Some(i.flags)),
file_name_last_modified: file_name.as_ref().and_then(|i| Some(i.modified)),
file_name_last_access: file_name.as_ref().and_then(|i| Some(i.accessed)),
file_name_created: file_name.as_ref().and_then(|i| Some(i.created)),
file_size,
full_path: parser
.get_full_path_for_entry(entry)
.expect("I/O Err")
.unwrap_or_default(),
}
}
}
|