ntfs_reader/
mft.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::io::{Read, Seek, SeekFrom};
6
7use crate::{
8    aligned_reader::open_volume,
9    api::*,
10    attribute::NtfsAttribute,
11    errors::{NtfsReaderError, NtfsReaderResult},
12    file::NtfsFile,
13    volume::Volume,
14};
15
16pub struct Mft {
17    pub volume: Volume,
18    pub data: Vec<u8>,
19    pub bitmap: Vec<u8>,
20    pub max_record: u64,
21}
22
23impl Mft {
24    pub fn new(volume: Volume) -> NtfsReaderResult<Self> {
25        let mut reader = open_volume(&volume.path)?;
26
27        let mft_record = Self::get_record_fs(
28            &mut reader,
29            volume.file_record_size as usize,
30            volume.mft_position,
31        );
32
33        let mut data =
34            Self::read_data_fs(&volume, &mut reader, &mft_record, NtfsAttributeType::Data)
35                .ok_or_else(|| NtfsReaderError::MissingMftAttribute("Data".to_string()))?;
36        let bitmap =
37            Self::read_data_fs(&volume, &mut reader, &mft_record, NtfsAttributeType::Bitmap)
38                .ok_or_else(|| NtfsReaderError::MissingMftAttribute("Bitmap".to_string()))?;
39
40        let max_record = (data.len() / volume.file_record_size as usize) as u64;
41
42        // Is this even worth the extra time?
43        // let max_bit = bitmap
44        //     .iter()
45        //     .enumerate()
46        //     .filter(|x| *x.1 != 0)
47        //     .max_by_key(|x| x.0)
48        //     .unwrap_or((0, &0));
49        // let max_record_bitmap = max_bit.0 as u64 * 8 + *max_bit.1 as u64;
50        // let max_record_mft = (data.len() / volume.file_record_size as usize) as u64;
51        // let max_record = u64::min(max_record_bitmap, max_record_mft);
52
53        // Fixup all records so we are non mutable from now on.
54        for number in 0..max_record {
55            let start = number as usize * volume.file_record_size as usize;
56            let end = start + volume.file_record_size as usize;
57            let data = &mut data[start..end];
58            Self::fixup_record(data);
59        }
60
61        Ok(Mft {
62            volume,
63            data,
64            bitmap,
65            max_record,
66        })
67    }
68
69    pub fn record_exists(&self, number: u64) -> bool {
70        if number > self.max_record {
71            return false;
72        }
73
74        let bitmap_idx = (number / 8) as usize;
75        let bitmap_off = number % 8;
76
77        if bitmap_idx >= self.bitmap.len() {
78            return false;
79        }
80
81        let bit = self.bitmap[bitmap_idx];
82        bit & (1 << bitmap_off) != 0
83    }
84
85    pub fn iterate_files<F>(&self, mut f: F)
86    where
87        F: FnMut(&NtfsFile),
88    {
89        for number in FIRST_NORMAL_RECORD..self.max_record {
90            if self.record_exists(number) {
91                if let Some(file) = self.get_record(number) {
92                    if file.is_used() {
93                        f(&file);
94                    }
95                }
96            }
97        }
98    }
99
100    pub fn get_record_data(&self, number: u64) -> &[u8] {
101        let start = number as usize * self.volume.file_record_size as usize;
102        let end = start + self.volume.file_record_size as usize;
103        &self.data[start..end]
104    }
105
106    pub fn get_record(&self, number: u64) -> Option<NtfsFile<'_>> {
107        if number >= self.max_record {
108            return None;
109        }
110        let data = self.get_record_data(number);
111
112        if NtfsFile::is_valid(data) {
113            return Some(NtfsFile::new(number, data));
114        }
115
116        None
117    }
118
119    pub fn get_record_fs<R>(fs: &mut R, file_record_size: usize, position: u64) -> Vec<u8>
120    where
121        R: Seek + Read,
122    {
123        let mut data = vec![0; file_record_size];
124        let _ = fs.seek(SeekFrom::Start(position));
125        let _ = fs.read_exact(&mut data);
126
127        if NtfsFile::is_valid(&data) {
128            Self::fixup_record(&mut data);
129            data
130        } else {
131            Vec::new()
132        }
133    }
134
135    pub fn read_data_fs<R>(
136        volume: &Volume,
137        reader: &mut R,
138        record: &[u8],
139        attribute_type: NtfsAttributeType,
140    ) -> Option<Vec<u8>>
141    where
142        R: Seek + Read,
143    {
144        let header = unsafe { &*(record.as_ptr() as *const NtfsFileRecordHeader) };
145        let mut att_offset = header.attributes_offset as usize;
146
147        // First pass: look for the attribute directly in this record
148        loop {
149            if att_offset >= header.used_size as usize {
150                break;
151            }
152
153            let att = NtfsAttribute::new(&record[att_offset..]);
154            if att.header.type_id == NtfsAttributeType::End as u32 {
155                break;
156            }
157
158            if att.header.type_id == attribute_type as u32 {
159                return Some(Self::read_attribute_data(reader, &att, volume));
160            }
161
162            att_offset += att.header.length as usize;
163        }
164
165        // Second pass: if not found, check attribute list entries
166        att_offset = header.attributes_offset as usize;
167        loop {
168            if att_offset >= header.used_size as usize {
169                break;
170            }
171
172            let att = NtfsAttribute::new(&record[att_offset..]);
173            if att.header.type_id == NtfsAttributeType::End as u32 {
174                break;
175            }
176
177            if att.header.type_id == NtfsAttributeType::AttributeList as u32 {
178                let att_list_data = if att.header.is_non_resident != 0 {
179                    Self::read_attribute_data(reader, &att, volume)
180                } else {
181                    let header = unsafe {
182                        &*(record[att_offset..].as_ptr() as *const NtfsResidentAttributeHeader)
183                    };
184                    let value_length = header.value_length;
185                    record[att_offset + header.value_offset as usize
186                        ..att_offset + header.value_offset as usize + value_length as usize]
187                        .to_vec()
188                };
189
190                let mut list_offset = 0;
191
192                while list_offset < att_list_data.len() {
193                    let entry = unsafe {
194                        &*(att_list_data[list_offset..].as_ptr() as *const NtfsAttributeListEntry)
195                    };
196
197                    let type_id = entry.type_id;
198                    let reference = entry.reference();
199
200                    if type_id == attribute_type as u32 {
201                        let record_position =
202                            volume.mft_position + (reference * volume.file_record_size);
203                        let target_record = Self::get_record_fs(
204                            reader,
205                            volume.file_record_size as usize,
206                            record_position,
207                        );
208
209                        if !target_record.is_empty() {
210                            // Find the attribute directly in the target record
211                            let header = unsafe {
212                                &*(target_record.as_ptr() as *const NtfsFileRecordHeader)
213                            };
214                            let mut att_offset = header.attributes_offset as usize;
215
216                            loop {
217                                if att_offset >= header.used_size as usize {
218                                    break;
219                                }
220
221                                let att = NtfsAttribute::new(&target_record[att_offset..]);
222                                if att.header.type_id == NtfsAttributeType::End as u32 {
223                                    break;
224                                }
225
226                                if att.header.type_id == attribute_type as u32 {
227                                    return Some(Self::read_attribute_data(reader, &att, volume));
228                                }
229
230                                att_offset += att.header.length as usize;
231                            }
232                        }
233                    }
234
235                    list_offset += entry.length as usize;
236                    // Align to 8 bytes
237                    list_offset += (8 - (list_offset % 8)) % 8;
238                }
239            }
240
241            att_offset += att.header.length as usize;
242        }
243
244        None
245    }
246
247    fn read_attribute_data<R>(reader: &mut R, att: &NtfsAttribute, volume: &Volume) -> Vec<u8>
248    where
249        R: Seek + Read,
250    {
251        let mut data = Vec::<u8>::new();
252
253        if att.header.is_non_resident == 0 {
254            data.copy_from_slice(att.as_resident_data());
255        } else {
256            let mut buffer = Vec::new();
257            let (size, runs) = att.get_nonresident_data_runs(volume);
258            data.reserve_exact(size);
259            let mut copied = 0usize;
260
261            for run in runs.iter() {
262                if copied >= size {
263                    break;
264                }
265
266                let buf_size = usize::min(run.len(), size - copied);
267                buffer.resize(buf_size, 0u8);
268
269                let _ = reader.seek(SeekFrom::Start(run.start as u64));
270                let _ = reader.read_exact(&mut buffer);
271
272                data.append(&mut buffer.clone());
273                copied += buf_size;
274            }
275        }
276
277        data
278    }
279
280    fn fixup_record(data: &mut [u8]) {
281        let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
282
283        // Fixup
284        let usn_start = header.update_sequence_offset as usize;
285        //let usn_end = usn_start + 2;
286        let usa_start = usn_start + 2;
287        let usa_end = usn_start + header.update_sequence_length as usize * 2;
288
289        //let mut usn = [0u8; 2];
290        //usn.copy_from_slice(&data[usn_start..usn_end]);
291
292        let mut sector_off = SECTOR_SIZE - 2;
293        for usa_off in (usa_start..usa_end).step_by(2) {
294            let mut usa = [0u8; 2];
295            usa.clone_from_slice(&data[usa_off..usa_off + 2]);
296
297            //let dst = &data[sector_off..sector_off + 2];
298            //if dst != usn {
299            //    return false;
300            //}
301
302            data[sector_off..sector_off + 2].copy_from_slice(&usa);
303            sector_off += SECTOR_SIZE;
304        }
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use crate::{
311        errors::NtfsReaderResult, mft::Mft, test_utils::TEST_VOLUME_LETTER, volume::Volume,
312    };
313
314    #[test]
315    fn test_mft_creation() -> NtfsReaderResult<()> {
316        let vol = Volume::new(format!("\\\\.\\{}:", TEST_VOLUME_LETTER))?;
317        let mft = Mft::new(vol)?;
318
319        assert!(mft.max_record > 0);
320        assert!(!mft.data.is_empty());
321        assert!(!mft.bitmap.is_empty());
322
323        Ok(())
324    }
325
326    #[test]
327    fn test_record_exists() -> NtfsReaderResult<()> {
328        let vol = Volume::new(format!("\\\\.\\{}:", TEST_VOLUME_LETTER))?;
329        let mft = Mft::new(vol)?;
330
331        // MFT record (0) should always exist
332        assert!(mft.record_exists(0));
333
334        // Test out of bounds
335        assert!(!mft.record_exists(u64::MAX));
336        assert!(!mft.record_exists(mft.max_record + 1));
337
338        Ok(())
339    }
340
341    #[test]
342    fn test_get_record() -> NtfsReaderResult<()> {
343        let vol = Volume::new(format!("\\\\.\\{}:", TEST_VOLUME_LETTER))?;
344        let mft = Mft::new(vol)?;
345
346        // MFT record (0) should be retrievable
347        let record = mft.get_record(0);
348        assert!(record.is_some());
349
350        // Invalid record number should return None
351        let invalid = mft.get_record(mft.max_record + 1);
352        assert!(invalid.is_none());
353
354        Ok(())
355    }
356
357    #[test]
358    fn test_iterate_files() -> NtfsReaderResult<()> {
359        let vol = Volume::new(format!("\\\\.\\{}:", TEST_VOLUME_LETTER))?;
360        let mft = Mft::new(vol)?;
361
362        let mut count = 0;
363        mft.iterate_files(|_file| {
364            count += 1;
365        });
366
367        assert!(count > 0, "Should iterate over at least some files");
368
369        Ok(())
370    }
371}