1use crate::intern::PreprocessedMft;
2use crate::{FilenameInfo, TimestampTuple};
3use anyhow::Result;
4use bodyfile::Bodyfile3Line;
5use likely_stable::unlikely;
6use mft::attribute::{MftAttributeContent, MftAttributeType};
7use mft::MftEntry;
8use num::ToPrimitive;
9use std::cell::RefCell;
10use std::cmp;
11use usnjrnl::{CommonUsnRecord, UsnRecordData};
12use winstructs::ntfs::mft_reference::MftReference;
13
14pub struct CompleteMftEntry {
36 base_entry: MftReference,
37 file_name_attribute: Option<FilenameInfo>,
38 standard_info_timestamps: Option<TimestampTuple>,
39 full_path: RefCell<String>,
40 is_allocated: bool,
41 deletion_status: RefCell<&'static str>,
42 usnjrnl_records: Vec<CommonUsnRecord>,
43 streams: Vec<StreamAttribute>,
44 is_directory: bool,
45}
46
47pub struct StreamAttribute {
48 attribute_type: MftAttributeType,
49 name: Option<String>,
50 instance: u16,
51}
52
53impl CompleteMftEntry {
54 pub fn from_base_entry(entry_reference: MftReference, entry: MftEntry) -> Self {
55 let mut c = Self {
56 base_entry: entry_reference,
57 file_name_attribute: None,
58 standard_info_timestamps: None,
59 full_path: RefCell::new(String::new()),
60 is_allocated: entry.is_allocated(),
61 usnjrnl_records: Vec::new(),
62 deletion_status: RefCell::new(if entry.is_allocated() {
63 ""
64 } else {
65 " (deleted)"
66 }),
67 streams: Vec::new(),
68 is_directory: entry.is_dir(),
69 };
70 c.update_attributes(&entry);
71 c
72 }
73
74 pub fn from_nonbase_entry(_entry_ref: MftReference, entry: MftEntry) -> Self {
75 let mut c = Self {
76 base_entry: entry.header.base_reference,
77 file_name_attribute: None,
78 standard_info_timestamps: None,
79 full_path: RefCell::new(String::new()),
80 is_allocated: false,
81 usnjrnl_records: Vec::new(),
82 deletion_status: RefCell::new(" (deleted)"),
83 streams: Vec::new(),
84 is_directory: false,
85 };
86 c.add_nonbase_entry(entry);
87 c
88 }
89
90 pub fn from_usnjrnl_records(_entry_ref: MftReference, records: Vec<CommonUsnRecord>) -> Self {
91 let mut records = records;
92 records.sort_by(|a, b| a.data.timestamp().partial_cmp(b.data.timestamp()).unwrap());
93
94 Self {
95 base_entry: _entry_ref,
96 file_name_attribute: None,
97 standard_info_timestamps: None,
98 full_path: RefCell::new(String::new()),
99 is_allocated: false,
100 usnjrnl_records: records,
101 deletion_status: RefCell::new(" (deleted)"),
102 streams: Vec::new(),
103 is_directory: false,
104 }
105 }
106
107 pub fn base_entry(&self) -> &MftReference {
108 &self.base_entry
109 }
110
111 pub fn is_allocated(&self) -> bool {
112 self.is_allocated
113 }
114
115 pub fn set_base_entry(&mut self, entry_ref: MftReference, entry: MftEntry) {
116 assert_eq!(self.base_entry, entry_ref);
117
118 self.update_attributes(&entry);
119 self.is_allocated = entry.is_allocated();
120 self.is_directory = entry.is_dir();
121 }
122
123 pub fn add_nonbase_entry(&mut self, e: MftEntry) {
124 self.update_attributes(&e);
125 }
126
127 pub fn add_usnjrnl_records(&mut self, records: Vec<CommonUsnRecord>) {
128 let mut records = records;
129 records.sort_by(|a, b| a.data.timestamp().partial_cmp(b.data.timestamp()).unwrap());
130
131 if self.usnjrnl_records.is_empty() {
132 self.usnjrnl_records = records;
133 } else {
134 self.usnjrnl_records.extend(records);
135 }
136 }
137
138 fn update_attributes(&mut self, entry: &MftEntry) {
139 for attr_result in entry
140 .iter_attributes_matching(Some(vec![
141 MftAttributeType::StandardInformation,
142 MftAttributeType::FileName,
143 MftAttributeType::DATA,
144 MftAttributeType::IndexRoot,
145 ]))
146 .filter_map(Result::ok)
147 {
148 if attr_result.header.type_code == MftAttributeType::IndexRoot
151 || attr_result.header.type_code == MftAttributeType::DATA
152 {
153 self.streams.push(StreamAttribute {
154 attribute_type: attr_result.header.type_code,
155 name: attr_result
156 .header
157 .name_offset
158 .and(Some(attr_result.header.name)),
159 instance: attr_result.header.instance,
160 });
161 continue;
162 }
163
164 match attr_result.data {
165 MftAttributeContent::AttrX10(standard_info_attribute) => {
166 if self.standard_info_timestamps.is_none() {
167 self.standard_info_timestamps =
168 Some(TimestampTuple::from(&standard_info_attribute));
169 } else {
170 panic!("multiple standard information attributes found")
171 }
172 }
173
174 MftAttributeContent::AttrX30(file_name_attribute) => {
175 match self.file_name_attribute {
176 None => {
177 self.file_name_attribute = Some(FilenameInfo::from(
178 &file_name_attribute,
179 &attr_result.header,
180 ))
181 }
182 Some(ref mut name_attr) => {
183 name_attr.update(&file_name_attribute, &attr_result.header)
184 }
185 }
186 }
187 _ => panic!("filter for iter_attributes_matching() isn't working"),
188 }
189 }
190 }
191
192 pub fn parent(&self) -> Option<&MftReference> {
193 match self.file_name_attribute {
194 None => None,
195 Some(ref fn_attr) => Some(fn_attr.parent()),
196 }
197 }
198
199 fn set_folder_name(&self, mft: &PreprocessedMft, parent: &MftReference, my_name: &str) {
200 assert_ne!(parent, &self.base_entry);
201 let mut fp = self.full_path.borrow_mut();
202
203 let parent_info = mft.get_full_path(parent);
204
205 *fp = parent_info.full_path;
206 if !&fp.ends_with('/') {
207 fp.push('/');
208 }
209 fp.push_str(my_name);
210 }
211
212 pub fn get_full_path(&self, mft: &PreprocessedMft) -> String {
213 if unlikely(self.full_path.borrow().is_empty()) {
214 if self.base_entry.entry == 5
215 {
217 *self.full_path.borrow_mut() = String::from("/");
218 return self.full_path.borrow().clone();
219 }
220
221 match self.filename_info() {
222 Some(name) => match self.parent() {
223 None => *self.full_path.borrow_mut() = name.filename().clone(),
224 Some(p) => self.set_folder_name(mft, p, name.filename()),
225 },
226 None => {
227 let my_name = match self.filename_from_usnjrnl() {
228 Some(name) => name.to_owned(),
229 None => format!(
230 "unnamed_{}_{}",
231 self.base_entry.entry, self.base_entry.sequence
232 ),
233 };
234
235 match self.parent_from_usnjrnl() {
236 None => *self.full_path.borrow_mut() = my_name,
237 Some(p) => self.set_folder_name(mft, &p, &my_name),
238 };
239 }
240 }
241 }
242 self.full_path.borrow().to_string()
243 }
244
245 fn filename_from_usnjrnl(&self) -> Option<&str> {
246 self.usnjrnl_records.last().map(|r| r.data.filename())
247 }
248
249 fn parent_from_usnjrnl(&self) -> Option<MftReference> {
250 self.usnjrnl_records.last().and_then(|r| match &r.data {
251 UsnRecordData::V2(data) => Some(data.ParentFileReferenceNumber),
252 #[allow(unreachable_patterns)]
253 _ => None,
254 })
255 }
256
257 pub fn filesize(&self) -> u64 {
258 match self.file_name_attribute {
259 Some(ref fn_attr) => fn_attr.logical_size(),
260 None => 0,
261 }
262 }
263
264 fn format(
265 &self,
266 display_name: String,
267 timestamps: &TimestampTuple,
268 attribute_id: u32,
269 instance_id: u16,
270 ) -> String {
271 Bodyfile3Line::new()
272 .with_owned_name(format!("{}{}", display_name, self.deletion_status.borrow()))
273 .with_owned_inode(format!(
274 "{}-{}-{}",
275 self.base_entry().entry,
276 attribute_id,
277 instance_id
278 ))
279 .with_size(self.filesize())
280 .with_atime(timestamps.accessed())
281 .with_mtime(timestamps.mft_modified())
282 .with_ctime(timestamps.modified())
283 .with_crtime(timestamps.created())
284 .to_string()
285 }
286
287 fn format_fn(&self, mft: &PreprocessedMft) -> Option<String> {
288 self.file_name_attribute.as_ref().map(|fn_attr| {
289 self.format(
290 format!("{} ($FILE_NAME)", self.get_full_path(mft)),
291 fn_attr.timestamps(),
292 MftAttributeType::FileName.to_u32().unwrap(),
293 fn_attr.instance_id(),
294 )
295 })
296 }
297
298 fn format_si(
299 &self,
300 mft: &PreprocessedMft,
301 stream_name: Option<&String>,
302 attribute_id: u32,
303 instance_id: u16,
304 ) -> Option<String> {
305 self.standard_info_timestamps.as_ref().map(|si| {
306 let name = match stream_name {
307 None => self.get_full_path(mft),
308 Some(n) => format!("{}:{}", self.get_full_path(mft), n),
309 };
310 self.format(name, si, attribute_id, instance_id)
311 })
312 }
313
314 fn mft_filename(&self) -> Option<&String> {
316 match &self.file_name_attribute {
317 Some(fni) => Some(fni.filename()),
318 None => None,
319 }
320 }
321
322 fn format_usnjrnl(
323 &self,
324 mft: &PreprocessedMft,
325 record: &CommonUsnRecord,
326 usnjrnl_longflags: bool,
327 ) -> String {
328 match &record.data {
329 UsnRecordData::V2(data) => {
330 let filename_info = match self.mft_filename() {
331 None => format!(" filename={}", data.FileName),
332 Some(f) => {
333 if f != &data.FileName {
334 format!(" filename={}", data.FileName)
335 } else {
336 "".to_owned()
337 }
338 }
339 };
340
341 let reason_info = if usnjrnl_longflags {
342 format!(" reason={:+}", data.Reason)
343 } else {
344 format!(" reason={}", data.Reason)
345 };
346
347 let parent_info = mft.get_full_path(&data.ParentFileReferenceNumber);
348 let parent_info = match &parent_info.reference {
349 Some(parent_ref) => {
350 if parent_ref == &data.ParentFileReferenceNumber
351 || !parent_info.is_allocated
352 && parent_ref
353 == &MftReference::new(
354 data.ParentFileReferenceNumber.entry,
355 data.ParentFileReferenceNumber.sequence + 1,
356 )
357 {
358 "".to_owned()
359 } else {
360 format!(
361 " parent={}-{}/{}-{}/'{}'",
362 parent_ref.entry,
363 parent_ref.sequence,
364 data.ParentFileReferenceNumber.entry,
365 data.ParentFileReferenceNumber.sequence,
366 parent_info.full_path
367 )
368 }
369 }
370 None => format!(" parent='{}'", parent_info.full_path),
371 };
372
373 let display_name = format!(
374 "{} ($UsnJrnl{}{}{})",
375 self.get_full_path(mft),
376 filename_info,
377 parent_info,
378 reason_info
379 );
380 let timestamp = data.TimeStamp.timestamp();
381 Bodyfile3Line::new()
382 .with_owned_name(display_name)
383 .with_atime(timestamp)
384 .with_owned_inode(format!(
385 "{mft_entry}-{attr_type}-{usn_number}",
386 mft_entry = data.FileReferenceNumber.entry,
387 attr_type = "???",
388 usn_number = data.FileReferenceNumber.sequence
389 ))
390 .to_string()
391 }
392 }
393 }
394
395 pub fn filename_info(&self) -> &Option<FilenameInfo> {
396 if self.file_name_attribute.is_none() && self.is_allocated {
397 #[cfg(debug_assertions)]
398 panic!(
399 "no $FILE_NAME attribute found for $MFT entry {}-{}",
400 self.base_entry().entry,
401 self.base_entry().sequence
402 );
403
404 #[cfg(not(debug_assertions))]
405 log::error!(
406 "no $FILE_NAME attribute found for $MFT entry {}-{}. This is fatal because this is not a deleted file",
407 self.base_entry().entry,
408 self.base_entry().sequence
409 );
410 } &self.file_name_attribute
418 }
419
420 pub fn bodyfile_lines(&self, mft: &PreprocessedMft, usnjrnl_longflags: bool) -> BodyfileLines {
421 let mut lines: Vec<String> = Vec::new();
422 for d in self.streams.iter() {
423 let name = if d.attribute_type == MftAttributeType::IndexRoot
425 && d.name == Some("$I30".to_owned())
426 {
427 None
428 } else {
429 d.name.as_ref()
430 };
431
432 if let Some(line) =
433 self.format_si(mft, name, d.attribute_type.to_u32().unwrap(), d.instance)
434 {
435 lines.push(line);
436 }
437 }
438
439 if lines.is_empty() {
440 if let Some(line) = self.format_si(mft, None, 0, 0) {
441 lines.push(line);
442 }
443 }
444
445 BodyfileLines {
446 standard_info: lines,
447 filename_info: self.format_fn(mft),
448 usnjrnl_records: self
449 .usnjrnl_records
450 .iter()
451 .map(|r| self.format_usnjrnl(mft, r, usnjrnl_longflags))
452 .collect(),
453 }
454 }
455
456 pub fn bodyfile_lines_count(&self) -> usize {
457 (match &self.standard_info_timestamps {
458 Some(_) => cmp::min(self.streams.len(), 1),
459 None => 0,
460 } + match &self.file_name_attribute {
461 Some(_) => 1,
462 None => 0,
463 } + self.usnjrnl_records.len())
464 }
465}
466
467pub struct BodyfileLines {
468 standard_info: Vec<String>,
469 filename_info: Option<String>,
470 usnjrnl_records: Vec<String>,
471}
472
473impl Iterator for BodyfileLines {
474 type Item = String;
475 fn next(&mut self) -> Option<Self::Item> {
476 if !self.standard_info.is_empty() {
477 return self.standard_info.pop();
478 }
479 if self.filename_info.is_some() {
480 return self.filename_info.take();
481 }
482 self.usnjrnl_records.pop()
483 }
484}