1use std::collections::VecDeque;
6use std::ffi::{CString, OsString};
7use std::mem::size_of;
8use std::os::raw::c_void;
9use std::os::windows::ffi::OsStringExt;
10use std::path::{Path, PathBuf};
11
12use windows::core::PCSTR;
13use windows::Win32::Foundation::{self, ERROR_IO_PENDING, ERROR_MORE_DATA};
14use windows::Win32::Storage::FileSystem::{self, FILE_FLAG_BACKUP_SEMANTICS};
15use windows::Win32::System::Ioctl;
16use windows::Win32::System::Threading::INFINITE;
17use windows::Win32::System::IO::{self, GetQueuedCompletionStatus};
18
19use crate::{api::FileId, errors::NtfsReaderResult, volume::Volume};
20
21#[repr(align(64))]
22#[derive(Debug, Clone, Copy)]
23struct AlignedBuffer<const N: usize>([u8; N]);
24
25fn get_usn_record_time(timestamp: i64) -> std::time::Duration {
26 if timestamp <= 0 {
27 return std::time::Duration::from_nanos(0);
28 }
29
30 let nanos = (timestamp as i128).saturating_mul(100);
31 let capped = nanos.min(u64::MAX as i128);
32 std::time::Duration::from_nanos(capped as u64)
33}
34
35fn get_usn_record_name(file_name_length: u16, file_name: *const u16) -> String {
36 let size = (file_name_length / 2) as usize;
37
38 if size > 0 {
39 unsafe {
40 let name_u16 = std::slice::from_raw_parts(file_name, size);
41 let name = std::ffi::OsString::from_wide(name_u16)
42 .to_string_lossy()
43 .into_owned();
44 return name;
45 }
46 }
47
48 String::new()
49}
50
51fn get_file_path(volume_handle: Foundation::HANDLE, file_id: FileId) -> Option<PathBuf> {
52 let (id, id_type) = match file_id {
53 FileId::Normal(id) => (
54 FileSystem::FILE_ID_DESCRIPTOR_0 { FileId: id as i64 },
55 FileSystem::FileIdType,
56 ),
57 FileId::Extended(id) => (
58 FileSystem::FILE_ID_DESCRIPTOR_0 { ExtendedFileId: id },
59 FileSystem::ExtendedFileIdType,
60 ),
61 };
62
63 let file_id_desc = FileSystem::FILE_ID_DESCRIPTOR {
64 Type: id_type,
65 dwSize: size_of::<FileSystem::FILE_ID_DESCRIPTOR>() as u32,
66 Anonymous: id,
67 };
68
69 unsafe {
70 let file_handle = FileSystem::OpenFileById(
71 volume_handle,
72 &file_id_desc,
73 0,
74 FileSystem::FILE_SHARE_READ
75 | FileSystem::FILE_SHARE_WRITE
76 | FileSystem::FILE_SHARE_DELETE,
77 None,
78 FILE_FLAG_BACKUP_SEMANTICS,
79 )
80 .unwrap_or(Foundation::INVALID_HANDLE_VALUE);
81
82 if file_handle.is_invalid() {
83 return None;
84 }
85
86 let mut info_buffer_size = size_of::<FileSystem::FILE_NAME_INFO>()
87 + (Foundation::MAX_PATH as usize) * size_of::<u16>();
88 let mut info_buffer = vec![0u8; info_buffer_size];
89
90 let result = loop {
91 let info_result = FileSystem::GetFileInformationByHandleEx(
92 file_handle,
93 FileSystem::FileNameInfo,
94 info_buffer.as_mut_ptr() as *mut _,
95 info_buffer_size as u32,
96 );
97
98 match info_result {
99 Ok(_) => {
100 let (_, body, _) = info_buffer.align_to::<FileSystem::FILE_NAME_INFO>();
101 let info = &body[0];
102 let name_len = info.FileNameLength as usize / size_of::<u16>();
103 let name_u16 = std::slice::from_raw_parts(info.FileName.as_ptr(), name_len);
104 break Some(PathBuf::from(OsString::from_wide(name_u16)));
105 }
106 Err(err) => {
107 if err.code() == ERROR_MORE_DATA.to_hresult() {
108 let required_size = info_buffer.align_to::<FileSystem::FILE_NAME_INFO>().1
110 [0]
111 .FileNameLength as usize;
112
113 info_buffer_size = size_of::<FileSystem::FILE_NAME_INFO>() + required_size;
114 info_buffer.resize(info_buffer_size, 0);
115 } else {
116 break None;
117 }
118 }
119 }
120 };
121
122 let _ = Foundation::CloseHandle(file_handle);
123 result
124 }
125}
126
127fn get_usn_record_path(
128 volume_path: &Path,
129 volume_handle: Foundation::HANDLE,
130 file_name: String,
131 file_id: FileId,
132 parent_id: FileId,
133) -> PathBuf {
134 if let Some(parent_path) = get_file_path(volume_handle, parent_id) {
138 return volume_path.join(parent_path.join(&file_name));
139 } else {
140 if let Some(path) = get_file_path(volume_handle, file_id) {
143 return volume_path.join(path);
144 }
145 }
146
147 tracing::debug!("Could not get path: {}", file_name);
148 PathBuf::from(&file_name)
149}
150
151#[derive(Debug, Clone)]
152pub struct UsnRecord {
153 pub usn: i64,
154 pub timestamp: std::time::Duration,
155 pub file_id: FileId,
156 pub parent_id: FileId,
157 pub reason: u32,
158 pub path: PathBuf,
159}
160
161impl UsnRecord {
162 fn from_v2(journal: &Journal, rec: &Ioctl::USN_RECORD_V2) -> Self {
163 let usn = rec.Usn;
164 let timestamp = get_usn_record_time(rec.TimeStamp);
165 let file_id = FileId::Normal(rec.FileReferenceNumber);
166 let parent_id = FileId::Normal(rec.ParentFileReferenceNumber);
167 let reason = rec.Reason;
168 let name = get_usn_record_name(rec.FileNameLength, rec.FileName.as_ptr());
169 let path = get_usn_record_path(
170 &journal.volume.path,
171 journal.volume_handle,
172 name,
173 file_id,
174 parent_id,
175 );
176
177 UsnRecord {
178 usn,
179 timestamp,
180 file_id,
181 parent_id,
182 reason,
183 path,
184 }
185 }
186
187 fn from_v3(journal: &Journal, rec: &Ioctl::USN_RECORD_V3) -> Self {
188 let usn = rec.Usn;
189 let timestamp = get_usn_record_time(rec.TimeStamp);
190 let file_id = FileId::Extended(rec.FileReferenceNumber);
191 let parent_id = FileId::Extended(rec.ParentFileReferenceNumber);
192 let reason = rec.Reason;
193
194 let name = get_usn_record_name(rec.FileNameLength, rec.FileName.as_ptr());
195 let path = get_usn_record_path(
196 &journal.volume.path,
197 journal.volume_handle,
198 name,
199 file_id,
200 parent_id,
201 );
202
203 UsnRecord {
204 usn,
205 timestamp,
206 file_id,
207 parent_id,
208 reason,
209 path,
210 }
211 }
212}
213
214#[derive(Debug, Clone)]
215pub enum NextUsn {
216 First,
217 Next,
218 Custom(i64),
219}
220
221#[derive(Debug, Clone)]
222pub enum HistorySize {
223 Unlimited,
224 Limited(usize),
225}
226
227#[derive(Debug, Clone)]
228pub struct JournalOptions {
229 pub reason_mask: u32,
230 pub next_usn: NextUsn,
231 pub max_history_size: HistorySize,
232}
233
234impl Default for JournalOptions {
235 fn default() -> Self {
236 JournalOptions {
237 reason_mask: 0xFFFFFFFF,
238 next_usn: NextUsn::Next,
239 max_history_size: HistorySize::Unlimited,
240 }
241 }
242}
243
244pub struct Journal {
245 volume: Volume,
246 volume_handle: Foundation::HANDLE,
247 port: Foundation::HANDLE,
248 journal: Ioctl::USN_JOURNAL_DATA_V2,
249 next_usn: i64,
250 reason_mask: u32, history: VecDeque<UsnRecord>,
252 max_history_size: usize,
253}
254
255impl Journal {
256 pub fn new(volume: Volume, options: JournalOptions) -> NtfsReaderResult<Journal> {
257 let volume_handle: Foundation::HANDLE;
258
259 unsafe {
260 let path = CString::new(volume.path.to_str().unwrap()).unwrap();
262
263 volume_handle = FileSystem::CreateFileA(
264 PCSTR::from_raw(path.as_bytes_with_nul().as_ptr()),
265 (FileSystem::FILE_GENERIC_READ | FileSystem::FILE_GENERIC_WRITE).0,
266 FileSystem::FILE_SHARE_READ
267 | FileSystem::FILE_SHARE_WRITE
268 | FileSystem::FILE_SHARE_DELETE,
269 None,
270 FileSystem::OPEN_EXISTING,
271 FileSystem::FILE_FLAG_OVERLAPPED,
272 None,
273 )?;
274 }
275
276 let mut journal = Ioctl::USN_JOURNAL_DATA_V2::default();
277
278 unsafe {
279 let mut ioctl_bytes_returned = 0;
280 IO::DeviceIoControl(
281 volume_handle,
282 Ioctl::FSCTL_QUERY_USN_JOURNAL,
283 None,
284 0,
285 Some(&mut journal as *mut _ as *mut c_void),
286 size_of::<Ioctl::USN_JOURNAL_DATA_V2>() as u32,
287 Some(&mut ioctl_bytes_returned),
288 None,
289 )?;
290 }
291
292 let next_usn = match options.next_usn {
293 NextUsn::First => 0,
294 NextUsn::Next => journal.NextUsn,
295 NextUsn::Custom(usn) => usn,
296 };
297
298 let max_history_size = match options.max_history_size {
299 HistorySize::Unlimited => 0,
300 HistorySize::Limited(size) => size,
301 };
302
303 let port = unsafe { IO::CreateIoCompletionPort(volume_handle, None, 0, 1)? };
304
305 Ok(Journal {
306 volume,
307 volume_handle,
308 port,
309 journal,
310 next_usn,
311 reason_mask: options.reason_mask,
312 history: VecDeque::new(),
313 max_history_size,
314 })
315 }
316
317 pub fn read(&mut self) -> NtfsReaderResult<Vec<UsnRecord>> {
318 self.read_sized::<4096>()
319 }
320
321 pub fn read_sized<const BUFFER_SIZE: usize>(&mut self) -> NtfsReaderResult<Vec<UsnRecord>> {
322 let mut results = Vec::<UsnRecord>::new();
323
324 let mut read = Ioctl::READ_USN_JOURNAL_DATA_V1 {
325 StartUsn: self.next_usn,
326 ReasonMask: self.reason_mask,
327 ReturnOnlyOnClose: 0,
328 Timeout: 0,
329 BytesToWaitFor: 0,
330 UsnJournalID: self.journal.UsnJournalID,
331 MinMajorVersion: 2,
332 MaxMajorVersion: u16::min(3, self.journal.MaxSupportedMajorVersion),
333 };
334
335 let mut buffer = AlignedBuffer::<BUFFER_SIZE>([0u8; BUFFER_SIZE]);
336
337 let mut bytes_returned = 0;
338 let mut overlapped = IO::OVERLAPPED {
339 ..Default::default()
340 };
341
342 unsafe {
343 let result = IO::DeviceIoControl(
344 self.volume_handle,
345 Ioctl::FSCTL_READ_USN_JOURNAL,
346 Some(&mut read as *mut _ as *mut c_void),
347 size_of::<Ioctl::READ_USN_JOURNAL_DATA_V1>() as u32,
348 Some(&mut buffer as *mut _ as *mut c_void),
349 BUFFER_SIZE as u32,
350 Some(&mut bytes_returned),
351 Some(&mut overlapped),
352 );
353
354 match result {
355 Ok(_) => {}
356 Err(err) => {
357 if err.code() == ERROR_IO_PENDING.to_hresult() {
358 let mut key = 0usize;
359 let mut completed = std::ptr::null_mut();
360 GetQueuedCompletionStatus(
361 self.port,
362 &mut bytes_returned,
363 &mut key,
364 &mut completed,
365 INFINITE,
366 )?;
367 } else {
368 return Err(err.into());
369 }
370 }
371 }
372 }
373
374 let next_usn = i64::from_le_bytes(buffer.0[0..8].try_into().unwrap());
375 if next_usn == 0 || next_usn < self.next_usn {
376 return Ok(results);
377 } else {
378 self.next_usn = next_usn;
379 }
380
381 let mut offset = 8; while offset < bytes_returned {
383 let remaining = (bytes_returned - offset) as usize;
384 if remaining < size_of::<Ioctl::USN_RECORD_COMMON_HEADER>() {
385 break;
386 }
387
388 let (record_len, record) = unsafe {
389 let record_ptr =
390 buffer.0[offset as usize..].as_ptr() as *const Ioctl::USN_RECORD_UNION;
391
392 let record_len = (*record_ptr).Header.RecordLength;
393 if record_len == 0 || record_len as usize > remaining {
394 break;
395 }
396
397 let record = match (*record_ptr).Header.MajorVersion {
398 2 => Some(UsnRecord::from_v2(self, &(*record_ptr).V2)),
399 3 => Some(UsnRecord::from_v3(self, &(*record_ptr).V3)),
400 _ => None,
401 };
402
403 (record_len, record)
404 };
405
406 if let Some(record) = record {
407 if record.reason
408 & (Ioctl::USN_REASON_RENAME_OLD_NAME
409 | Ioctl::USN_REASON_HARD_LINK_CHANGE
410 | Ioctl::USN_REASON_REPARSE_POINT_CHANGE)
411 != 0
412 {
413 if self.max_history_size > 0 && self.history.len() >= self.max_history_size {
414 self.history.pop_front();
415 }
416 self.history.push_back(record.clone());
417 }
418
419 results.push(record);
420 }
421
422 offset += record_len;
423 }
424
425 Ok(results)
426 }
427
428 pub fn match_rename(&self, record: &UsnRecord) -> Option<PathBuf> {
429 if record.reason & Ioctl::USN_REASON_RENAME_NEW_NAME == 0 {
430 return None;
431 }
432
433 self.history
434 .iter()
435 .find(|r| r.file_id == record.file_id && r.usn < record.usn)
436 .map(|r| r.path.clone())
437 }
438
439 pub fn trim_history(&mut self, min_usn: Option<i64>) {
440 match min_usn {
441 Some(usn) => self.history.retain(|r| r.usn > usn),
442 None => self.history.clear(),
443 }
444 }
445
446 pub fn get_next_usn(&self) -> i64 {
447 self.next_usn
448 }
449
450 pub fn get_reason_str(reason: u32) -> String {
451 let mut reason_str = String::new();
452
453 if reason & Ioctl::USN_REASON_BASIC_INFO_CHANGE != 0 {
454 reason_str.push_str("USN_REASON_BASIC_INFO_CHANGE ");
455 }
456 if reason & Ioctl::USN_REASON_CLOSE != 0 {
457 reason_str.push_str("USN_REASON_CLOSE ");
458 }
459 if reason & Ioctl::USN_REASON_COMPRESSION_CHANGE != 0 {
460 reason_str.push_str("USN_REASON_COMPRESSION_CHANGE ");
461 }
462 if reason & Ioctl::USN_REASON_DATA_EXTEND != 0 {
463 reason_str.push_str("USN_REASON_DATA_EXTEND ");
464 }
465 if reason & Ioctl::USN_REASON_DATA_OVERWRITE != 0 {
466 reason_str.push_str("USN_REASON_DATA_OVERWRITE ");
467 }
468 if reason & Ioctl::USN_REASON_DATA_TRUNCATION != 0 {
469 reason_str.push_str("USN_REASON_DATA_TRUNCATION ");
470 }
471 if reason & Ioctl::USN_REASON_DESIRED_STORAGE_CLASS_CHANGE != 0 {
472 reason_str.push_str("USN_REASON_DESIRED_STORAGE_CLASS_CHANGE ");
473 }
474 if reason & Ioctl::USN_REASON_EA_CHANGE != 0 {
475 reason_str.push_str("USN_REASON_EA_CHANGE ");
476 }
477 if reason & Ioctl::USN_REASON_ENCRYPTION_CHANGE != 0 {
478 reason_str.push_str("USN_REASON_ENCRYPTION_CHANGE ");
479 }
480 if reason & Ioctl::USN_REASON_FILE_CREATE != 0 {
481 reason_str.push_str("USN_REASON_FILE_CREATE ");
482 }
483 if reason & Ioctl::USN_REASON_FILE_DELETE != 0 {
484 reason_str.push_str("USN_REASON_FILE_DELETE ");
485 }
486 if reason & Ioctl::USN_REASON_HARD_LINK_CHANGE != 0 {
487 reason_str.push_str("USN_REASON_HARD_LINK_CHANGE ");
488 }
489 if reason & Ioctl::USN_REASON_INDEXABLE_CHANGE != 0 {
490 reason_str.push_str("USN_REASON_INDEXABLE_CHANGE ");
491 }
492 if reason & Ioctl::USN_REASON_INTEGRITY_CHANGE != 0 {
493 reason_str.push_str("USN_REASON_INTEGRITY_CHANGE ");
494 }
495 if reason & Ioctl::USN_REASON_NAMED_DATA_EXTEND != 0 {
496 reason_str.push_str("USN_REASON_NAMED_DATA_EXTEND ");
497 }
498 if reason & Ioctl::USN_REASON_NAMED_DATA_OVERWRITE != 0 {
499 reason_str.push_str("USN_REASON_NAMED_DATA_OVERWRITE ");
500 }
501 if reason & Ioctl::USN_REASON_NAMED_DATA_TRUNCATION != 0 {
502 reason_str.push_str("USN_REASON_NAMED_DATA_TRUNCATION ");
503 }
504 if reason & Ioctl::USN_REASON_OBJECT_ID_CHANGE != 0 {
505 reason_str.push_str("USN_REASON_OBJECT_ID_CHANGE ");
506 }
507 if reason & Ioctl::USN_REASON_RENAME_NEW_NAME != 0 {
508 reason_str.push_str("USN_REASON_RENAME_NEW_NAME ");
509 }
510 if reason & Ioctl::USN_REASON_RENAME_OLD_NAME != 0 {
511 reason_str.push_str("USN_REASON_RENAME_OLD_NAME ");
512 }
513 if reason & Ioctl::USN_REASON_REPARSE_POINT_CHANGE != 0 {
514 reason_str.push_str("USN_REASON_REPARSE_POINT_CHANGE ");
515 }
516 if reason & Ioctl::USN_REASON_SECURITY_CHANGE != 0 {
517 reason_str.push_str("USN_REASON_SECURITY_CHANGE ");
518 }
519 if reason & Ioctl::USN_REASON_STREAM_CHANGE != 0 {
520 reason_str.push_str("USN_REASON_STREAM_CHANGE ");
521 }
522 if reason & Ioctl::USN_REASON_TRANSACTED_CHANGE != 0 {
523 reason_str.push_str("USN_REASON_TRANSACTED_CHANGE ");
524 }
525
526 reason_str
527 }
528}
529
530impl Drop for Journal {
531 fn drop(&mut self) {
532 unsafe {
533 let _ = Foundation::CloseHandle(self.volume_handle);
534 let _ = Foundation::CloseHandle(self.port);
535 }
536 }
537}