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                let stdinfo = att.as_standard_info();
95
96                accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
97                created = Some(ntfs_to_unix_time(stdinfo.creation_time));
98                modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
99            }
100
101            if att.header.type_id == NtfsAttributeType::Data as u32 {
102                if att.header.is_non_resident == 0 {
103                    size = att.header_res.value_length as u64;
104                } else {
105                    size = att.header_nonres.data_size;
106                }
107            }
108        });
109
110        FileInfo {
111            name: String::new(),
112            path: PathBuf::new(),
113            is_directory: file.is_directory(),
114            size,
115            created,
116            accessed,
117            modified,
118        }
119    }
120
121    fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
122        let mut next_parent;
123
124        if let Some(name) = file.get_best_file_name(mft) {
125            self.name = name.to_string();
126            next_parent = name.parent();
127        } else {
128            //warn!("No name for file {}", file.number);
129            return;
130        }
131
132        let mut components = Vec::new();
133        loop {
134            if next_parent == ROOT_RECORD {
135                break;
136            }
137
138            let cur_file = mft.get_record(next_parent);
139            if cur_file.is_none() {
140                return;
141            }
142            let cur_file = cur_file.unwrap();
143
144            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
145                let cur_name = cur_name_att.to_string();
146                components.push((cur_file.number(), PathBuf::from(cur_name)));
147                next_parent = cur_name_att.parent();
148            } else {
149                return;
150            }
151        }
152
153        let mut path = mft.volume.path.clone();
154        for (_, comp) in components.iter().rev() {
155            path.push(comp);
156        }
157        path.push(&self.name);
158
159        self.path = path;
160    }
161
162    fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
163        &mut self,
164        mft: &Mft,
165        file: &NtfsFile,
166        cache: &mut C,
167    ) {
168        let mut next_parent;
169
170        if let Some(name) = file.get_best_file_name(mft) {
171            self.name = name.to_string();
172            next_parent = name.parent();
173        } else {
174            return;
175        }
176
177        let mut components = Vec::new();
178        let mut cached_path = None;
179        loop {
180            if next_parent == ROOT_RECORD {
181                break;
182            }
183
184            // Cache hit?
185            if let Some(cur_path) = cache.get(next_parent) {
186                cached_path = Some(cur_path);
187                break;
188            }
189
190            let cur_file = mft.get_record(next_parent);
191            if cur_file.is_none() {
192                return;
193            }
194            let cur_file = cur_file.unwrap();
195
196            if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
197                let cur_name = cur_name_att.to_string();
198                components.push((cur_file.number(), PathBuf::from(cur_name)));
199                next_parent = cur_name_att.parent();
200            } else {
201                return;
202            }
203        }
204
205        let mut path = PathBuf::from(cached_path.unwrap_or(&mft.volume.path));
206
207        for (number, comp) in components.iter().rev() {
208            path.push(comp);
209            cache.insert(*number, path.clone());
210        }
211
212        path.push(&self.name);
213        cache.insert(file.number, path.clone());
214
215        self.path = path;
216    }
217}