Skip to main content

ntfs_reader/
file.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::mem::size_of;
6
7use crate::{api::*, attribute::NtfsAttribute, mft::Mft};
8
9pub struct NtfsFile<'a> {
10    pub number: u64,
11    pub header: &'a NtfsFileRecordHeader,
12    pub data: &'a [u8],
13}
14
15impl<'a> NtfsFile<'a> {
16    pub fn new(number: u64, data: &'a [u8]) -> Self {
17        assert!(
18            data.len() >= size_of::<NtfsFileRecordHeader>(),
19            "NTFS file record too small",
20        );
21        let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
22        NtfsFile {
23            number,
24            header,
25            data,
26        }
27    }
28
29    pub fn number(&self) -> u64 {
30        self.number
31    }
32
33    pub fn reference_number(&self) -> u64 {
34        let seq = self.header.sequence_value as u64;
35        (seq << 48) | (self.number & 0x0000_FFFF_FFFF_FFFF)
36    }
37
38    pub fn base_reference_number(&self) -> Option<u64> {
39        let reference = self.header.base_reference;
40        (reference != 0).then_some(reference)
41    }
42
43    pub fn base_record_number(&self) -> Option<u64> {
44        self.base_reference_number()
45            .map(|reference| reference & 0x0000_FFFF_FFFF_FFFF)
46    }
47
48    pub fn is_extension(&self) -> bool {
49        self.base_reference_number().is_some()
50    }
51
52    pub fn get_file_id(&self) -> FileId {
53        FileId::Normal(self.reference_number())
54    }
55
56    pub fn is_valid(data: &[u8]) -> bool {
57        if data.len() < size_of::<NtfsFileRecordHeader>() {
58            return false;
59        }
60        let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
61        if &header.signature != FILE_RECORD_SIGNATURE {
62            return false;
63        }
64
65        if header.update_sequence_length == 0 {
66            return false;
67        }
68
69        if header.used_size as usize > data.len() {
70            return false;
71        }
72
73        let usa_end =
74            header.update_sequence_offset as usize + header.update_sequence_length as usize * 2;
75
76        let usa_num = header.update_sequence_length as usize - 1;
77        let sector_num = data.len() / SECTOR_SIZE;
78
79        if usa_end > data.len() || usa_num > sector_num {
80            return false;
81        }
82
83        if header.attributes_offset as usize >= header.used_size as usize {
84            return false;
85        }
86
87        true
88    }
89
90    pub fn attributes<F>(&self, mut f: F)
91    where
92        F: FnMut(&NtfsAttribute),
93    {
94        let mut offset = self.header.attributes_offset as usize;
95        let used = usize::min(self.header.used_size as usize, self.data.len());
96
97        while offset < used {
98            let slice = &self.data[offset..used];
99            let attr = match NtfsAttribute::new(slice) {
100                Some(attr) => attr,
101                None => break,
102            };
103
104            if attr.header.type_id == NtfsAttributeType::End as u32 {
105                break;
106            }
107
108            f(&attr);
109
110            let attr_len = attr.len();
111            if attr_len == 0 {
112                break;
113            }
114            offset = match offset.checked_add(attr_len) {
115                Some(next) if next <= used => next,
116                _ => break,
117            };
118        }
119    }
120
121    pub fn get_attribute(&self, attribute_type: NtfsAttributeType) -> Option<NtfsAttribute<'_>> {
122        let mut offset = self.header.attributes_offset as usize;
123        let used = usize::min(self.header.used_size as usize, self.data.len());
124
125        while offset < used {
126            let slice = &self.data[offset..used];
127            let attr = match NtfsAttribute::new(slice) {
128                Some(attr) => attr,
129                None => break,
130            };
131
132            if attr.header.type_id == NtfsAttributeType::End as u32 {
133                break;
134            }
135            if attr.header.type_id == attribute_type as u32 {
136                return Some(attr);
137            }
138
139            let attr_len = attr.len();
140            if attr_len == 0 {
141                break;
142            }
143            offset = match offset.checked_add(attr_len) {
144                Some(next) if next <= used => next,
145                _ => break,
146            };
147        }
148        None
149    }
150
151    pub fn get_best_file_name(&self, mft: &Mft) -> Option<NtfsFileName> {
152        let mut best = None;
153
154        for record in mft.file_records(self) {
155            let mut preferred = None;
156            record.file_names(|name| {
157                if name.is_reparse_point() {
158                    return;
159                }
160                if name.header.namespace == NtfsFileNamespace::Win32 as u8
161                    || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
162                {
163                    preferred = Some(name);
164                } else if best.is_none() {
165                    best = Some(name);
166                }
167            });
168
169            if preferred.is_some() {
170                return preferred;
171            }
172        }
173
174        best
175    }
176
177    fn file_names<F>(&self, mut f: F)
178    where
179        F: FnMut(NtfsFileName),
180    {
181        let mut offset = self.header.attributes_offset as usize;
182        let used = usize::min(self.header.used_size as usize, self.data.len());
183
184        while offset < used {
185            let slice = &self.data[offset..used];
186            let attr = match NtfsAttribute::new(slice) {
187                Some(attr) => attr,
188                None => break,
189            };
190
191            if attr.header.type_id == NtfsAttributeType::End as u32 {
192                break;
193            }
194
195            if attr.header.type_id == NtfsAttributeType::FileName as u32 {
196                if let Some(name) = attr.as_name() {
197                    f(name);
198                }
199            }
200
201            let attr_len = attr.len();
202            if attr_len == 0 {
203                break;
204            }
205            offset = match offset.checked_add(attr_len) {
206                Some(next) if next <= used => next,
207                _ => break,
208            };
209        }
210    }
211
212    // This cannot read nonresident data!
213    pub fn read_data(&self) -> Option<&[u8]> {
214        if let Some(att) = self.get_attribute(NtfsAttributeType::Data) {
215            if att.header.is_non_resident == 0 {
216                return att.as_resident_data();
217            }
218        }
219        None
220    }
221
222    pub fn is_used(&self) -> bool {
223        self.header.flags & NtfsFileFlags::InUse as u16 != 0
224    }
225
226    pub fn is_directory(&self) -> bool {
227        self.header.flags & NtfsFileFlags::IsDirectory as u16 != 0
228    }
229}