1use 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
18const MAX_PATH_DEPTH: usize = 1024;
19
20pub trait FileInfoCache<'a> {
21 fn get(&self, number: u64) -> Option<&Path>;
22 fn insert(&mut self, number: u64, path: PathBuf);
23}
24
25#[derive(Default)]
26pub struct HashMapCache(pub HashMap<u64, PathBuf>);
27impl FileInfoCache<'_> for HashMapCache {
28 fn get(&self, number: u64) -> Option<&Path> {
29 if let Some(p) = self.0.get(&number) {
30 Some(p)
31 } else {
32 None
33 }
34 }
35
36 fn insert(&mut self, number: u64, path: PathBuf) {
37 self.0.insert(number, path);
38 }
39}
40
41#[derive(Default)]
42pub struct VecCache(pub Vec<Option<PathBuf>>);
43impl FileInfoCache<'_> for VecCache {
44 fn get(&self, number: u64) -> Option<&Path> {
45 if (self.0.len() as u64) > number {
46 if let Some(p) = &self.0[number as usize] {
47 return Some(p);
48 }
49 }
50 None
51 }
52
53 fn insert(&mut self, number: u64, path: PathBuf) {
54 if (self.0.len() as u64) <= number {
55 self.0.resize(number as usize + 1, None);
56 }
57 self.0[number as usize] = Some(path);
58 }
59}
60
61pub struct FileInfo {
62 pub name: String,
63 pub path: PathBuf,
64 pub is_directory: bool,
65 pub size: u64,
66 pub file_attributes: u32,
68 pub created: Option<OffsetDateTime>,
69 pub accessed: Option<OffsetDateTime>,
70 pub modified: Option<OffsetDateTime>,
71}
72
73impl FileInfo {
74 pub fn new(mft: &Mft, file: &NtfsFile) -> Self {
75 let mut info = Self::_new(mft, file);
76 info._compute_path(mft, file);
77 info
78 }
79
80 pub fn with_cache<C: for<'a> FileInfoCache<'a>>(
81 mft: &Mft,
82 file: &NtfsFile,
83 cache: &mut C,
84 ) -> Self {
85 let mut info = Self::_new(mft, file);
86 info._compute_path_with_cache(mft, file, cache);
87 info
88 }
89
90 fn _new(mft: &Mft, file: &NtfsFile) -> Self {
91 let mut accessed = None;
92 let mut created = None;
93 let mut modified = None;
94 let mut size = 0u64;
95 let mut file_attributes = 0u32;
96
97 for record in mft.file_records(file) {
98 record.attributes(|att| {
99 if att.header.type_id == NtfsAttributeType::StandardInformation as u32 {
100 if let Some(stdinfo) = att.as_standard_info() {
101 accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
102 created = Some(ntfs_to_unix_time(stdinfo.creation_time));
103 modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
104 file_attributes = stdinfo.file_attributes;
105 }
106 }
107
108 if att.header.type_id == NtfsAttributeType::Data as u32
111 && att.header.name_length == 0
112 {
113 if att.header.is_non_resident == 0 {
114 if let Some(header) = att.resident_header() {
115 size = header.value_length as u64;
116 }
117 } else if let Some(header) = att.nonresident_header() {
118 if header.lowest_vcn == 0 {
119 size = header.data_size;
120 }
121 }
122 }
123 });
124 }
125
126 let base = file
127 .base_record_number()
128 .and_then(|number| mft.get_record(number));
129 let logical_file = base.as_ref().unwrap_or(file);
130
131 FileInfo {
132 name: String::new(),
133 path: PathBuf::new(),
134 is_directory: logical_file.is_directory(),
135 size,
136 file_attributes,
137 created,
138 accessed,
139 modified,
140 }
141 }
142
143 fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
144 let mut next_parent;
145
146 if let Some(name) = file.get_best_file_name(mft) {
147 self.name = name.to_string();
148 next_parent = name.parent();
149 } else {
150 tracing::debug!("No name for file {}", file.number);
151 return;
152 }
153
154 let mut components = Vec::new();
155 for _ in 0..MAX_PATH_DEPTH {
156 if next_parent == ROOT_RECORD {
157 break;
158 }
159
160 let cur_file = match mft.get_record(next_parent) {
161 Some(f) => f,
162 None => return,
163 };
164
165 if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
166 let cur_name = cur_name_att.to_string();
167 components.push((cur_file.number(), PathBuf::from(cur_name)));
168 next_parent = cur_name_att.parent();
169 } else {
170 return;
171 }
172 }
173
174 let mut path = mft.volume.path.clone();
175 for (_, comp) in components.iter().rev() {
176 path.push(comp);
177 }
178 path.push(&self.name);
179
180 self.path = path;
181 }
182
183 fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
184 &mut self,
185 mft: &Mft,
186 file: &NtfsFile,
187 cache: &mut C,
188 ) {
189 let mut next_parent;
190
191 if let Some(name) = file.get_best_file_name(mft) {
192 self.name = name.to_string();
193 next_parent = name.parent();
194 } else {
195 return;
196 }
197
198 let mut components = Vec::new();
199 let mut cached_path = None;
200 for _ in 0..MAX_PATH_DEPTH {
201 if next_parent == ROOT_RECORD {
202 break;
203 }
204
205 if let Some(cur_path) = cache.get(next_parent) {
207 cached_path = Some(cur_path);
208 break;
209 }
210
211 let cur_file = match mft.get_record(next_parent) {
212 Some(f) => f,
213 None => return,
214 };
215
216 if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
217 let cur_name = cur_name_att.to_string();
218 components.push((cur_file.number(), PathBuf::from(cur_name)));
219 next_parent = cur_name_att.parent();
220 } else {
221 return;
222 }
223 }
224
225 let mut path = PathBuf::from(cached_path.unwrap_or(&mft.volume.path));
226
227 for (number, comp) in components.iter().rev() {
228 path.push(comp);
229 cache.insert(*number, path.clone());
230 }
231
232 path.push(&self.name);
233 cache.insert(file.number, path.clone());
234
235 self.path = path;
236 }
237}