Skip to main content

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
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 created: Option<OffsetDateTime>,
67    pub accessed: Option<OffsetDateTime>,
68    pub modified: Option<OffsetDateTime>,
69}
70
71impl FileInfo {
72    pub fn new(mft: &Mft, file: &NtfsFile) -> Self {
73        let mut info = Self::_new(file);
74        info._compute_path(mft, file);
75        info
76    }
77
78    pub fn with_cache<C: for<'a> FileInfoCache<'a>>(
79        mft: &Mft,
80        file: &NtfsFile,
81        cache: &mut C,
82    ) -> Self {
83        let mut info = Self::_new(file);
84        info._compute_path_with_cache(mft, file, cache);
85        info
86    }
87
88    fn _new(file: &NtfsFile) -> Self {
89        let mut accessed = None;
90        let mut created = None;
91        let mut modified = None;
92        let mut size = 0u64;
93
94        file.attributes(|att| {
95            if att.header.type_id == NtfsAttributeType::StandardInformation as u32 {
96                if let Some(stdinfo) = att.as_standard_info() {
97                    accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
98                    created = Some(ntfs_to_unix_time(stdinfo.creation_time));
99                    modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
100                }
101            }
102
103            if att.header.type_id == NtfsAttributeType::Data as u32 {
104                if att.header.is_non_resident == 0 {
105                    if let Some(header) = att.resident_header() {
106                        size = header.value_length as u64;
107                    }
108                } else if let Some(header) = att.nonresident_header() {
109                    size = header.data_size;
110                }
111            }
112        });
113
114        FileInfo {
115            name: String::new(),
116            path: PathBuf::new(),
117            is_directory: file.is_directory(),
118            size,
119            created,
120            accessed,
121            modified,
122        }
123    }
124
125    fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
126        let mut next_parent;
127
128        if let Some(name) = file.get_best_file_name(mft) {
129            self.name = name.to_string();
130            next_parent = name.parent();
131        } else {
132            tracing::debug!("No name for file {}", file.number);
133            return;
134        }
135
136        let mut components = Vec::new();
137        for _ in 0..MAX_PATH_DEPTH {
138            if next_parent == ROOT_RECORD {
139                break;
140            }
141
142            let cur_file = match mft.get_record(next_parent) {
143                Some(f) => f,
144                None => return,
145            };
146
147            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
148                let cur_name = cur_name_att.to_string();
149                components.push((cur_file.number(), PathBuf::from(cur_name)));
150                next_parent = cur_name_att.parent();
151            } else {
152                return;
153            }
154        }
155
156        let mut path = mft.volume.path.clone();
157        for (_, comp) in components.iter().rev() {
158            path.push(comp);
159        }
160        path.push(&self.name);
161
162        self.path = path;
163    }
164
165    fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
166        &mut self,
167        mft: &Mft,
168        file: &NtfsFile,
169        cache: &mut C,
170    ) {
171        let mut next_parent;
172
173        if let Some(name) = file.get_best_file_name(mft) {
174            self.name = name.to_string();
175            next_parent = name.parent();
176        } else {
177            return;
178        }
179
180        let mut components = Vec::new();
181        let mut cached_path = None;
182        for _ in 0..MAX_PATH_DEPTH {
183            if next_parent == ROOT_RECORD {
184                break;
185            }
186
187            // Cache hit?
188            if let Some(cur_path) = cache.get(next_parent) {
189                cached_path = Some(cur_path);
190                break;
191            }
192
193            let cur_file = match mft.get_record(next_parent) {
194                Some(f) => f,
195                None => return,
196            };
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}