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 crate::{api::*, attribute::NtfsAttribute, mft::Mft};
6
7pub struct NtfsFile<'a> {
8    pub number: u64,
9    pub header: &'a NtfsFileRecordHeader,
10    pub data: &'a [u8],
11}
12
13impl<'a> NtfsFile<'a> {
14    pub fn new(number: u64, data: &'a [u8]) -> Self {
15        unsafe {
16            let header = &*(data.as_ptr() as *const NtfsFileRecordHeader);
17            NtfsFile {
18                number,
19                header,
20                data,
21            }
22        }
23    }
24
25    pub fn number(&self) -> u64 {
26        self.number
27    }
28
29    pub fn reference_number(&self) -> u64 {
30        let seq = self.header.sequence_value as u64;
31        (seq << 48) | (self.number & 0x0000_FFFF_FFFF_FFFF)
32    }
33
34    pub fn get_file_id(&self) -> FileId {
35        FileId::Normal(self.reference_number())
36    }
37
38    pub fn is_valid(data: &[u8]) -> bool {
39        let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
40        if &header.signature != FILE_RECORD_SIGNATURE {
41            return false;
42        }
43
44        if header.update_sequence_length == 0 {
45            return false;
46        }
47
48        let usa_end =
49            header.update_sequence_offset as usize + header.update_sequence_length as usize * 2;
50
51        let usa_num = header.update_sequence_length as usize - 1;
52        let sector_num = data.len() / SECTOR_SIZE;
53
54        if usa_end > data.len() || usa_num > sector_num {
55            return false;
56        }
57
58        true
59    }
60
61    pub fn attributes<F>(&self, mut f: F)
62    where
63        F: FnMut(&NtfsAttribute),
64    {
65        let mut offset = self.header.attributes_offset as usize;
66        loop {
67            if offset >= self.header.used_size as usize {
68                break;
69            }
70
71            let att_type = u32::from_le_bytes(self.data[offset..offset + 4].try_into().unwrap());
72            if att_type == NtfsAttributeType::End as u32 {
73                break;
74            }
75
76            let att = NtfsAttribute::new(&self.data[offset..]);
77            f(&att);
78
79            offset += att.header.length as usize;
80        }
81    }
82
83    pub fn get_attribute(&self, attribute_type: NtfsAttributeType) -> Option<NtfsAttribute<'_>> {
84        let mut offset = self.header.attributes_offset as usize;
85
86        loop {
87            if offset >= self.header.used_size as usize {
88                break;
89            }
90            let att = NtfsAttribute::new(&self.data[offset..]);
91            if att.header.type_id == NtfsAttributeType::End as u32 {
92                break;
93            }
94            if att.header.type_id == attribute_type as u32 {
95                return Some(NtfsAttribute::new(&self.data[offset..]));
96            }
97
98            offset += att.header.length as usize;
99        }
100        None
101    }
102
103    pub fn get_best_file_name(&self, mft: &Mft) -> Option<NtfsFileName> {
104        let mut offset = self.header.attributes_offset as usize;
105        let mut best = None;
106
107        loop {
108            if offset >= self.header.used_size as usize {
109                break;
110            }
111            let att = NtfsAttribute::new(&self.data[offset..]);
112            if att.header.type_id == NtfsAttributeType::End as u32 {
113                break;
114            }
115
116            if att.header.type_id == NtfsAttributeType::FileName as u32 {
117                let name = att.as_name();
118
119                // Ignore junctions
120                if !name.is_reparse_point() {
121                    if name.header.namespace == NtfsFileNamespace::Win32 as u8
122                        || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
123                    {
124                        return Some(*name);
125                    } else {
126                        best = Some(*name);
127                    }
128                }
129            }
130
131            if att.header.type_id == NtfsAttributeType::AttributeList as u32 {
132                let header = unsafe {
133                    &*(self.data[offset..].as_ptr() as *const NtfsResidentAttributeHeader)
134                };
135
136                let att_data = &self.data[offset + header.value_offset as usize..];
137
138                let mut att_offset = 0;
139                while att_offset < header.value_length as usize {
140                    let entry = unsafe {
141                        &*(att_data[att_offset..].as_ptr() as *const NtfsAttributeListEntry)
142                    };
143                    if entry.type_id == NtfsAttributeType::FileName as u32 {
144                        let rec = mft.get_record(entry.reference())?;
145                        let att = rec.get_attribute(NtfsAttributeType::FileName)?;
146                        let name = att.as_name();
147
148                        // Ignore junctions
149                        if !name.is_reparse_point() {
150                            if name.header.namespace == NtfsFileNamespace::Win32 as u8
151                                || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
152                            {
153                                return Some(*name);
154                            } else {
155                                best = Some(*name);
156                                break;
157                            }
158                        }
159                    }
160
161                    att_offset += entry.length as usize;
162                    // Make sure the offset is aligned to 8 bytes
163                    att_offset += (8 - (att_offset % 8)) % 8;
164                }
165            }
166
167            offset += att.header.length as usize;
168        }
169
170        best
171    }
172
173    // This cannot read nonresident data!
174    pub fn read_data(&self) -> Option<&[u8]> {
175        if let Some(att) = self.get_attribute(NtfsAttributeType::Data) {
176            assert!(att.header.is_non_resident != 0);
177            return Some(att.as_resident_data());
178        }
179        None
180    }
181
182    pub fn is_used(&self) -> bool {
183        self.header.flags & NtfsFileFlags::InUse as u16 != 0
184    }
185
186    pub fn is_directory(&self) -> bool {
187        self.header.flags & NtfsFileFlags::IsDirectory as u16 != 0
188    }
189}