1use 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 get_file_id(&self) -> FileId {
39 FileId::Normal(self.reference_number())
40 }
41
42 pub fn is_valid(data: &[u8]) -> bool {
43 if data.len() < size_of::<NtfsFileRecordHeader>() {
44 return false;
45 }
46 let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
47 if &header.signature != FILE_RECORD_SIGNATURE {
48 return false;
49 }
50
51 if header.update_sequence_length == 0 {
52 return false;
53 }
54
55 if header.used_size as usize > data.len() {
56 return false;
57 }
58
59 let usa_end =
60 header.update_sequence_offset as usize + header.update_sequence_length as usize * 2;
61
62 let usa_num = header.update_sequence_length as usize - 1;
63 let sector_num = data.len() / SECTOR_SIZE;
64
65 if usa_end > data.len() || usa_num > sector_num {
66 return false;
67 }
68
69 if header.attributes_offset as usize >= header.used_size as usize {
70 return false;
71 }
72
73 true
74 }
75
76 pub fn attributes<F>(&self, mut f: F)
77 where
78 F: FnMut(&NtfsAttribute),
79 {
80 let mut offset = self.header.attributes_offset as usize;
81 let used = usize::min(self.header.used_size as usize, self.data.len());
82
83 while offset < used {
84 let slice = &self.data[offset..used];
85 let attr = match NtfsAttribute::new(slice) {
86 Some(attr) => attr,
87 None => break,
88 };
89
90 if attr.header.type_id == NtfsAttributeType::End as u32 {
91 break;
92 }
93
94 f(&attr);
95
96 let attr_len = attr.len();
97 if attr_len == 0 {
98 break;
99 }
100 offset = match offset.checked_add(attr_len) {
101 Some(next) if next <= used => next,
102 _ => break,
103 };
104 }
105 }
106
107 pub fn get_attribute(&self, attribute_type: NtfsAttributeType) -> Option<NtfsAttribute<'_>> {
108 let mut offset = self.header.attributes_offset as usize;
109 let used = usize::min(self.header.used_size as usize, self.data.len());
110
111 while offset < used {
112 let slice = &self.data[offset..used];
113 let attr = match NtfsAttribute::new(slice) {
114 Some(attr) => attr,
115 None => break,
116 };
117
118 if attr.header.type_id == NtfsAttributeType::End as u32 {
119 break;
120 }
121 if attr.header.type_id == attribute_type as u32 {
122 return Some(attr);
123 }
124
125 let attr_len = attr.len();
126 if attr_len == 0 {
127 break;
128 }
129 offset = match offset.checked_add(attr_len) {
130 Some(next) if next <= used => next,
131 _ => break,
132 };
133 }
134 None
135 }
136
137 pub fn get_best_file_name(&self, mft: &Mft) -> Option<NtfsFileName> {
138 let mut offset = self.header.attributes_offset as usize;
139 let used = usize::min(self.header.used_size as usize, self.data.len());
140 let mut best = None;
141
142 while offset < used {
143 let slice = &self.data[offset..used];
144 let attr = match NtfsAttribute::new(slice) {
145 Some(attr) => attr,
146 None => break,
147 };
148
149 if attr.header.type_id == NtfsAttributeType::End as u32 {
150 break;
151 }
152
153 if attr.header.type_id == NtfsAttributeType::FileName as u32 {
154 if let Some(name) = attr.as_name() {
155 if !name.is_reparse_point() {
156 if name.header.namespace == NtfsFileNamespace::Win32 as u8
157 || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
158 {
159 return Some(name);
160 } else {
161 best = Some(name);
162 }
163 }
164 }
165 }
166
167 if attr.header.type_id == NtfsAttributeType::AttributeList as u32 {
168 if attr.header.is_non_resident != 0 {
169 break;
171 }
172 let header = match attr.resident_header() {
173 Some(header) => header,
174 None => break,
175 };
176 let value_offset = header.value_offset as usize;
177 let value_length = header.value_length as usize;
178 let value_end = match value_offset.checked_add(value_length) {
179 Some(end) if end <= attr.data().len() => end,
180 _ => break,
181 };
182 let attr_slice = attr.data();
183 let att_data = &attr_slice[value_offset..value_end];
184
185 let mut att_offset = 0usize;
186 while att_offset < att_data.len() {
187 let entry_slice = &att_data[att_offset..];
188 let entry = match parse_attribute_list_entry(entry_slice) {
189 Some(entry) => entry,
190 None => break,
191 };
192 let entry_len = entry.length as usize;
193 if entry.type_id == NtfsAttributeType::FileName as u32 {
194 let rec = mft.get_record(entry.reference())?;
195 let att = rec.get_attribute(NtfsAttributeType::FileName)?;
196
197 if let Some(name) = att.as_name() {
198 if !name.is_reparse_point() {
199 if name.header.namespace == NtfsFileNamespace::Win32 as u8
200 || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
201 {
202 return Some(name);
203 } else {
204 best = Some(name);
205 break;
206 }
207 }
208 }
209 }
210
211 if entry_len == 0 {
212 break;
213 }
214 att_offset = match att_offset.checked_add(entry_len) {
215 Some(next) if next <= att_data.len() => next,
216 _ => break,
217 };
218 let align = (8 - (att_offset % 8)) % 8;
219 att_offset = match att_offset.checked_add(align) {
220 Some(next) if next <= att_data.len() => next,
221 _ => break,
222 };
223 }
224 }
225
226 let attr_len = attr.len();
227 if attr_len == 0 {
228 break;
229 }
230 offset = match offset.checked_add(attr_len) {
231 Some(next) if next <= used => next,
232 _ => break,
233 };
234 }
235
236 best
237 }
238
239 pub fn read_data(&self) -> Option<&[u8]> {
241 if let Some(att) = self.get_attribute(NtfsAttributeType::Data) {
242 if att.header.is_non_resident == 0 {
243 return att.as_resident_data();
244 }
245 }
246 None
247 }
248
249 pub fn is_used(&self) -> bool {
250 self.header.flags & NtfsFileFlags::InUse as u16 != 0
251 }
252
253 pub fn is_directory(&self) -> bool {
254 self.header.flags & NtfsFileFlags::IsDirectory as u16 != 0
255 }
256}
257
258fn parse_attribute_list_entry(data: &[u8]) -> Option<&NtfsAttributeListEntry> {
259 if data.len() < size_of::<NtfsAttributeListEntry>() {
260 return None;
261 }
262 let entry = unsafe { &*(data.as_ptr() as *const NtfsAttributeListEntry) };
263 let length = entry.length as usize;
264 if length < size_of::<NtfsAttributeListEntry>() || length > data.len() {
265 return None;
266 }
267 Some(entry)
268}