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, 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 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) -> Result<Journal, std::io::Error> {
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) -> Result<Vec<UsnRecord>, std::io::Error> {
318 self.read_sized::<4096>()
319 }
320
321 pub fn read_sized<const BUFFER_SIZE: usize>(
322 &mut self,
323 ) -> Result<Vec<UsnRecord>, std::io::Error> {
324 let mut results = Vec::<UsnRecord>::new();
325
326 let mut read = Ioctl::READ_USN_JOURNAL_DATA_V1 {
327 StartUsn: self.next_usn,
328 ReasonMask: self.reason_mask,
329 ReturnOnlyOnClose: 0,
330 Timeout: 0,
331 BytesToWaitFor: 0,
332 UsnJournalID: self.journal.UsnJournalID,
333 MinMajorVersion: 2,
334 MaxMajorVersion: u16::min(3, self.journal.MaxSupportedMajorVersion),
335 };
336
337 let mut buffer = AlignedBuffer::<BUFFER_SIZE>([0u8; BUFFER_SIZE]);
338
339 let mut bytes_returned = 0;
340 let mut overlapped = IO::OVERLAPPED {
341 ..Default::default()
342 };
343
344 unsafe {
345 let result = IO::DeviceIoControl(
346 self.volume_handle,
347 Ioctl::FSCTL_READ_USN_JOURNAL,
348 Some(&mut read as *mut _ as *mut c_void),
349 size_of::<Ioctl::READ_USN_JOURNAL_DATA_V1>() as u32,
350 Some(&mut buffer as *mut _ as *mut c_void),
351 BUFFER_SIZE as u32,
352 Some(&mut bytes_returned),
353 Some(&mut overlapped),
354 );
355
356 match result {
357 Ok(_) => {}
358 Err(err) => {
359 if err.code() == ERROR_IO_PENDING.to_hresult() {
360 let mut key = 0usize;
361 let mut completed = std::ptr::null_mut();
362 GetQueuedCompletionStatus(
363 self.port,
364 &mut bytes_returned,
365 &mut key,
366 &mut completed,
367 INFINITE,
368 )?;
369 } else {
370 return Err(err.into());
371 }
372 }
373 }
374 }
375
376 let next_usn = i64::from_le_bytes(buffer.0[0..8].try_into().unwrap());
377 if next_usn == 0 || next_usn < self.next_usn {
378 return Ok(results);
379 } else {
380 self.next_usn = next_usn;
381 }
382
383 let mut offset = 8; while offset < bytes_returned {
385 let (record_len, record) = unsafe {
386 let record_ptr = std::mem::transmute::<*const u8, *const Ioctl::USN_RECORD_UNION>(
387 buffer.0[offset as usize..].as_ptr(),
388 );
389
390 let record_len = (*record_ptr).Header.RecordLength;
391 if record_len == 0 {
392 break;
393 }
394
395 let record = match (*record_ptr).Header.MajorVersion {
396 2 => Some(UsnRecord::from_v2(self, &(*record_ptr).V2)),
397 3 => Some(UsnRecord::from_v3(self, &(*record_ptr).V3)),
398 _ => None,
399 };
400
401 (record_len, record)
402 };
403
404 if let Some(record) = record {
405 if record.reason
406 & (Ioctl::USN_REASON_RENAME_OLD_NAME
407 | Ioctl::USN_REASON_HARD_LINK_CHANGE
408 | Ioctl::USN_REASON_REPARSE_POINT_CHANGE)
409 != 0
410 {
411 if self.max_history_size > 0 && self.history.len() >= self.max_history_size {
412 self.history.pop_front();
413 }
414 self.history.push_back(record.clone());
415 }
416
417 results.push(record);
418 }
419
420 offset += record_len;
421 }
422
423 Ok(results)
424 }
425
426 pub fn match_rename(&self, record: &UsnRecord) -> Option<PathBuf> {
427 if record.reason & Ioctl::USN_REASON_RENAME_NEW_NAME == 0 {
428 return None;
429 }
430
431 self.history
432 .iter()
433 .find(|r| r.file_id == record.file_id && r.usn < record.usn)
434 .map(|r| r.path.clone())
435 }
436
437 pub fn trim_history(&mut self, min_usn: Option<i64>) {
438 match min_usn {
439 Some(usn) => self.history.retain(|r| r.usn > usn),
440 None => self.history.clear(),
441 }
442 }
443
444 pub fn get_next_usn(&self) -> i64 {
445 self.next_usn
446 }
447
448 pub fn get_reason_str(reason: u32) -> String {
449 let mut reason_str = String::new();
450
451 if reason & Ioctl::USN_REASON_BASIC_INFO_CHANGE != 0 {
452 reason_str.push_str("USN_REASON_BASIC_INFO_CHANGE ");
453 }
454 if reason & Ioctl::USN_REASON_CLOSE != 0 {
455 reason_str.push_str("USN_REASON_CLOSE ");
456 }
457 if reason & Ioctl::USN_REASON_COMPRESSION_CHANGE != 0 {
458 reason_str.push_str("USN_REASON_COMPRESSION_CHANGE ");
459 }
460 if reason & Ioctl::USN_REASON_DATA_EXTEND != 0 {
461 reason_str.push_str("USN_REASON_DATA_EXTEND ");
462 }
463 if reason & Ioctl::USN_REASON_DATA_OVERWRITE != 0 {
464 reason_str.push_str("USN_REASON_DATA_OVERWRITE ");
465 }
466 if reason & Ioctl::USN_REASON_DATA_TRUNCATION != 0 {
467 reason_str.push_str("USN_REASON_DATA_TRUNCATION ");
468 }
469 if reason & Ioctl::USN_REASON_DESIRED_STORAGE_CLASS_CHANGE != 0 {
470 reason_str.push_str("USN_REASON_DESIRED_STORAGE_CLASS_CHANGE ");
471 }
472 if reason & Ioctl::USN_REASON_EA_CHANGE != 0 {
473 reason_str.push_str("USN_REASON_EA_CHANGE ");
474 }
475 if reason & Ioctl::USN_REASON_ENCRYPTION_CHANGE != 0 {
476 reason_str.push_str("USN_REASON_ENCRYPTION_CHANGE ");
477 }
478 if reason & Ioctl::USN_REASON_FILE_CREATE != 0 {
479 reason_str.push_str("USN_REASON_FILE_CREATE ");
480 }
481 if reason & Ioctl::USN_REASON_FILE_DELETE != 0 {
482 reason_str.push_str("USN_REASON_FILE_DELETE ");
483 }
484 if reason & Ioctl::USN_REASON_HARD_LINK_CHANGE != 0 {
485 reason_str.push_str("USN_REASON_HARD_LINK_CHANGE ");
486 }
487 if reason & Ioctl::USN_REASON_INDEXABLE_CHANGE != 0 {
488 reason_str.push_str("USN_REASON_INDEXABLE_CHANGE ");
489 }
490 if reason & Ioctl::USN_REASON_INTEGRITY_CHANGE != 0 {
491 reason_str.push_str("USN_REASON_INTEGRITY_CHANGE ");
492 }
493 if reason & Ioctl::USN_REASON_NAMED_DATA_EXTEND != 0 {
494 reason_str.push_str("USN_REASON_NAMED_DATA_EXTEND ");
495 }
496 if reason & Ioctl::USN_REASON_NAMED_DATA_OVERWRITE != 0 {
497 reason_str.push_str("USN_REASON_NAMED_DATA_OVERWRITE ");
498 }
499 if reason & Ioctl::USN_REASON_NAMED_DATA_TRUNCATION != 0 {
500 reason_str.push_str("USN_REASON_NAMED_DATA_TRUNCATION ");
501 }
502 if reason & Ioctl::USN_REASON_OBJECT_ID_CHANGE != 0 {
503 reason_str.push_str("USN_REASON_OBJECT_ID_CHANGE ");
504 }
505 if reason & Ioctl::USN_REASON_RENAME_NEW_NAME != 0 {
506 reason_str.push_str("USN_REASON_RENAME_NEW_NAME ");
507 }
508 if reason & Ioctl::USN_REASON_RENAME_OLD_NAME != 0 {
509 reason_str.push_str("USN_REASON_RENAME_OLD_NAME ");
510 }
511 if reason & Ioctl::USN_REASON_REPARSE_POINT_CHANGE != 0 {
512 reason_str.push_str("USN_REASON_REPARSE_POINT_CHANGE ");
513 }
514 if reason & Ioctl::USN_REASON_SECURITY_CHANGE != 0 {
515 reason_str.push_str("USN_REASON_SECURITY_CHANGE ");
516 }
517 if reason & Ioctl::USN_REASON_STREAM_CHANGE != 0 {
518 reason_str.push_str("USN_REASON_STREAM_CHANGE ");
519 }
520 if reason & Ioctl::USN_REASON_TRANSACTED_CHANGE != 0 {
521 reason_str.push_str("USN_REASON_TRANSACTED_CHANGE ");
522 }
523
524 reason_str
525 }
526}
527
528impl Drop for Journal {
529 fn drop(&mut self) {
530 unsafe {
531 let _ = Foundation::CloseHandle(self.volume_handle);
532 let _ = Foundation::CloseHandle(self.port);
533 }
534 }
535}