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    /// Raw Windows `FILE_ATTRIBUTE_*` flags from `$STANDARD_INFORMATION`.
67    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                // Ignore named alternate data streams. For a split non-resident
109                // stream, only the first extent (lowest VCN 0) carries the file size.
110                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            // Cache hit?
206            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}