@@ -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,22 @@ 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+ let module: HMODULE = GetModuleHandleW ( windows_sys:: w!( "kernel32.dll" ) ) ;
285+ if module. is_null ( ) {
286+ return DirectoryReaderKind :: Standard ;
287+ }
288+
289+ let func_ptr = GetProcAddress ( module, windows_sys:: s!( "ReadDirectoryChangesExW" ) ) ;
290+ if func_ptr. is_some ( ) {
291+ DirectoryReaderKind :: Extended
292+ } else {
293+ DirectoryReaderKind :: Standard
294+ }
295+ }
296+ }
297+
271298fn start_read (
272299 rd : & ReadData ,
273300 event_handler : Arc < Mutex < dyn EventHandler > > ,
@@ -304,33 +331,61 @@ fn start_read(
304331 let request = Box :: leak ( request) ;
305332 ( * overlapped) . hEvent = request as * mut _ as _ ;
306333
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 ( ) ) ;
334+ match rd. directory_reader {
335+ DirectoryReaderKind :: Extended => {
336+ // This is using an asynchronous call with a completion routine for receiving notifications
337+ // An I/O completion port would probably be more performant
338+ let ret = ReadDirectoryChangesExW (
339+ handle,
340+ request. buffer . as_mut_ptr ( ) as * mut c_void ,
341+ BUF_SIZE ,
342+ monitor_subdir,
343+ flags,
344+ & mut 0u32 as * mut u32 , // not used for async reqs
345+ overlapped,
346+ Some ( handle_extended_event) ,
347+ ReadDirectoryNotifyExtendedInformation ,
348+ ) ;
349+
350+ if ret == 0 {
351+ // error reading. retransmute request memory to allow drop.
352+ // Because of the error, ownership of the `overlapped` alloc was not passed
353+ // over to `ReadDirectoryChangesExW`.
354+ // So we can claim ownership back.
355+ let _overlapped = Box :: from_raw ( overlapped) ;
356+ let request = Box :: from_raw ( request) ;
357+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
358+ }
359+ }
360+ DirectoryReaderKind :: Standard => {
361+ // This is using an asynchronous call with a completion routine for receiving notifications
362+ // An I/O completion port would probably be more performant
363+ let ret = ReadDirectoryChangesW (
364+ handle,
365+ request. buffer . as_mut_ptr ( ) as * mut c_void ,
366+ BUF_SIZE ,
367+ monitor_subdir,
368+ flags,
369+ & mut 0u32 as * mut u32 , // not used for async reqs
370+ overlapped,
371+ Some ( handle_event) ,
372+ ) ;
373+
374+ if ret == 0 {
375+ // error reading. retransmute request memory to allow drop.
376+ // Because of the error, ownership of the `overlapped` alloc was not passed
377+ // over to `ReadDirectoryChangesW`.
378+ // So we can claim ownership back.
379+ let _overlapped = Box :: from_raw ( overlapped) ;
380+ let request = Box :: from_raw ( request) ;
381+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
382+ }
383+ }
329384 }
330385 }
331386}
332387
333- unsafe extern "system" fn handle_event (
388+ unsafe extern "system" fn handle_extended_event (
334389 error_code : u32 ,
335390 _bytes_written : u32 ,
336391 overlapped : * mut OVERLAPPED ,
@@ -359,7 +414,7 @@ unsafe extern "system" fn handle_event(
359414 _ => {
360415 // Some unidentified error occurred, log and unwatch the directory, then return.
361416 log:: error!(
362- "unknown error in ReadDirectoryChangesW for directory {}: {}" ,
417+ "unknown error in ReadDirectoryChangesExW for directory {}: {}" ,
363418 request. data. dir. display( ) ,
364419 error_code
365420 ) ;
@@ -377,12 +432,12 @@ unsafe extern "system" fn handle_event(
377432 request. action_tx ,
378433 ) ;
379434
380- // The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length
435+ // The FILE_NOTIFY_EXTENDED_INFORMATION struct has a variable length due to the variable length
381436 // string as its last member. Each struct contains an offset for getting the next entry in
382437 // the buffer.
383438 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 .
439+ // In Wine, FILE_NOTIFY_EXTENDED_INFORMATION structs are packed placed in the buffer;
440+ // they are aligned to 16bit (WCHAR) boundary instead of 32bit required by FILE_NOTIFY_EXTENDED_INFORMATION .
386441 // Hence, we need to use `read_unaligned` here to avoid UB.
387442 let mut cur_entry = ptr:: read_unaligned ( cur_offset as * const FILE_NOTIFY_EXTENDED_INFORMATION ) ;
388443 loop {
@@ -471,6 +526,138 @@ unsafe extern "system" fn handle_event(
471526 }
472527}
473528
529+ unsafe extern "system" fn handle_event (
530+ error_code : u32 ,
531+ _bytes_written : u32 ,
532+ overlapped : * mut OVERLAPPED ,
533+ ) {
534+ let overlapped: Box < OVERLAPPED > = Box :: from_raw ( overlapped) ;
535+ let request: Box < ReadDirectoryRequest > = Box :: from_raw ( overlapped. hEvent as * mut _ ) ;
536+
537+ match error_code {
538+ ERROR_OPERATION_ABORTED => {
539+ // received when dir is unwatched or watcher is shutdown; return and let overlapped/request get drop-cleaned
540+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
541+ return ;
542+ }
543+ ERROR_ACCESS_DENIED => {
544+ // This could happen when the watched directory is deleted or trashed, first check if it's the case.
545+ // If so, unwatch the directory and return, otherwise, continue to handle the event.
546+ if !request. data . dir . exists ( ) {
547+ request. unwatch ( ) ;
548+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
549+ return ;
550+ }
551+ }
552+ ERROR_SUCCESS => {
553+ // Success, continue to handle the event
554+ }
555+ _ => {
556+ // Some unidentified error occurred, log and unwatch the directory, then return.
557+ log:: error!(
558+ "unknown error in ReadDirectoryChangesW for directory {}: {}" ,
559+ request. data. dir. display( ) ,
560+ error_code
561+ ) ;
562+ request. unwatch ( ) ;
563+ ReleaseSemaphore ( request. data . complete_sem , 1 , ptr:: null_mut ( ) ) ;
564+ return ;
565+ }
566+ }
567+
568+ // Get the next request queued up as soon as possible
569+ start_read (
570+ & request. data ,
571+ request. event_handler . clone ( ) ,
572+ request. handle ,
573+ request. action_tx ,
574+ ) ;
575+
576+ // The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length
577+ // string as its last member. Each struct contains an offset for getting the next entry in
578+ // the buffer.
579+ let mut cur_offset: * const u8 = request. buffer . as_ptr ( ) ;
580+ // In Wine, FILE_NOTIFY_INFORMATION structs are packed placed in the buffer;
581+ // they are aligned to 16bit (WCHAR) boundary instead of 32bit required by FILE_NOTIFY_INFORMATION.
582+ // Hence, we need to use `read_unaligned` here to avoid UB.
583+ let mut cur_entry = ptr:: read_unaligned ( cur_offset as * const FILE_NOTIFY_INFORMATION ) ;
584+ loop {
585+ // filename length is size in bytes, so / 2
586+ let len = cur_entry. FileNameLength as usize / 2 ;
587+ let encoded_path: & [ u16 ] = slice:: from_raw_parts (
588+ cur_offset. offset ( std:: mem:: offset_of!( FILE_NOTIFY_INFORMATION , FileName ) as isize )
589+ as _ ,
590+ len,
591+ ) ;
592+ // prepend root to get a full path
593+ let path = request
594+ . data
595+ . dir
596+ . join ( PathBuf :: from ( OsString :: from_wide ( encoded_path) ) ) ;
597+
598+ // if we are watching a single file, ignore the event unless the path is exactly
599+ // the watched file
600+ let skip = match request. data . file {
601+ None => false ,
602+ Some ( ref watch_path) => * watch_path != path,
603+ } ;
604+
605+ if !skip {
606+ log:: trace!(
607+ "Event: path = `{}`, action = {:?}" ,
608+ path. display( ) ,
609+ cur_entry. Action
610+ ) ;
611+
612+ let newe = Event :: new ( EventKind :: Any ) . add_path ( path) ;
613+
614+ fn emit_event ( event_handler : & Mutex < dyn EventHandler > , res : Result < Event > ) {
615+ if let Ok ( mut guard) = event_handler. lock ( ) {
616+ let f: & mut dyn EventHandler = & mut * guard;
617+ f. handle_event ( res) ;
618+ }
619+ }
620+
621+ let event_handler = |res| emit_event ( & request. event_handler , res) ;
622+
623+ match cur_entry. Action {
624+ FILE_ACTION_RENAMED_OLD_NAME => {
625+ let kind = EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: From ) ) ;
626+ let ev = newe. set_kind ( kind) ;
627+ event_handler ( Ok ( ev) )
628+ }
629+ FILE_ACTION_RENAMED_NEW_NAME => {
630+ let kind = EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: To ) ) ;
631+ let ev = newe. set_kind ( kind) ;
632+ event_handler ( Ok ( ev) ) ;
633+ }
634+ FILE_ACTION_ADDED => {
635+ let kind = EventKind :: Create ( CreateKind :: Any ) ;
636+ let ev = newe. set_kind ( kind) ;
637+ event_handler ( Ok ( ev) ) ;
638+ }
639+ FILE_ACTION_REMOVED => {
640+ let kind = EventKind :: Remove ( RemoveKind :: Any ) ;
641+ let ev = newe. set_kind ( kind) ;
642+ event_handler ( Ok ( ev) ) ;
643+ }
644+ FILE_ACTION_MODIFIED => {
645+ let kind = EventKind :: Modify ( ModifyKind :: Any ) ;
646+ let ev = newe. set_kind ( kind) ;
647+ event_handler ( Ok ( ev) ) ;
648+ }
649+ _ => ( ) ,
650+ } ;
651+ }
652+
653+ if cur_entry. NextEntryOffset == 0 {
654+ break ;
655+ }
656+ cur_offset = cur_offset. offset ( cur_entry. NextEntryOffset as isize ) ;
657+ cur_entry = ptr:: read_unaligned ( cur_offset as * const FILE_NOTIFY_INFORMATION ) ;
658+ }
659+ }
660+
474661/// Watcher implementation based on ReadDirectoryChanges
475662#[ derive( Debug ) ]
476663pub struct ReadDirectoryChangesWatcher {
0 commit comments