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 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 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}