@@ -20,30 +20,38 @@ use std::slice;
2020use std:: sync:: { Arc , Mutex } ;
2121use std:: thread;
2222use windows_sys:: Win32 :: Foundation :: {
23- CloseHandle , ERROR_ACCESS_DENIED , ERROR_OPERATION_ABORTED , ERROR_SUCCESS , HANDLE ,
23+ CloseHandle , ERROR_ACCESS_DENIED , ERROR_OPERATION_ABORTED , ERROR_SUCCESS , HANDLE , HMODULE ,
2424 INVALID_HANDLE_VALUE , WAIT_OBJECT_0 ,
2525} ;
2626use windows_sys:: Win32 :: Storage :: FileSystem :: {
27- CreateFileW , ReadDirectoryChangesExW , ReadDirectoryNotifyExtendedInformation ,
28- FILE_ACTION_ADDED , FILE_ACTION_MODIFIED , FILE_ACTION_REMOVED , FILE_ACTION_RENAMED_NEW_NAME ,
29- FILE_ACTION_RENAMED_OLD_NAME , FILE_ATTRIBUTE_DIRECTORY , FILE_FLAG_BACKUP_SEMANTICS ,
30- FILE_FLAG_OVERLAPPED , FILE_LIST_DIRECTORY , FILE_NOTIFY_CHANGE_ATTRIBUTES ,
31- FILE_NOTIFY_CHANGE_CREATION , FILE_NOTIFY_CHANGE_DIR_NAME , FILE_NOTIFY_CHANGE_FILE_NAME ,
32- FILE_NOTIFY_CHANGE_LAST_WRITE , FILE_NOTIFY_CHANGE_SECURITY , FILE_NOTIFY_CHANGE_SIZE ,
33- FILE_NOTIFY_EXTENDED_INFORMATION , FILE_SHARE_DELETE , FILE_SHARE_READ , FILE_SHARE_WRITE ,
34- OPEN_EXISTING ,
27+ CreateFileW , ReadDirectoryChangesExW , ReadDirectoryChangesW ,
28+ ReadDirectoryNotifyExtendedInformation , FILE_ACTION_ADDED , FILE_ACTION_MODIFIED ,
29+ FILE_ACTION_REMOVED , FILE_ACTION_RENAMED_NEW_NAME , FILE_ACTION_RENAMED_OLD_NAME ,
30+ FILE_ATTRIBUTE_DIRECTORY , FILE_FLAG_BACKUP_SEMANTICS , FILE_FLAG_OVERLAPPED ,
31+ FILE_LIST_DIRECTORY , FILE_NOTIFY_CHANGE_ATTRIBUTES , FILE_NOTIFY_CHANGE_CREATION ,
32+ FILE_NOTIFY_CHANGE_DIR_NAME , FILE_NOTIFY_CHANGE_FILE_NAME , FILE_NOTIFY_CHANGE_LAST_WRITE ,
33+ FILE_NOTIFY_CHANGE_SECURITY , FILE_NOTIFY_CHANGE_SIZE , FILE_NOTIFY_EXTENDED_INFORMATION ,
34+ FILE_NOTIFY_INFORMATION , FILE_SHARE_DELETE , FILE_SHARE_READ , FILE_SHARE_WRITE , OPEN_EXISTING ,
3535} ;
36+ use windows_sys:: Win32 :: System :: LibraryLoader :: { GetModuleHandleW , GetProcAddress } ;
3637use windows_sys:: Win32 :: System :: Threading :: {
3738 CreateSemaphoreW , ReleaseSemaphore , WaitForSingleObjectEx , INFINITE ,
3839} ;
3940use windows_sys:: Win32 :: System :: IO :: { CancelIo , OVERLAPPED } ;
4041
4142const BUF_SIZE : u32 = 16384 ;
4243
44+ #[ derive( Clone , Copy ) ]
45+ enum DirectoryReaderKind {
46+ Standard ,
47+ Extended ,
48+ }
49+
4350#[ derive( Clone ) ]
4451struct ReadData {
4552 dir : PathBuf , // directory that is being watched
4653 file : Option < PathBuf > , // if a file is being watched, this is its full path
54+ directory_reader : DirectoryReaderKind ,
4755 complete_sem : HANDLE ,
4856 is_recursive : bool ,
4957}
@@ -87,6 +95,7 @@ struct ReadDirectoryChangesServer {
8795 meta_tx : Sender < MetaEvent > ,
8896 cmd_tx : Sender < Result < PathBuf > > ,
8997 watches : HashMap < PathBuf , WatchState > ,
98+ reader_kind : DirectoryReaderKind ,
9099 wakeup_sem : HANDLE ,
91100}
92101
@@ -113,6 +122,7 @@ impl ReadDirectoryChangesServer {
113122 meta_tx,
114123 cmd_tx,
115124 watches : HashMap :: new ( ) ,
125+ reader_kind : available_directory_reader_kind ( ) ,
116126 wakeup_sem,
117127 } ;
118128 server. run ( ) ;
@@ -229,6 +239,7 @@ impl ReadDirectoryChangesServer {
229239 let rd = ReadData {
230240 dir : dir_target,
231241 file : wf,
242+ directory_reader : self . reader_kind ,
232243 complete_sem : semaphore,
233244 is_recursive,
234245 } ;
@@ -268,6 +279,24 @@ fn stop_watch(ws: &WatchState, meta_tx: &Sender<MetaEvent>) {
268279 let _ = meta_tx. send ( MetaEvent :: SingleWatchComplete ) ;
269280}
270281
282+ fn available_directory_reader_kind ( ) -> DirectoryReaderKind {
283+ unsafe {
284+ // Get handle to kernel32.dll
285+ let module: HMODULE = GetModuleHandleW ( windows_sys:: w!( "kernel32.dll" ) ) ;
286+ if module. is_null ( ) {
287+ return DirectoryReaderKind :: Standard ;
288+ }
289+
290+ // Check if function exists
291+ let func_ptr = GetProcAddress ( module, windows_sys:: s!( "ReadDirectoryChangesExW" ) ) ;
292+ if func_ptr. is_some ( ) {
293+ DirectoryReaderKind :: Extended
294+ } else {
295+ DirectoryReaderKind :: Standard
296+ }
297+ }
298+ }
299+
271300fn start_read (
272301 rd : & ReadData ,
273302 event_handler : Arc < Mutex < dyn EventHandler > > ,
@@ -304,33 +333,61 @@ fn start_read(
304333 let request = Box :: leak ( request) ;
305334 ( * overlapped) . hEvent = request as * mut _ as _ ;
306335
307- // This is using an asynchronous call with a completion routine for receiving notifications
308- // An I/O completion port would probably be more performant
309- let ret = ReadDirectoryChangesExW (
310- handle,
311- request. buffer . as_mut_ptr ( ) as * mut c_void ,
312- BUF_SIZE ,
313- monitor_subdir,
314- flags,
315- & mut 0u32 as * mut u32 , // not used for async reqs
316- overlapped,
317- Some ( handle_event) ,
318- ReadDirectoryNotifyExtendedInformation ,
319- ) ;
320-
321- if ret == 0 {
322- // error reading. retransmute request memory to allow drop.
323- // Because of the error, ownership of the `overlapped` alloc was not passed
324- // over to `ReadDirectoryChangesW`.
325- // So we can claim ownership back.
326- let _overlapped = Box :: from_raw ( overlapped) ;
327- let request = Box :: from_raw ( request) ;
328- ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
336+ match rd. directory_reader {
337+ DirectoryReaderKind :: Extended => {
338+ // This is using an asynchronous call with a completion routine for receiving notifications
339+ // An I/O completion port would probably be more performant
340+ let ret = ReadDirectoryChangesExW (
341+ handle,
342+ request. buffer . as_mut_ptr ( ) as * mut c_void ,
343+ BUF_SIZE ,
344+ monitor_subdir,
345+ flags,
346+ & mut 0u32 as * mut u32 , // not used for async reqs
347+ overlapped,
348+ Some ( handle_extended_event) ,
349+ ReadDirectoryNotifyExtendedInformation ,
350+ ) ;
351+
352+ if ret == 0 {
353+ // error reading. retransmute request memory to allow drop.
354+ // Because of the error, ownership of the `overlapped` alloc was not passed
355+ // over to `ReadDirectoryChangesExW`.
356+ // So we can claim ownership back.
357+ let _overlapped = Box :: from_raw ( overlapped) ;
358+ let request = Box :: from_raw ( request) ;
359+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
360+ }
361+ }
362+ DirectoryReaderKind :: Standard => {
363+ // This is using an asynchronous call with a completion routine for receiving notifications
364+ // An I/O completion port would probably be more performant
365+ let ret = ReadDirectoryChangesW (
366+ handle,
367+ request. buffer . as_mut_ptr ( ) as * mut c_void ,
368+ BUF_SIZE ,
369+ monitor_subdir,
370+ flags,
371+ & mut 0u32 as * mut u32 , // not used for async reqs
372+ overlapped,
373+ Some ( handle_event) ,
374+ ) ;
375+
376+ if ret == 0 {
377+ // error reading. retransmute request memory to allow drop.
378+ // Because of the error, ownership of the `overlapped` alloc was not passed
379+ // over to `ReadDirectoryChangesW`.
380+ // So we can claim ownership back.
381+ let _overlapped = Box :: from_raw ( overlapped) ;
382+ let request = Box :: from_raw ( request) ;
383+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
384+ }
385+ }
329386 }
330387 }
331388}
332389
333- unsafe extern "system" fn handle_event (
390+ unsafe extern "system" fn handle_extended_event (
334391 error_code : u32 ,
335392 _bytes_written : u32 ,
336393 overlapped : * mut OVERLAPPED ,
@@ -359,7 +416,7 @@ unsafe extern "system" fn handle_event(
359416 _ => {
360417 // Some unidentified error occurred, log and unwatch the directory, then return.
361418 log:: error!(
362- "unknown error in ReadDirectoryChangesW for directory {}: {}" ,
419+ "unknown error in ReadDirectoryChangesExW for directory {}: {}" ,
363420 request. data. dir. display( ) ,
364421 error_code
365422 ) ;
@@ -377,12 +434,12 @@ unsafe extern "system" fn handle_event(
377434 request. action_tx ,
378435 ) ;
379436
380- // The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length
437+ // The FILE_NOTIFY_EXTENDED_INFORMATION struct has a variable length due to the variable length
381438 // string as its last member. Each struct contains an offset for getting the next entry in
382439 // the buffer.
383440 let mut cur_offset: * const u8 = request. buffer . as_ptr ( ) ;
384- // In Wine, FILE_NOTIFY_INFORMATION structs are packed placed in the buffer;
385- // they are aligned to 16bit (WCHAR) boundary instead of 32bit required by FILE_NOTIFY_INFORMATION .
441+ // In Wine, FILE_NOTIFY_EXTENDED_INFORMATION structs are packed placed in the buffer;
442+ // they are aligned to 16bit (WCHAR) boundary instead of 32bit required by FILE_NOTIFY_EXTENDED_INFORMATION .
386443 // Hence, we need to use `read_unaligned` here to avoid UB.
387444 let mut cur_entry = ptr:: read_unaligned ( cur_offset as * const FILE_NOTIFY_EXTENDED_INFORMATION ) ;
388445 loop {
@@ -471,6 +528,138 @@ unsafe extern "system" fn handle_event(
471528 }
472529}
473530
531+ unsafe extern "system" fn handle_event (
532+ error_code : u32 ,
533+ _bytes_written : u32 ,
534+ overlapped : * mut OVERLAPPED ,
535+ ) {
536+ let overlapped: Box < OVERLAPPED > = Box :: from_raw ( overlapped) ;
537+ let request: Box < ReadDirectoryRequest > = Box :: from_raw ( overlapped. hEvent as * mut _ ) ;
538+
539+ match error_code {
540+ ERROR_OPERATION_ABORTED => {
541+ // received when dir is unwatched or watcher is shutdown; return and let overlapped/request get drop-cleaned
542+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
543+ return ;
544+ }
545+ ERROR_ACCESS_DENIED => {
546+ // This could happen when the watched directory is deleted or trashed, first check if it's the case.
547+ // If so, unwatch the directory and return, otherwise, continue to handle the event.
548+ if !request. data . dir . exists ( ) {
549+ request. unwatch ( ) ;
550+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
551+ return ;
552+ }
553+ }
554+ ERROR_SUCCESS => {
555+ // Success, continue to handle the event
556+ }
557+ _ => {
558+ // Some unidentified error occurred, log and unwatch the directory, then return.
559+ log:: error!(
560+ "unknown error in ReadDirectoryChangesW for directory {}: {}" ,
561+ request. data. dir. display( ) ,
562+ error_code
563+ ) ;
564+ request. unwatch ( ) ;
565+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
566+ return ;
567+ }
568+ }
569+
570+ // Get the next request queued up as soon as possible
571+ start_read (
572+ & request. data ,
573+ request. event_handler . clone ( ) ,
574+ request. handle ,
575+ request. action_tx ,
576+ ) ;
577+
578+ // The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length
579+ // string as its last member. Each struct contains an offset for getting the next entry in
580+ // the buffer.
581+ let mut cur_offset: * const u8 = request. buffer . as_ptr ( ) ;
582+ // In Wine, FILE_NOTIFY_INFORMATION structs are packed placed in the buffer;
583+ // they are aligned to 16bit (WCHAR) boundary instead of 32bit required by FILE_NOTIFY_INFORMATION.
584+ // Hence, we need to use `read_unaligned` here to avoid UB.
585+ let mut cur_entry = ptr:: read_unaligned ( cur_offset as * const FILE_NOTIFY_INFORMATION ) ;
586+ loop {
587+ // filename length is size in bytes, so / 2
588+ let len = cur_entry. FileNameLength as usize / 2 ;
589+ let encoded_path: & [ u16 ] = slice:: from_raw_parts (
590+ cur_offset. offset ( std:: mem:: offset_of!( FILE_NOTIFY_INFORMATION , FileName ) as isize )
591+ as _ ,
592+ len,
593+ ) ;
594+ // prepend root to get a full path
595+ let path = request
596+ . data
597+ . dir
598+ . join ( PathBuf :: from ( OsString :: from_wide ( encoded_path) ) ) ;
599+
600+ // if we are watching a single file, ignore the event unless the path is exactly
601+ // the watched file
602+ let skip = match request. data . file {
603+ None => false ,
604+ Some ( ref watch_path) => * watch_path != path,
605+ } ;
606+
607+ if !skip {
608+ log:: trace!(
609+ "Event: path = `{}`, action = {:?}" ,
610+ path. display( ) ,
611+ cur_entry. Action
612+ ) ;
613+
614+ let newe = Event :: new ( EventKind :: Any ) . add_path ( path) ;
615+
616+ fn emit_event ( event_handler : & Mutex < dyn EventHandler > , res : Result < Event > ) {
617+ if let Ok ( mut guard) = event_handler. lock ( ) {
618+ let f: & mut dyn EventHandler = & mut * guard;
619+ f. handle_event ( res) ;
620+ }
621+ }
622+
623+ let event_handler = |res| emit_event ( & request. event_handler , res) ;
624+
625+ match cur_entry. Action {
626+ FILE_ACTION_RENAMED_OLD_NAME => {
627+ let kind = EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: From ) ) ;
628+ let ev = newe. set_kind ( kind) ;
629+ event_handler ( Ok ( ev) )
630+ }
631+ FILE_ACTION_RENAMED_NEW_NAME => {
632+ let kind = EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: To ) ) ;
633+ let ev = newe. set_kind ( kind) ;
634+ event_handler ( Ok ( ev) ) ;
635+ }
636+ FILE_ACTION_ADDED => {
637+ let kind = EventKind :: Create ( CreateKind :: Any ) ;
638+ let ev = newe. set_kind ( kind) ;
639+ event_handler ( Ok ( ev) ) ;
640+ }
641+ FILE_ACTION_REMOVED => {
642+ let kind = EventKind :: Remove ( RemoveKind :: Any ) ;
643+ let ev = newe. set_kind ( kind) ;
644+ event_handler ( Ok ( ev) ) ;
645+ }
646+ FILE_ACTION_MODIFIED => {
647+ let kind = EventKind :: Modify ( ModifyKind :: Any ) ;
648+ let ev = newe. set_kind ( kind) ;
649+ event_handler ( Ok ( ev) ) ;
650+ }
651+ _ => ( ) ,
652+ } ;
653+ }
654+
655+ if cur_entry. NextEntryOffset == 0 {
656+ break ;
657+ }
658+ cur_offset = cur_offset. offset ( cur_entry. NextEntryOffset as isize ) ;
659+ cur_entry = ptr:: read_unaligned ( cur_offset as * const FILE_NOTIFY_INFORMATION ) ;
660+ }
661+ }
662+
474663/// Watcher implementation based on ReadDirectoryChanges
475664#[ derive( Debug ) ]
476665pub struct ReadDirectoryChangesWatcher {
0 commit comments