ntfs_reader/
file_info.rs

1// Copyright (c) 2022, Matteo Bernacchia <dev@kikijiki.com>. All rights reserved.
2// This project is dual licensed under the Apache License 2.0 and the MIT license.
3// See the LICENSE files in the project root for details.
4
5use std::{
6    collections::HashMap,
7    path::{Path, PathBuf},
8};
9
10use time::OffsetDateTime;
11
12use crate::{
13    api::{ntfs_to_unix_time, NtfsAttributeType, ROOT_RECORD},
14    file::NtfsFile,
15    mft::Mft,
16};
17
18pub trait FileInfoCache<'a> {
19    fn get(&self, number: u64) -> Option<&Path>;
20    fn insert(&mut self, number: u64, path: PathBuf);
21}
22
23#[derive(Default)]
24pub struct HashMapCache(pub HashMap<u64, PathBuf>);
25impl FileInfoCache<'_> for HashMapCache {
26    fn get(&self, number: u64) -> Option<&Path> {
27        if let Some(p) = self.0.get(&number) {
28            Some(p)
29        } else {
30            None
31        }
32    }
33
34    fn insert(&mut self, number: u64, path: PathBuf) {
35        self.0.insert(number, path);
36    }
37}
38
39#[derive(Default)]
40pub struct VecCache(pub Vec<Option<PathBuf>>);
41impl FileInfoCache<'_> for VecCache {
42    fn get(&self, number: u64) -> Option<&Path> {
43        if self.0.len() > number as usize {
44            if let Some(p) = &self.0[number as usize] {
45                return Some(p);
46            }
47        }
48        None
49    }
50
51    fn insert(&mut self, number: u64, path: PathBuf) {
52        if self.0.len() <= number as usize {
53            self.0.resize(number as usize + 1, None);
54        }
55        self.0[number as usize] = Some(path);
56    }
57}
58
59pub struct FileInfo {
60    pub name: String,
61    pub path: PathBuf,
62    pub is_directory: bool,
63    pub size: u64,
64    pub created: Option<OffsetDateTime>,
65    pub accessed: Option<OffsetDateTime>,
66    pub modified: Option<OffsetDateTime>,
67}
68
69impl FileInfo {
70    pub fn new(mft: &Mft, file: &NtfsFile) -> Self {
71        let mut info = Self::_new(file);
72        info._compute_path(mft, file);
73        info
74    }
75
76    pub fn with_cache<C: for<'a> FileInfoCache<'a>>(
77        mft: &Mft,
78        file: &NtfsFile,
79        cache: &mut C,
80    ) -> Self {
81        let mut info = Self::_new(file);
82        info._compute_path_with_cache(mft, file, cache);
83        info
84    }
85
86    fn _new(file: &NtfsFile) -> Self {
87        let mut accessed = None;
88        let mut created = None;
89        let mut modified = None;
90        let mut size = 0u64;
91
92        file.attributes(|att| {
93            if att.header.type_id == NtfsAttributeType::StandardInformation as u32 {
94                if let Some(stdinfo) = att.as_standard_info() {
95                    accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
96                    created = Some(ntfs_to_unix_time(stdinfo.creation_time));
97                    modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
98                }
99            }
100
101            if att.header.type_id == NtfsAttributeType::Data as u32 {
102                if att.header.is_non_resident == 0 {
103                    if let Some(header) = att.resident_header() {
104                        size = header.value_length as u64;
105                    }
106                } else if let Some(header) = att.nonresident_header() {
107                    size = header.data_size;
108                }
109            }
110        });
111
112        FileInfo {
113            name: String::new(),
114            path: PathBuf::new(),
115            is_directory: file.is_directory(),
116            size,
117            created,
118            accessed,
119            modified,
120        }
121    }
122
123    fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
124        let mut next_parent;
125
126        if let Some(name) = file.get_best_file_name(mft) {
127            self.name = name.to_string();
128            next_parent = name.parent();
129        } else {
130            //warn!("No name for file {}", file.number);
131            return;
132        }
133
134        let mut components = Vec::new();
135        loop {
136            if next_parent == ROOT_RECORD {
137                break;
138            }
139
140            let cur_file = mft.get_record(next_parent);
141            if cur_file.is_none() {
142                return;
143            }
144            let cur_file = cur_file.unwrap();
145
146            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
147                let cur_name = cur_name_att.to_string();
148                components.push((cur_file.number(), PathBuf::from(cur_name)));
149                next_parent = cur_name_att.parent();
150            } else {
151                return;
152            }
153        }
154
155        let mut path = mft.volume.path.clone();
156        for (_, comp) in components.iter().rev() {
157            path.push(comp);
158        }
159        path.push(&self.name);
160
161        self.path = path;
162    }
163
164    fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
165        &mut self,
166        mft: &Mft,
167        file: &NtfsFile,
168        cache: &mut C,
169    ) {
170        let mut next_parent;
171
172        if let Some(name) = file.get_best_file_name(mft) {
173            self.name = name.to_string();
174            next_parent = name.parent();
175        } else {
176            return;
177        }
178
179        let mut components = Vec::new();
180        let mut cached_path = None;
181        loop {
182            if next_parent == ROOT_RECORD {
183                break;
184            }
185
186            // Cache hit?
187            if let Some(cur_path) = cache.get(next_parent) {
188                cached_path = Some(cur_path);
189                break;
190            }
191
192            let cur_file = mft.get_record(next_parent);
193            if cur_file.is_none() {
194                return;
195            }
196            let cur_file = cur_file.unwrap();
197
198            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
199                let cur_name = cur_name_att.to_string();
200                components.push((cur_file.number(), PathBuf::from(cur_name)));
201                next_parent = cur_name_att.parent();
202            } else {
203                return;
204            }
205        }
206
207        let mut path = PathBuf::from(cached_path.unwrap_or(&mft.volume.path));
208
209        for (number, comp) in components.iter().rev() {
210            path.push(comp);
211            cache.insert(*number, path.clone());
212        }
213
214        path.push(&self.name);
215        cache.insert(file.number, path.clone());
216
217        self.path = path;
218    }
219}