[coop] Temporarily restore MonoThreadInfo when TLS destructor runs. Fixes #43099
[mono.git] / mcs / class / referencesource / System / services / io / system / io / FileSystemWatcher.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="FileSystemWatcher.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.IO {
8     using System.Threading;
9     using System.Runtime.InteropServices;
10     using System.Diagnostics;
11     using System.Diagnostics.CodeAnalysis;
12     using System.ComponentModel;
13     using System.ComponentModel.Design;
14     using Microsoft.Win32;
15     using Microsoft.Win32.SafeHandles;
16     using System.Security.Permissions;
17     using System.Security;
18     using System.Globalization;
19     using System.Runtime.Versioning;
20
21     /// <devdoc>
22     ///    <para>Listens to the system directory change notifications and
23     ///       raises events when a directory or file within a directory changes.</para>
24     /// </devdoc>
25     [
26     DefaultEvent("Changed"),
27     // Disabling partial trust scenarios
28     PermissionSet(SecurityAction.LinkDemand, Name="FullTrust"),
29     PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust"),
30     IODescription(SR.FileSystemWatcherDesc)
31     ]
32     public class FileSystemWatcher : Component, ISupportInitialize {
33         /// <devdoc>
34         ///     Private instance variables
35         /// </devdoc>
36         // Directory being monitored
37         private string directory;
38
39         // Filter for name matching
40         private string filter;
41
42         // Unmanaged handle to monitored directory
43         private SafeFileHandle directoryHandle;
44
45         // The watch filter for the API call.
46         private const NotifyFilters defaultNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
47         private NotifyFilters notifyFilters = defaultNotifyFilters;
48
49         // Flag to watch subtree of this directory
50         private bool includeSubdirectories = false;
51
52         // Flag to note whether we are attached to the thread pool and responding to changes
53         private bool enabled = false;
54
55         // Are we in init?
56         private bool initializing = false;
57
58         // Buffer size
59         private int internalBufferSize = 8192;
60                 
61         // Used for synchronization
62         private WaitForChangedResult changedResult;
63         private bool isChanged = false;
64         private ISynchronizeInvoke synchronizingObject;
65         private bool readGranted;
66         private bool disposed;
67         // Current "session" ID to ignore old events whenever we stop then 
68         // restart.
69         private int currentSession;
70
71         // Event handlers
72         private FileSystemEventHandler onChangedHandler = null;
73         private FileSystemEventHandler onCreatedHandler = null;
74         private FileSystemEventHandler onDeletedHandler = null;
75         private RenamedEventHandler onRenamedHandler = null;
76         private ErrorEventHandler onErrorHandler = null;
77
78         // Thread gate holder and constats
79         private bool stopListening = false;        
80
81         // Used for async method
82         private bool runOnce = false;
83
84         // To validate the input for "path"
85         private static readonly char[] wildcards = new char[] { '?', '*' };
86
87         private static int notifyFiltersValidMask;        
88
89         // Additional state information to pass to callback.  Note that we
90         // never return this object to users, but we do pass state in it.
91         private sealed class FSWAsyncResult : IAsyncResult
92         {
93             internal int session;
94             internal byte[] buffer;
95
96             public bool IsCompleted                { get { throw new NotImplementedException(); } }
97             public WaitHandle AsyncWaitHandle    { get { throw new NotImplementedException(); } }            
98             public Object AsyncState            { get { throw new NotImplementedException(); } }                
99             public bool CompletedSynchronously    { get { throw new NotImplementedException(); } }                
100         }
101
102         static FileSystemWatcher() {
103             notifyFiltersValidMask = 0;
104             foreach (int enumValue in Enum.GetValues(typeof(NotifyFilters)))
105                 notifyFiltersValidMask |= enumValue;
106         }
107
108         /// <devdoc>
109         /// <para>Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class.</para>
110         /// </devdoc>
111         public FileSystemWatcher() {
112             this.directory = String.Empty;
113             this.filter = "*.*";
114         }
115
116         /// <devdoc>
117         ///    <para>
118         ///       Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class,
119         ///       given the specified directory to monitor.
120         ///    </para>
121         /// </devdoc>
122         [ResourceExposure(ResourceScope.Machine)]
123         [ResourceConsumption(ResourceScope.Machine)]
124         public FileSystemWatcher(string path) : this(path, "*.*") {
125         }
126
127
128         /// <devdoc>
129         ///    <para>
130         ///       Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class,
131         ///       given the specified directory and type of files to monitor.
132         ///    </para>
133         /// </devdoc>
134         [ResourceExposure(ResourceScope.Machine)]
135         [ResourceConsumption(ResourceScope.Machine)]
136         public FileSystemWatcher(string path, string filter) {
137             if (path == null)
138                 throw new ArgumentNullException("path");
139
140             if (filter == null)
141                 throw new ArgumentNullException("filter");
142             
143             // Early check for directory parameter so that an exception can be thrown as early as possible.
144             if (path.Length == 0 || !Directory.Exists(path))
145                 throw new ArgumentException(SR.GetString(SR.InvalidDirName, path));            
146
147             this.directory = path;
148             this.filter = filter;
149         }
150
151         /// <devdoc>
152         ///    <para>
153         ///       Gets or sets the type of changes to watch for.
154         ///    </para>
155         /// </devdoc>
156         [
157         DefaultValue(defaultNotifyFilters),
158         IODescription(SR.FSW_ChangedFilter)
159         ]
160         public NotifyFilters NotifyFilter {
161             get {
162                 return notifyFilters;
163             }
164             set {
165                 if (((int) value & ~notifyFiltersValidMask) != 0)
166                     throw new InvalidEnumArgumentException("value", (int)value, typeof(NotifyFilters));                                                                                
167
168                 if (notifyFilters != value) {
169                     notifyFilters = value;
170
171                     Restart();
172                 }
173             }
174         }
175
176         /// <devdoc>
177         ///    <para>Gets or sets a value indicating whether the component is enabled.</para>
178         /// </devdoc>
179         [
180         DefaultValue(false),
181         IODescription(SR.FSW_Enabled)
182         ]
183         public bool EnableRaisingEvents {
184             get {
185                 return enabled;
186             }
187             set {
188
189                 if (enabled == value) {
190                     return;
191                 }
192
193                 enabled = value;
194
195                 if (!IsSuspended()) {
196                     if (enabled) {
197                         StartRaisingEvents();
198                     }
199                     else {
200                         StopRaisingEvents();
201                     }
202                 }
203             }
204         }
205
206         /// <devdoc>
207         ///    <para>Gets or sets the filter string, used to determine what files are monitored in a directory.</para>
208         /// </devdoc>
209         [
210         DefaultValue("*.*"),
211         IODescription(SR.FSW_Filter),
212         TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign),
213         SettingsBindable(true),        
214         ]
215         public string Filter {
216             get {
217                 return filter;
218             }
219             set {                
220                 if (String.IsNullOrEmpty(value)) {
221                     value = "*.*";
222                 }
223                 if (String.Compare(filter, value, StringComparison.OrdinalIgnoreCase) != 0) {
224                     filter = value;
225                 }
226             }
227         }
228
229         /// <devdoc>
230         ///    <para>
231         ///       Gets or sets a
232         ///       value indicating whether subdirectories within the specified path should be monitored.
233         ///    </para>
234         /// </devdoc>
235         [
236         DefaultValue(false),
237         IODescription(SR.FSW_IncludeSubdirectories)
238         ]
239         public bool IncludeSubdirectories {
240             get {
241                 return includeSubdirectories;
242             }
243             set {
244                 if (includeSubdirectories != value) {
245                     includeSubdirectories = value;
246
247                     Restart();
248                 }
249             }
250         }
251
252         /// <devdoc>
253         ///    <para>Gets or
254         ///       sets the size of the internal buffer.</para>
255         /// </devdoc>
256         [
257         Browsable(false),
258         DefaultValue(8192)
259         ]
260         public int InternalBufferSize {
261             get {
262                 return internalBufferSize;
263             }
264             set {
265                 if (internalBufferSize != value) {
266                     if (value < 4096) {
267                         value = 4096;
268                     }
269
270                     internalBufferSize = value;
271
272                     Restart();
273                 }
274             }
275         }
276
277         private bool IsHandleInvalid {
278             get {
279                 return (directoryHandle == null || directoryHandle.IsInvalid);
280             }
281         }
282         
283         /// <devdoc>
284         ///    <para>Gets or sets the path of the directory to watch.</para>
285         /// </devdoc>
286         [
287         DefaultValue(""),
288         IODescription(SR.FSW_Path),
289         Editor("System.Diagnostics.Design.FSWPathEditor, " + AssemblyRef.SystemDesign, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),        
290         TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign),
291         SettingsBindable(true)
292         ]
293         public string Path {
294             [ResourceExposure(ResourceScope.Machine)]
295             [ResourceConsumption(ResourceScope.Machine)]
296             get {
297                 return directory;
298             }
299             [ResourceExposure(ResourceScope.Machine)]
300             [ResourceConsumption(ResourceScope.Machine)]
301             set {
302                 value = (value == null) ? string.Empty : value;
303                 if (String.Compare(directory, value, StringComparison.OrdinalIgnoreCase) != 0) {
304                     if (DesignMode) {
305                         // Don't check the path if in design mode, try to do simple syntax check                 
306                         if (value.IndexOfAny(FileSystemWatcher.wildcards) != -1 || value.IndexOfAny(System.IO.Path.GetInvalidPathChars()) != -1) {
307                             throw new ArgumentException(SR.GetString(SR.InvalidDirName, value));
308                         }
309                     }
310                     else {
311                         if (!Directory.Exists(value))                             
312                             throw new ArgumentException(SR.GetString(SR.InvalidDirName, value));                        
313                     }
314                     directory = value;
315                     readGranted = false;
316                     Restart();
317                 }
318             }
319         }
320
321         /// <internalonly/>
322         /// <devdoc>
323         /// </devdoc>
324         [Browsable(false)]
325         public override ISite Site {
326             get {
327                 return base.Site;
328             }
329             set {
330                 base.Site = value;
331
332                 // set EnableRaisingEvents to true at design time so the user
333                 // doesn't have to manually. We can't do this in
334                 // the constructor because in code it should
335                 // default to false.
336                 if (Site != null && Site.DesignMode)
337                     EnableRaisingEvents = true;
338             }
339         }
340
341         /// <devdoc>
342         ///    <para>
343         ///       Gets or sets the object used to marshal the event handler calls issued as a
344         ///       result of a directory change.
345         ///    </para>
346         /// </devdoc>
347         [
348         Browsable(false),
349         DefaultValue(null), 
350         IODescription(SR.FSW_SynchronizingObject)
351         ]
352         public ISynchronizeInvoke SynchronizingObject {
353             get {
354                 if (this.synchronizingObject == null && DesignMode) {
355                     IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
356                     if (host != null) {
357                         object baseComponent = host.RootComponent;
358                         if (baseComponent != null && baseComponent is ISynchronizeInvoke)
359                             this.synchronizingObject = (ISynchronizeInvoke)baseComponent;
360                     }                        
361                 }
362             
363                 return this.synchronizingObject;
364             }
365             
366             set {
367                 this.synchronizingObject = value;
368             }
369         }        
370
371         /// <devdoc>
372         ///    <para>
373         ///       Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
374         ///       is changed.
375         ///    </para>
376         /// </devdoc>
377         [IODescription(SR.FSW_Changed)]
378         public event FileSystemEventHandler Changed {
379             add {
380                 onChangedHandler += value;
381             }
382             remove {                            
383                 onChangedHandler -= value;
384             }
385         }
386
387         /// <devdoc>
388         ///    <para>
389         ///       Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
390         ///       is created.
391         ///    </para>
392         /// </devdoc>
393         [IODescription(SR.FSW_Created)]
394         public event FileSystemEventHandler Created {
395             add {
396                 onCreatedHandler += value;
397             }
398             remove {
399                 onCreatedHandler -= value;
400             }
401         }
402
403         /// <devdoc>
404         ///    <para>
405         ///       Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
406         ///       is deleted.
407         ///    </para>
408         /// </devdoc>
409         [IODescription(SR.FSW_Deleted)]
410         public event FileSystemEventHandler Deleted {
411             add{
412                 onDeletedHandler += value;
413             }
414             remove {
415                 onDeletedHandler -= value;
416             }
417         }
418
419         /// <devdoc>
420         ///    <para>
421         ///       Occurs when the internal buffer overflows.
422         ///    </para>
423         /// </devdoc>
424         [Browsable(false)]
425         public event ErrorEventHandler Error {
426             add {
427                 onErrorHandler += value;
428             }
429             remove {
430                 onErrorHandler -= value;
431             }
432         }
433
434         /// <devdoc>
435         ///    <para>
436         ///       Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
437         ///       is renamed.
438         ///    </para>
439         /// </devdoc>
440         [IODescription(SR.FSW_Renamed)]
441         public event RenamedEventHandler Renamed {
442             add {
443                 onRenamedHandler += value;
444             }
445             remove {
446                 onRenamedHandler -= value;
447             }
448         }
449
450         /// <devdoc>
451         ///    <para>Notifies the object that initialization is beginning and tells it to standby.</para>
452         /// </devdoc>
453         public void BeginInit() {
454             bool oldEnabled = enabled;
455             StopRaisingEvents();
456             enabled = oldEnabled;
457             initializing = true;
458         }
459
460         /// <devdoc>
461         ///     Callback from thread pool.
462         /// </devdoc>
463         /// <internalonly/>
464         private unsafe void CompletionStatusChanged(uint errorCode, uint numBytes, NativeOverlapped * overlappedPointer) {
465
466             Overlapped overlapped = Overlapped.Unpack(overlappedPointer);
467             FSWAsyncResult asyncResult = (FSWAsyncResult) overlapped.AsyncResult;
468
469             try {                
470
471                 if (stopListening) {
472                     return;
473                 }
474
475                 lock (this) {
476
477                     if (errorCode != 0) {
478                         if (errorCode == 995 /* ERROR_OPERATION_ABORTED */) {
479                             //Win2000 inside a service the first completion status is false
480                             //cannot return without monitoring again.
481                             //Because this return statement is inside a try/finally block,
482                             //the finally block will execute. It does restart the monitoring.
483                             return;
484                         }
485                         else {
486                             OnError(new ErrorEventArgs(new Win32Exception((int)errorCode)));
487                             EnableRaisingEvents = false;
488                             return;
489                         }
490                     }
491
492                     // Ignore any events that occurred before this "session",
493                     // so we don't get changed or error events after we 
494                     // told FSW to stop.
495                     if (asyncResult.session != currentSession)                    
496                         return;
497
498
499                     if (numBytes == 0) {
500                         NotifyInternalBufferOverflowEvent();
501                     }
502                     else {  // Else, parse each of them and notify appropriate delegates
503     
504                         /******
505                             Format for the buffer is the following C struct:
506     
507                             typedef struct _FILE_NOTIFY_INFORMATION {
508                                DWORD NextEntryOffset;
509                                DWORD Action;
510                                DWORD FileNameLength;
511                                WCHAR FileName[1];
512                             } FILE_NOTIFY_INFORMATION;
513     
514                             NOTE1: FileNameLength is length in bytes.
515                             NOTE2: The Filename is a Unicode string that's NOT NULL terminated.
516                             NOTE3: A NextEntryOffset of zero means that it's the last entry
517                         *******/
518     
519                         // Parse the file notify buffer:
520                         int offset = 0;
521                         int nextOffset, action, nameLength;
522                         string oldName = null;
523                         string name = null;
524     
525                         do {
526
527                             fixed (byte * buffPtr = asyncResult.buffer) {
528
529                                 // Get next offset:
530                                 nextOffset = *( (int *) (buffPtr + offset) );
531
532                                 // Get change flag:
533                                 action = *( (int *) (buffPtr + offset + 4) );
534
535                                 // Get filename length (in bytes):
536                                 nameLength = *( (int *) (buffPtr + offset + 8) );                                                                
537                                 name = new String( (char *) (buffPtr + offset + 12), 0, nameLength / 2);
538                             }
539
540
541                             /* A slightly convoluted piece of code follows.  Here's what's happening:
542     
543                                We wish to collapse the poorly done rename notifications from the
544                                ReadDirectoryChangesW API into a nice rename event. So to do that,
545                                it's assumed that a FILE_ACTION_RENAMED_OLD_NAME will be followed
546                                immediately by a FILE_ACTION_RENAMED_NEW_NAME in the buffer, which is
547                                all that the following code is doing.
548     
549                                On a FILE_ACTION_RENAMED_OLD_NAME, it asserts that no previous one existed
550                                and saves its name.  If there are no more events in the buffer, it'll
551                                assert and fire a RenameEventArgs with the Name field null.
552     
553                                If a NEW_NAME action comes in with no previous OLD_NAME, we assert and fire
554                                a rename event with the OldName field null.
555     
556                                If the OLD_NAME and NEW_NAME actions are indeed there one after the other,
557                                we'll fire the RenamedEventArgs normally and clear oldName.
558     
559                                If the OLD_NAME is followed by another action, we assert and then fire the
560                                rename event with the Name field null and then fire the next action.
561     
562                                In case it's not a OLD_NAME or NEW_NAME action, we just fire the event normally.
563     
564                                (Phew!)
565                              */
566     
567                             // If the action is RENAMED_FROM, save the name of the file
568                             if (action == Direct.FILE_ACTION_RENAMED_OLD_NAME) {
569                                 Debug.Assert(oldName == null, "FileSystemWatcher: Two FILE_ACTION_RENAMED_OLD_NAME " +
570                                                               "in a row!  [" + oldName + "], [ " + name + "]");
571     
572                                 oldName = name;
573                             }
574                             else if (action == Direct.FILE_ACTION_RENAMED_NEW_NAME) {
575                                 if (oldName != null) {
576                                     NotifyRenameEventArgs(WatcherChangeTypes.Renamed, name, oldName);
577                                     oldName = null;
578                                 }
579                                 else {
580                                     Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_NEW_NAME with no" +
581                                                                   "old name! [ " + name + "]");
582     
583                                     NotifyRenameEventArgs(WatcherChangeTypes.Renamed, name, oldName);
584                                     oldName = null;
585                                 }
586                             }
587                             else {
588                                 if (oldName != null) {
589                                     Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_OLD_NAME with no" +
590                                                                   "new name!  [" + oldName + "]");
591     
592                                     NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
593                                     oldName = null;
594                                 }
595     
596                                 // Notify each file of change
597                                 NotifyFileSystemEventArgs(action, name);
598     
599                             }
600     
601                             offset += nextOffset;
602                         } while (nextOffset != 0);
603     
604                         if (oldName != null) {
605                             Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_OLD_NAME with no" +
606                                                           "new name!  [" + oldName + "]");
607     
608                             NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
609                             oldName = null;
610                         }
611                     }                                                                        
612                 }
613             }
614             finally {
615                 Overlapped.Free(overlappedPointer);
616                 if (!stopListening && !runOnce) {
617                     Monitor(asyncResult.buffer);
618                 } 
619             }                                                    
620         }                            
621
622         /// <devdoc>
623         /// </devdoc>
624         protected override void Dispose(bool disposing) {
625             try {
626                 if (disposing) {
627                     
628                     //Stop raising events cleans up managed and
629                     //unmanaged resources.                    
630                     StopRaisingEvents();
631
632                     // Clean up managed resources
633                     onChangedHandler = null;
634                     onCreatedHandler = null;
635                     onDeletedHandler = null;
636                     onRenamedHandler = null;
637                     onErrorHandler = null;
638                     readGranted = false;
639                 
640                 } else {
641                     stopListening = true;
642                              
643                     // Clean up unmanaged resources
644                     if (!IsHandleInvalid) {
645                         directoryHandle.Close();
646                     }                                                          
647                 }     
648            
649             } finally {
650                 this.disposed = true;
651                 base.Dispose(disposing);
652             }
653         }
654                              
655         /// <devdoc>
656         ///    <para>
657         ///       Notifies the object that initialization is complete.
658         ///    </para>
659         /// </devdoc>
660         public void EndInit() {
661             initializing = false;
662             // Unless user told us NOT to start after initialization, we'll start listening
663             // to events
664             if (directory.Length != 0 && enabled == true)
665                 StartRaisingEvents();            
666         }        
667
668         
669         /// <devdoc>
670         ///     Returns true if the component is either in a Begin/End Init block or in design mode.
671         /// </devdoc>
672         // <internalonly/>
673         //
674         private bool IsSuspended() {
675             return initializing || DesignMode;
676         }
677
678         /// <devdoc>
679         ///     Sees if the name given matches the name filter we have.
680         /// </devdoc>
681         /// <internalonly/>
682         private bool MatchPattern(string relativePath) {            
683             string name = System.IO.Path.GetFileName(relativePath);            
684             if (name != null)
685                 return PatternMatcher.StrictMatchPattern(filter.ToUpper(CultureInfo.InvariantCulture), name.ToUpper(CultureInfo.InvariantCulture));
686             else
687                 return false;                
688         }
689
690         /// <devdoc>
691         ///     Calls native API and sets up handle with the directory change API.
692         /// </devdoc>
693         /// <internalonly/>
694         [ResourceExposure(ResourceScope.None)]
695         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
696         private unsafe void Monitor(byte[] buffer) {
697             if (!enabled || IsHandleInvalid) {
698                 return;
699             }
700
701             Overlapped overlapped = new Overlapped();            
702             if (buffer == null) {
703                 try {
704                     buffer = new byte[internalBufferSize];
705                 }
706                 catch (OutOfMemoryException) {
707                         throw new OutOfMemoryException(SR.GetString(SR.BufferSizeTooLarge, internalBufferSize.ToString(CultureInfo.CurrentCulture)));
708                 }
709             }
710                         
711             // Pass "session" counter to callback:
712             FSWAsyncResult asyncResult = new FSWAsyncResult();
713             asyncResult.session = currentSession;
714             asyncResult.buffer = buffer;
715
716             // Pack overlapped. The buffer will be pinned by Overlapped:
717             overlapped.AsyncResult = asyncResult;
718             NativeOverlapped* overlappedPointer = overlapped.Pack(new IOCompletionCallback(this.CompletionStatusChanged), buffer);
719
720             // Can now call OS:
721             int size;
722             bool ok = false;
723
724             try {
725                 // There could be a ---- in user code between calling StopRaisingEvents (where we close the handle) 
726                 // and when we get here from CompletionStatusChanged. 
727                 // We might need to take a lock to prevent ---- absolutely, instead just catch 
728                 // ObjectDisposedException from SafeHandle in case it is disposed
729                 if (!IsHandleInvalid) {
730                     // An interrupt is possible here
731                     fixed (byte * buffPtr = buffer) {
732                         ok = UnsafeNativeMethods.ReadDirectoryChangesW(directoryHandle,
733                                                            new HandleRef(this, (IntPtr) buffPtr),
734                                                            internalBufferSize,
735                                                            includeSubdirectories ? 1 : 0,
736                                                            (int) notifyFilters,
737                                                            out size,
738                                                            overlappedPointer,
739                                                            NativeMethods.NullHandleRef);
740                     }
741                 }
742             } catch (ObjectDisposedException ) { //Ignore
743                 Debug.Assert(IsHandleInvalid, "ObjectDisposedException from something other than SafeHandle?");
744             } catch (ArgumentNullException ) { //Ignore
745                 Debug.Assert(IsHandleInvalid, "ArgumentNullException from something other than SafeHandle?");
746             } finally {
747                 if (! ok) {
748                     Overlapped.Free(overlappedPointer);
749
750                     // If the handle was for some reason changed or closed during this call, then don't throw an
751                     // exception.  Else, it's a valid error.
752                     if (!IsHandleInvalid) {
753                         OnError(new ErrorEventArgs(new Win32Exception()));
754                     }
755                 }
756             }
757         }                            
758         
759         /// <devdoc>
760         ///     Raises the event to each handler in the list.
761         /// </devdoc>
762         /// <internalonly/>
763         private void NotifyFileSystemEventArgs(int action, string name) {
764             if (!MatchPattern(name)) {
765                 return;
766             }
767
768             switch (action) {
769                 case Direct.FILE_ACTION_ADDED:
770                     OnCreated(new FileSystemEventArgs(WatcherChangeTypes.Created, directory, name));
771                     break;
772                 case Direct.FILE_ACTION_REMOVED:
773                     OnDeleted(new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, name));
774                     break;
775                 case Direct.FILE_ACTION_MODIFIED:
776                     OnChanged(new FileSystemEventArgs(WatcherChangeTypes.Changed, directory, name));
777                     break;
778
779                 default:
780                     Debug.Fail("Unknown FileSystemEvent action type!  Value: "+action);
781                     break;
782             }
783         }
784
785         /// <devdoc>
786         ///     Raises the event to each handler in the list.
787         /// </devdoc>
788         /// <internalonly/>
789         private void NotifyInternalBufferOverflowEvent() {
790             InternalBufferOverflowException ex = new InternalBufferOverflowException(SR.GetString(SR.FSW_BufferOverflow, directory));
791
792             ErrorEventArgs errevent = new ErrorEventArgs(ex);
793
794             OnError(errevent);
795         }
796
797         /// <devdoc>
798         ///     Raises the event to each handler in the list.
799         /// </devdoc>
800         /// <internalonly/>
801         private void NotifyRenameEventArgs(WatcherChangeTypes action, string name, string oldName) {
802             //filter if neither new name or old name are a match a specified pattern
803             if (!MatchPattern(name) && !MatchPattern(oldName)) {
804                 return;
805             }
806
807             RenamedEventArgs renevent = new RenamedEventArgs(action, directory, name, oldName);
808             OnRenamed(renevent);
809         }
810
811         /// <devdoc>
812         ///    <para>
813         ///       Raises the <see cref='System.IO.FileSystemWatcher.Changed'/> event.
814         ///    </para>
815         /// </devdoc>
816         [SuppressMessage("Microsoft.Security","CA2109:ReviewVisibleEventHandlers", MessageId="0#", Justification="Changing from protected to private would be a breaking change")]
817         protected void OnChanged(FileSystemEventArgs e) {
818             // To avoid ---- between remove handler and raising the event
819             FileSystemEventHandler changedHandler = onChangedHandler;
820             
821             if (changedHandler != null) {
822                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
823                     this.SynchronizingObject.BeginInvoke(changedHandler, new object[]{this, e});
824                 else                        
825                    changedHandler(this, e);                
826             }
827         }
828
829         /// <devdoc>
830         ///    <para>
831         ///       Raises the <see cref='System.IO.FileSystemWatcher.Created'/> event.
832         ///    </para>
833         /// </devdoc>
834         [SuppressMessage("Microsoft.Security","CA2109:ReviewVisibleEventHandlers", MessageId="0#", Justification="Changing from protected to private would be a breaking change")]
835         protected void OnCreated(FileSystemEventArgs e) {
836             // To avoid ---- between remove handler and raising the event
837             FileSystemEventHandler createdHandler = onCreatedHandler;
838             if (createdHandler != null) {
839                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
840                     this.SynchronizingObject.BeginInvoke(createdHandler, new object[]{this, e});
841                 else                        
842                    createdHandler(this, e);                
843             }
844         }
845
846         /// <devdoc>
847         ///    <para>
848         ///       Raises the <see cref='System.IO.FileSystemWatcher.Deleted'/> event.
849         ///    </para>
850         /// </devdoc>
851         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
852         protected void OnDeleted(FileSystemEventArgs e) {
853             // To avoid ---- between remove handler and raising the event
854             FileSystemEventHandler deletedHandler = onDeletedHandler;
855             if (deletedHandler != null) {
856                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
857                     this.SynchronizingObject.BeginInvoke(deletedHandler, new object[]{this, e});
858                 else                        
859                    deletedHandler(this, e);                
860             }
861         }
862
863         /// <devdoc>
864         ///    <para>
865         ///       Raises the <see cref='System.IO.FileSystemWatcher.Error'/> event.
866         ///    </para>
867         /// </devdoc>
868         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
869         protected void OnError(ErrorEventArgs e) {
870             // To avoid ---- between remove handler and raising the event
871             ErrorEventHandler errorHandler = onErrorHandler;
872             if (errorHandler != null) {
873                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
874                     this.SynchronizingObject.BeginInvoke(errorHandler, new object[]{this, e});
875                 else                        
876                    errorHandler(this, e);                
877             }
878         }
879
880         /// <devdoc>
881         ///     Internal method used for synchronous notification.
882         /// </devdoc>
883         /// <internalonly/>
884         private void OnInternalFileSystemEventArgs(object sender, FileSystemEventArgs e) {
885             lock (this) {
886                 // Only change the state of the changed result if it doesn't contain a previous one.
887                 if (isChanged != true) {
888                     changedResult = new WaitForChangedResult(e.ChangeType, e.Name, false);
889                     isChanged = true;
890                     System.Threading.Monitor.Pulse(this);
891                 }
892             }
893         }
894
895         /// <devdoc>
896         ///     Internal method used for synchronous notification.
897         /// </devdoc>
898         /// <internalonly/>
899         private void OnInternalRenameEventArgs(object sender, RenamedEventArgs e) {
900             lock (this) {
901                 // Only change the state of the changed result if it doesn't contain a previous one.
902                 if (isChanged != true) {
903                     changedResult = new WaitForChangedResult(e.ChangeType, e.Name, e.OldName, false);
904                     isChanged = true;
905                     System.Threading.Monitor.Pulse(this);
906                 }
907             }
908         }
909
910         /// <devdoc>
911         ///    <para>
912         ///       Raises the <see cref='System.IO.FileSystemWatcher.Renamed'/> event.
913         ///    </para>
914         /// </devdoc>
915         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
916         protected void OnRenamed(RenamedEventArgs e) {
917             RenamedEventHandler renamedHandler = onRenamedHandler;
918             if (renamedHandler != null) {
919                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
920                     this.SynchronizingObject.BeginInvoke(renamedHandler, new object[]{this, e});
921                 else                        
922                    renamedHandler(this, e);                
923             }
924         }
925
926         /// <devdoc>
927         ///     Stops and starts this object.
928         /// </devdoc>
929         /// <internalonly/>
930         private void Restart() {
931             if ((!IsSuspended()) && enabled) {
932                 StopRaisingEvents();
933                 StartRaisingEvents();
934             }
935         }
936
937         /// <devdoc>
938         ///    <para>
939         ///       Starts monitoring the specified directory.
940         ///    </para>
941         /// </devdoc>
942         [ResourceExposure(ResourceScope.None)]
943         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
944         private void StartRaisingEvents() {
945             //Cannot allocate the directoryHandle and the readBuffer if the object has been disposed; finalization has been suppressed.
946             if (this.disposed)
947                 throw new ObjectDisposedException(GetType().Name);
948                 
949             try {
950                 new EnvironmentPermission(PermissionState.Unrestricted).Assert();
951                 if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
952                     throw new PlatformNotSupportedException(SR.GetString(SR.WinNTRequired));
953                 }
954             }
955             finally {
956                 CodeAccessPermission.RevertAssert();
957             }
958
959             // If we're called when "Initializing" is true, set enabled to true
960             if (IsSuspended()) {
961                 enabled = true;
962                 return;
963             }
964         
965             if (!readGranted) {
966                 string fullPath;
967                 // Consider asserting path discovery permission here.
968                 fullPath = System.IO.Path.GetFullPath(directory);
969
970                 FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Read, fullPath);
971                 permission.Demand();                
972                 readGranted = true;                    
973             }
974             
975             
976             // If we're attached, don't do anything.
977             if (!IsHandleInvalid) {
978                 return;
979             }
980
981             // Create handle to directory being monitored
982             directoryHandle = NativeMethods.CreateFile(directory,            // Directory name
983                                 UnsafeNativeMethods.FILE_LIST_DIRECTORY,           // access (read-write) mode
984                                 UnsafeNativeMethods.FILE_SHARE_READ |
985                                     UnsafeNativeMethods.FILE_SHARE_DELETE |
986                                     UnsafeNativeMethods.FILE_SHARE_WRITE,          // share mode
987                                 null,                                              // security descriptor
988                                 UnsafeNativeMethods.OPEN_EXISTING,                 // how to create
989                                 UnsafeNativeMethods.FILE_FLAG_BACKUP_SEMANTICS |
990                                     UnsafeNativeMethods.FILE_FLAG_OVERLAPPED,      // file attributes
991                                 new SafeFileHandle(IntPtr.Zero, false)             // file with attributes to copy
992                             );
993
994             if (IsHandleInvalid) {
995                 throw new FileNotFoundException(SR.GetString(SR.FSW_IOError, directory));
996             }
997             
998             stopListening = false;
999             // Start ignoring all events that were initiated before this.
1000             Interlocked.Increment(ref currentSession);
1001
1002             // Attach handle to thread pool
1003             
1004             //SECREVIEW: At this point at least FileIOPermission has already been demanded.
1005             SecurityPermission secPermission = new SecurityPermission(PermissionState.Unrestricted);
1006             secPermission.Assert();
1007             try {
1008                 ThreadPool.BindHandle(directoryHandle);
1009             }
1010             finally {
1011                 SecurityPermission.RevertAssert();
1012             }                                                   
1013             enabled = true;
1014
1015             // Setup IO completion port
1016             Monitor(null);
1017         }
1018
1019         /// <devdoc>
1020         ///    <para>
1021         ///       Stops monitoring the specified directory.
1022         ///    </para>
1023         /// </devdoc>
1024         private void StopRaisingEvents() {
1025             if (IsSuspended()) {
1026                 enabled = false;
1027                 return;
1028             }
1029
1030             // If we're not attached, do nothing.
1031             if (IsHandleInvalid) {
1032                 return;
1033             }
1034
1035             // Close directory handle 
1036             // This operation doesn't need to be atomic because the API will deal with a closed
1037             // handle appropriately.
1038             // Ensure that the directoryHandle is set to INVALID_HANDLE before closing it, so that
1039             // the Monitor() can shutdown appropriately.
1040             // If we get here while asynchronously waiting on a change notification, closing the
1041             // directory handle should cause CompletionStatusChanged be be called
1042             // thus freeing the pinned buffer.
1043             stopListening = true;
1044             directoryHandle.Close();
1045             directoryHandle = null;
1046
1047
1048             // Start ignoring all events occurring after this.
1049             Interlocked.Increment(ref currentSession);
1050             
1051             // Set enabled to false
1052             enabled = false;
1053         }
1054
1055         /// <devdoc>
1056         ///    <para>
1057         ///       A synchronous method that returns a structure that
1058         ///       contains specific information on the change that occurred, given the type
1059         ///       of change that you wish to monitor.
1060         ///    </para>
1061         /// </devdoc>
1062         public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) {
1063             return WaitForChanged(changeType, -1);
1064         }
1065
1066         /// <devdoc>
1067         ///    <para>
1068         ///       A synchronous
1069         ///       method that returns a structure that contains specific information on the change that occurred, given the
1070         ///       type of change that you wish to monitor and the time (in milliseconds) to wait before timing out.
1071         ///    </para>
1072         /// </devdoc>
1073         public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) {
1074             FileSystemEventHandler dirHandler = new FileSystemEventHandler(this.OnInternalFileSystemEventArgs);
1075             RenamedEventHandler renameHandler = new RenamedEventHandler(this.OnInternalRenameEventArgs);
1076
1077             this.isChanged = false;
1078             this.changedResult = WaitForChangedResult.TimedOutResult;
1079
1080             // Register the internal event handler from the given change types.
1081             if ((changeType & WatcherChangeTypes.Created) != 0) {
1082                 this.Created += dirHandler;
1083             }
1084             if ((changeType & WatcherChangeTypes.Deleted) != 0) {
1085                 this.Deleted += dirHandler;
1086             }
1087             if ((changeType & WatcherChangeTypes.Changed) != 0) {
1088                 this.Changed += dirHandler;
1089             }
1090             if ((changeType & WatcherChangeTypes.Renamed) != 0) {
1091                 this.Renamed += renameHandler;
1092             }
1093
1094             // Save the Enabled state of this component to revert back to it later (if needed).
1095             bool savedEnabled = EnableRaisingEvents;
1096             if (savedEnabled == false) {
1097                 runOnce = true;
1098                 EnableRaisingEvents = true;
1099             }
1100
1101             // For each thread entering this wait loop, addref it and wait.  When the last one
1102             // exits, reset the waiterObject.
1103             WaitForChangedResult retVal = WaitForChangedResult.TimedOutResult;
1104             lock (this) {
1105                 if (timeout == -1) {
1106                     while (!isChanged) {
1107                         System.Threading.Monitor.Wait(this);
1108                     }
1109                 }
1110                 else {
1111                     System.Threading.Monitor.Wait(this, timeout, true);
1112                 }
1113
1114                 retVal = changedResult;
1115             }
1116
1117             // Revert the Enabled flag to its previous state.
1118             EnableRaisingEvents = savedEnabled;
1119             runOnce = false;
1120
1121             // Decouple the event handlers added above.
1122             if ((changeType & WatcherChangeTypes.Created) != 0) {
1123                 this.Created -= dirHandler;
1124             }
1125             if ((changeType & WatcherChangeTypes.Deleted) != 0) {
1126                 this.Deleted -= dirHandler;
1127             }
1128             if ((changeType & WatcherChangeTypes.Changed) != 0) {
1129                 this.Changed -= dirHandler;
1130             }
1131             if ((changeType & WatcherChangeTypes.Renamed) != 0) {
1132                 this.Renamed -= renameHandler;
1133             }
1134
1135             // Return the struct.
1136             return retVal;
1137         }
1138     }
1139
1140     /// <devdoc>
1141     ///    Helper class to hold to N/Direct call declaration and flags.
1142     /// </devdoc>
1143     [
1144         System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
1145     ]    
1146     internal static class Direct {
1147         // All possible action flags
1148         public const int FILE_ACTION_ADDED            = 1;
1149         public const int FILE_ACTION_REMOVED          = 2;
1150         public const int FILE_ACTION_MODIFIED         = 3;
1151         public const int FILE_ACTION_RENAMED_OLD_NAME = 4;
1152         public const int FILE_ACTION_RENAMED_NEW_NAME = 5;
1153
1154
1155         // All possible notifications flags
1156         public const int FILE_NOTIFY_CHANGE_FILE_NAME    = 0x00000001;
1157         public const int FILE_NOTIFY_CHANGE_DIR_NAME     = 0x00000002;
1158         public const int FILE_NOTIFY_CHANGE_NAME         = 0x00000003;
1159         public const int FILE_NOTIFY_CHANGE_ATTRIBUTES   = 0x00000004;
1160         public const int FILE_NOTIFY_CHANGE_SIZE         = 0x00000008;
1161         public const int FILE_NOTIFY_CHANGE_LAST_WRITE   = 0x00000010;
1162         public const int FILE_NOTIFY_CHANGE_LAST_ACCESS  = 0x00000020;
1163         public const int FILE_NOTIFY_CHANGE_CREATION     = 0x00000040;
1164         public const int FILE_NOTIFY_CHANGE_SECURITY     = 0x00000100;
1165     }
1166 }
1167
1168