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 
FileSystemWatcher()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>
FileSystemWatcher()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)]
FileSystemWatcher(string path)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)]
FileSystemWatcher(string path, string filter)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>
BeginInit()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/>
CompletionStatusChanged(uint errorCode, uint numBytes, NativeOverlapped * overlappedPointer)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                                     NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
590                                     oldName = null;
591                                 }
592 
593                                 // Notify each file of change
594                                 NotifyFileSystemEventArgs(action, name);
595 
596                             }
597 
598                             offset += nextOffset;
599                         } while (nextOffset != 0);
600 
601                         if (oldName != null) {
602                             Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_OLD_NAME with no" +
603                                                           "new name!  [" + oldName + "]");
604 
605                             NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
606                             oldName = null;
607                         }
608                     }
609                 }
610             }
611             finally {
612                 Overlapped.Free(overlappedPointer);
613                 if (!stopListening && !runOnce) {
614                     Monitor(asyncResult.buffer);
615                 }
616             }
617         }
618 
619         /// <devdoc>
620         /// </devdoc>
Dispose(bool disposing)621         protected override void Dispose(bool disposing) {
622             try {
623                 if (disposing) {
624 
625                     //Stop raising events cleans up managed and
626                     //unmanaged resources.
627                     StopRaisingEvents();
628 
629                     // Clean up managed resources
630                     onChangedHandler = null;
631                     onCreatedHandler = null;
632                     onDeletedHandler = null;
633                     onRenamedHandler = null;
634                     onErrorHandler = null;
635                     readGranted = false;
636 
637                 } else {
638                     stopListening = true;
639 
640                     // Clean up unmanaged resources
641                     if (!IsHandleInvalid) {
642                         directoryHandle.Close();
643                     }
644                 }
645 
646             } finally {
647                 this.disposed = true;
648                 base.Dispose(disposing);
649             }
650         }
651 
652         /// <devdoc>
653         ///    <para>
654         ///       Notifies the object that initialization is complete.
655         ///    </para>
656         /// </devdoc>
EndInit()657         public void EndInit() {
658             initializing = false;
659             // Unless user told us NOT to start after initialization, we'll start listening
660             // to events
661             if (directory.Length != 0 && enabled == true)
662                 StartRaisingEvents();
663         }
664 
665 
666         /// <devdoc>
667         ///     Returns true if the component is either in a Begin/End Init block or in design mode.
668         /// </devdoc>
669         // <internalonly/>
670         //
IsSuspended()671         private bool IsSuspended() {
672             return initializing || DesignMode;
673         }
674 
675         /// <devdoc>
676         ///     Sees if the name given matches the name filter we have.
677         /// </devdoc>
678         /// <internalonly/>
MatchPattern(string relativePath)679         private bool MatchPattern(string relativePath) {
680             string name = System.IO.Path.GetFileName(relativePath);
681             if (name != null)
682                 return PatternMatcher.StrictMatchPattern(filter.ToUpper(CultureInfo.InvariantCulture), name.ToUpper(CultureInfo.InvariantCulture));
683             else
684                 return false;
685         }
686 
687         /// <devdoc>
688         ///     Calls native API and sets up handle with the directory change API.
689         /// </devdoc>
690         /// <internalonly/>
691         [ResourceExposure(ResourceScope.None)]
692         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
Monitor(byte[] buffer)693         private unsafe void Monitor(byte[] buffer) {
694             if (!enabled || IsHandleInvalid) {
695                 return;
696             }
697 
698             Overlapped overlapped = new Overlapped();
699             if (buffer == null) {
700                 try {
701                     buffer = new byte[internalBufferSize];
702                 }
703                 catch (OutOfMemoryException) {
704                         throw new OutOfMemoryException(SR.GetString(SR.BufferSizeTooLarge, internalBufferSize.ToString(CultureInfo.CurrentCulture)));
705                 }
706             }
707 
708             // Pass "session" counter to callback:
709             FSWAsyncResult asyncResult = new FSWAsyncResult();
710             asyncResult.session = currentSession;
711             asyncResult.buffer = buffer;
712 
713             // Pack overlapped. The buffer will be pinned by Overlapped:
714             overlapped.AsyncResult = asyncResult;
715             NativeOverlapped* overlappedPointer = overlapped.Pack(new IOCompletionCallback(this.CompletionStatusChanged), buffer);
716 
717             // Can now call OS:
718             int size;
719             bool ok = false;
720 
721             try {
722                 // There could be a ---- in user code between calling StopRaisingEvents (where we close the handle)
723                 // and when we get here from CompletionStatusChanged.
724                 // We might need to take a lock to prevent ---- absolutely, instead just catch
725                 // ObjectDisposedException from SafeHandle in case it is disposed
726                 if (!IsHandleInvalid) {
727                     // An interrupt is possible here
728                     fixed (byte * buffPtr = buffer) {
729                         ok = UnsafeNativeMethods.ReadDirectoryChangesW(directoryHandle,
730                                                            new HandleRef(this, (IntPtr) buffPtr),
731                                                            internalBufferSize,
732                                                            includeSubdirectories ? 1 : 0,
733                                                            (int) notifyFilters,
734                                                            out size,
735                                                            overlappedPointer,
736                                                            NativeMethods.NullHandleRef);
737                     }
738                 }
739             } catch (ObjectDisposedException ) { //Ignore
740                 Debug.Assert(IsHandleInvalid, "ObjectDisposedException from something other than SafeHandle?");
741             } catch (ArgumentNullException ) { //Ignore
742                 Debug.Assert(IsHandleInvalid, "ArgumentNullException from something other than SafeHandle?");
743             } finally {
744                 if (! ok) {
745                     Overlapped.Free(overlappedPointer);
746 
747                     // If the handle was for some reason changed or closed during this call, then don't throw an
748                     // exception.  Else, it's a valid error.
749                     if (!IsHandleInvalid) {
750                         OnError(new ErrorEventArgs(new Win32Exception()));
751                     }
752                 }
753             }
754         }
755 
756         /// <devdoc>
757         ///     Raises the event to each handler in the list.
758         /// </devdoc>
759         /// <internalonly/>
NotifyFileSystemEventArgs(int action, string name)760         private void NotifyFileSystemEventArgs(int action, string name) {
761             if (!MatchPattern(name)) {
762                 return;
763             }
764 
765             switch (action) {
766                 case Direct.FILE_ACTION_ADDED:
767                     OnCreated(new FileSystemEventArgs(WatcherChangeTypes.Created, directory, name));
768                     break;
769                 case Direct.FILE_ACTION_REMOVED:
770                     OnDeleted(new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, name));
771                     break;
772                 case Direct.FILE_ACTION_MODIFIED:
773                     OnChanged(new FileSystemEventArgs(WatcherChangeTypes.Changed, directory, name));
774                     break;
775 
776                 default:
777                     Debug.Fail("Unknown FileSystemEvent action type!  Value: "+action);
778                     break;
779             }
780         }
781 
782         /// <devdoc>
783         ///     Raises the event to each handler in the list.
784         /// </devdoc>
785         /// <internalonly/>
NotifyInternalBufferOverflowEvent()786         private void NotifyInternalBufferOverflowEvent() {
787             InternalBufferOverflowException ex = new InternalBufferOverflowException(SR.GetString(SR.FSW_BufferOverflow, directory));
788 
789             ErrorEventArgs errevent = new ErrorEventArgs(ex);
790 
791             OnError(errevent);
792         }
793 
794         /// <devdoc>
795         ///     Raises the event to each handler in the list.
796         /// </devdoc>
797         /// <internalonly/>
NotifyRenameEventArgs(WatcherChangeTypes action, string name, string oldName)798         private void NotifyRenameEventArgs(WatcherChangeTypes action, string name, string oldName) {
799             //filter if neither new name or old name are a match a specified pattern
800             if (!MatchPattern(name) && !MatchPattern(oldName)) {
801                 return;
802             }
803 
804             RenamedEventArgs renevent = new RenamedEventArgs(action, directory, name, oldName);
805             OnRenamed(renevent);
806         }
807 
808         /// <devdoc>
809         ///    <para>
810         ///       Raises the <see cref='System.IO.FileSystemWatcher.Changed'/> event.
811         ///    </para>
812         /// </devdoc>
813         [SuppressMessage("Microsoft.Security","CA2109:ReviewVisibleEventHandlers", MessageId="0#", Justification="Changing from protected to private would be a breaking change")]
OnChanged(FileSystemEventArgs e)814         protected void OnChanged(FileSystemEventArgs e) {
815             // To avoid ---- between remove handler and raising the event
816             FileSystemEventHandler changedHandler = onChangedHandler;
817 
818             if (changedHandler != null) {
819                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
820                     this.SynchronizingObject.BeginInvoke(changedHandler, new object[]{this, e});
821                 else
822                    changedHandler(this, e);
823             }
824         }
825 
826         /// <devdoc>
827         ///    <para>
828         ///       Raises the <see cref='System.IO.FileSystemWatcher.Created'/> event.
829         ///    </para>
830         /// </devdoc>
831         [SuppressMessage("Microsoft.Security","CA2109:ReviewVisibleEventHandlers", MessageId="0#", Justification="Changing from protected to private would be a breaking change")]
OnCreated(FileSystemEventArgs e)832         protected void OnCreated(FileSystemEventArgs e) {
833             // To avoid ---- between remove handler and raising the event
834             FileSystemEventHandler createdHandler = onCreatedHandler;
835             if (createdHandler != null) {
836                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
837                     this.SynchronizingObject.BeginInvoke(createdHandler, new object[]{this, e});
838                 else
839                    createdHandler(this, e);
840             }
841         }
842 
843         /// <devdoc>
844         ///    <para>
845         ///       Raises the <see cref='System.IO.FileSystemWatcher.Deleted'/> event.
846         ///    </para>
847         /// </devdoc>
848         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
OnDeleted(FileSystemEventArgs e)849         protected void OnDeleted(FileSystemEventArgs e) {
850             // To avoid ---- between remove handler and raising the event
851             FileSystemEventHandler deletedHandler = onDeletedHandler;
852             if (deletedHandler != null) {
853                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
854                     this.SynchronizingObject.BeginInvoke(deletedHandler, new object[]{this, e});
855                 else
856                    deletedHandler(this, e);
857             }
858         }
859 
860         /// <devdoc>
861         ///    <para>
862         ///       Raises the <see cref='System.IO.FileSystemWatcher.Error'/> event.
863         ///    </para>
864         /// </devdoc>
865         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
OnError(ErrorEventArgs e)866         protected void OnError(ErrorEventArgs e) {
867             // To avoid ---- between remove handler and raising the event
868             ErrorEventHandler errorHandler = onErrorHandler;
869             if (errorHandler != null) {
870                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
871                     this.SynchronizingObject.BeginInvoke(errorHandler, new object[]{this, e});
872                 else
873                    errorHandler(this, e);
874             }
875         }
876 
877         /// <devdoc>
878         ///     Internal method used for synchronous notification.
879         /// </devdoc>
880         /// <internalonly/>
OnInternalFileSystemEventArgs(object sender, FileSystemEventArgs e)881         private void OnInternalFileSystemEventArgs(object sender, FileSystemEventArgs e) {
882             lock (this) {
883                 // Only change the state of the changed result if it doesn't contain a previous one.
884                 if (isChanged != true) {
885                     changedResult = new WaitForChangedResult(e.ChangeType, e.Name, false);
886                     isChanged = true;
887                     System.Threading.Monitor.Pulse(this);
888                 }
889             }
890         }
891 
892         /// <devdoc>
893         ///     Internal method used for synchronous notification.
894         /// </devdoc>
895         /// <internalonly/>
OnInternalRenameEventArgs(object sender, RenamedEventArgs e)896         private void OnInternalRenameEventArgs(object sender, RenamedEventArgs e) {
897             lock (this) {
898                 // Only change the state of the changed result if it doesn't contain a previous one.
899                 if (isChanged != true) {
900                     changedResult = new WaitForChangedResult(e.ChangeType, e.Name, e.OldName, false);
901                     isChanged = true;
902                     System.Threading.Monitor.Pulse(this);
903                 }
904             }
905         }
906 
907         /// <devdoc>
908         ///    <para>
909         ///       Raises the <see cref='System.IO.FileSystemWatcher.Renamed'/> event.
910         ///    </para>
911         /// </devdoc>
912         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
OnRenamed(RenamedEventArgs e)913         protected void OnRenamed(RenamedEventArgs e) {
914             RenamedEventHandler renamedHandler = onRenamedHandler;
915             if (renamedHandler != null) {
916                 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
917                     this.SynchronizingObject.BeginInvoke(renamedHandler, new object[]{this, e});
918                 else
919                    renamedHandler(this, e);
920             }
921         }
922 
923         /// <devdoc>
924         ///     Stops and starts this object.
925         /// </devdoc>
926         /// <internalonly/>
Restart()927         private void Restart() {
928             if ((!IsSuspended()) && enabled) {
929                 StopRaisingEvents();
930                 StartRaisingEvents();
931             }
932         }
933 
934         /// <devdoc>
935         ///    <para>
936         ///       Starts monitoring the specified directory.
937         ///    </para>
938         /// </devdoc>
939         [ResourceExposure(ResourceScope.None)]
940         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
StartRaisingEvents()941         private void StartRaisingEvents() {
942             //Cannot allocate the directoryHandle and the readBuffer if the object has been disposed; finalization has been suppressed.
943             if (this.disposed)
944                 throw new ObjectDisposedException(GetType().Name);
945 
946             try {
947                 new EnvironmentPermission(PermissionState.Unrestricted).Assert();
948                 if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
949                     throw new PlatformNotSupportedException(SR.GetString(SR.WinNTRequired));
950                 }
951             }
952             finally {
953                 CodeAccessPermission.RevertAssert();
954             }
955 
956             // If we're called when "Initializing" is true, set enabled to true
957             if (IsSuspended()) {
958                 enabled = true;
959                 return;
960             }
961 
962             if (!readGranted) {
963                 string fullPath;
964                 // Consider asserting path discovery permission here.
965                 fullPath = System.IO.Path.GetFullPath(directory);
966 
967                 FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Read, fullPath);
968                 permission.Demand();
969                 readGranted = true;
970             }
971 
972 
973             // If we're attached, don't do anything.
974             if (!IsHandleInvalid) {
975                 return;
976             }
977 
978             // Create handle to directory being monitored
979             directoryHandle = NativeMethods.CreateFile(directory,            // Directory name
980                                 UnsafeNativeMethods.FILE_LIST_DIRECTORY,           // access (read-write) mode
981                                 UnsafeNativeMethods.FILE_SHARE_READ |
982                                     UnsafeNativeMethods.FILE_SHARE_DELETE |
983                                     UnsafeNativeMethods.FILE_SHARE_WRITE,          // share mode
984                                 null,                                              // security descriptor
985                                 UnsafeNativeMethods.OPEN_EXISTING,                 // how to create
986                                 UnsafeNativeMethods.FILE_FLAG_BACKUP_SEMANTICS |
987                                     UnsafeNativeMethods.FILE_FLAG_OVERLAPPED,      // file attributes
988                                 new SafeFileHandle(IntPtr.Zero, false)             // file with attributes to copy
989                             );
990 
991             if (IsHandleInvalid) {
992                 throw new FileNotFoundException(SR.GetString(SR.FSW_IOError, directory));
993             }
994 
995             stopListening = false;
996             // Start ignoring all events that were initiated before this.
997             Interlocked.Increment(ref currentSession);
998 
999             // Attach handle to thread pool
1000 
1001             //SECREVIEW: At this point at least FileIOPermission has already been demanded.
1002             SecurityPermission secPermission = new SecurityPermission(PermissionState.Unrestricted);
1003             secPermission.Assert();
1004             try {
1005                 ThreadPool.BindHandle(directoryHandle);
1006             }
1007             finally {
1008                 SecurityPermission.RevertAssert();
1009             }
1010             enabled = true;
1011 
1012             // Setup IO completion port
1013             Monitor(null);
1014         }
1015 
1016         /// <devdoc>
1017         ///    <para>
1018         ///       Stops monitoring the specified directory.
1019         ///    </para>
1020         /// </devdoc>
StopRaisingEvents()1021         private void StopRaisingEvents() {
1022             if (IsSuspended()) {
1023                 enabled = false;
1024                 return;
1025             }
1026 
1027             // If we're not attached, do nothing.
1028             if (IsHandleInvalid) {
1029                 return;
1030             }
1031 
1032             // Close directory handle
1033             // This operation doesn't need to be atomic because the API will deal with a closed
1034             // handle appropriately.
1035             // Ensure that the directoryHandle is set to INVALID_HANDLE before closing it, so that
1036             // the Monitor() can shutdown appropriately.
1037             // If we get here while asynchronously waiting on a change notification, closing the
1038             // directory handle should cause CompletionStatusChanged be be called
1039             // thus freeing the pinned buffer.
1040             stopListening = true;
1041             directoryHandle.Close();
1042             directoryHandle = null;
1043 
1044 
1045             // Start ignoring all events occurring after this.
1046             Interlocked.Increment(ref currentSession);
1047 
1048             // Set enabled to false
1049             enabled = false;
1050         }
1051 
1052         /// <devdoc>
1053         ///    <para>
1054         ///       A synchronous method that returns a structure that
1055         ///       contains specific information on the change that occurred, given the type
1056         ///       of change that you wish to monitor.
1057         ///    </para>
1058         /// </devdoc>
WaitForChanged(WatcherChangeTypes changeType)1059         public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) {
1060             return WaitForChanged(changeType, -1);
1061         }
1062 
1063         /// <devdoc>
1064         ///    <para>
1065         ///       A synchronous
1066         ///       method that returns a structure that contains specific information on the change that occurred, given the
1067         ///       type of change that you wish to monitor and the time (in milliseconds) to wait before timing out.
1068         ///    </para>
1069         /// </devdoc>
WaitForChanged(WatcherChangeTypes changeType, int timeout)1070         public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) {
1071             FileSystemEventHandler dirHandler = new FileSystemEventHandler(this.OnInternalFileSystemEventArgs);
1072             RenamedEventHandler renameHandler = new RenamedEventHandler(this.OnInternalRenameEventArgs);
1073 
1074             this.isChanged = false;
1075             this.changedResult = WaitForChangedResult.TimedOutResult;
1076 
1077             // Register the internal event handler from the given change types.
1078             if ((changeType & WatcherChangeTypes.Created) != 0) {
1079                 this.Created += dirHandler;
1080             }
1081             if ((changeType & WatcherChangeTypes.Deleted) != 0) {
1082                 this.Deleted += dirHandler;
1083             }
1084             if ((changeType & WatcherChangeTypes.Changed) != 0) {
1085                 this.Changed += dirHandler;
1086             }
1087             if ((changeType & WatcherChangeTypes.Renamed) != 0) {
1088                 this.Renamed += renameHandler;
1089             }
1090 
1091             // Save the Enabled state of this component to revert back to it later (if needed).
1092             bool savedEnabled = EnableRaisingEvents;
1093             if (savedEnabled == false) {
1094                 runOnce = true;
1095                 EnableRaisingEvents = true;
1096             }
1097 
1098             // For each thread entering this wait loop, addref it and wait.  When the last one
1099             // exits, reset the waiterObject.
1100             WaitForChangedResult retVal = WaitForChangedResult.TimedOutResult;
1101             lock (this) {
1102                 if (timeout == -1) {
1103                     while (!isChanged) {
1104                         System.Threading.Monitor.Wait(this);
1105                     }
1106                 }
1107                 else {
1108                     System.Threading.Monitor.Wait(this, timeout, true);
1109                 }
1110 
1111                 retVal = changedResult;
1112             }
1113 
1114             // Revert the Enabled flag to its previous state.
1115             EnableRaisingEvents = savedEnabled;
1116             runOnce = false;
1117 
1118             // Decouple the event handlers added above.
1119             if ((changeType & WatcherChangeTypes.Created) != 0) {
1120                 this.Created -= dirHandler;
1121             }
1122             if ((changeType & WatcherChangeTypes.Deleted) != 0) {
1123                 this.Deleted -= dirHandler;
1124             }
1125             if ((changeType & WatcherChangeTypes.Changed) != 0) {
1126                 this.Changed -= dirHandler;
1127             }
1128             if ((changeType & WatcherChangeTypes.Renamed) != 0) {
1129                 this.Renamed -= renameHandler;
1130             }
1131 
1132             // Return the struct.
1133             return retVal;
1134         }
1135     }
1136 
1137     /// <devdoc>
1138     ///    Helper class to hold to N/Direct call declaration and flags.
1139     /// </devdoc>
1140     [
1141         System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
1142     ]
1143     internal static class Direct {
1144         // All possible action flags
1145         public const int FILE_ACTION_ADDED            = 1;
1146         public const int FILE_ACTION_REMOVED          = 2;
1147         public const int FILE_ACTION_MODIFIED         = 3;
1148         public const int FILE_ACTION_RENAMED_OLD_NAME = 4;
1149         public const int FILE_ACTION_RENAMED_NEW_NAME = 5;
1150 
1151 
1152         // All possible notifications flags
1153         public const int FILE_NOTIFY_CHANGE_FILE_NAME    = 0x00000001;
1154         public const int FILE_NOTIFY_CHANGE_DIR_NAME     = 0x00000002;
1155         public const int FILE_NOTIFY_CHANGE_NAME         = 0x00000003;
1156         public const int FILE_NOTIFY_CHANGE_ATTRIBUTES   = 0x00000004;
1157         public const int FILE_NOTIFY_CHANGE_SIZE         = 0x00000008;
1158         public const int FILE_NOTIFY_CHANGE_LAST_WRITE   = 0x00000010;
1159         public const int FILE_NOTIFY_CHANGE_LAST_ACCESS  = 0x00000020;
1160         public const int FILE_NOTIFY_CHANGE_CREATION     = 0x00000040;
1161         public const int FILE_NOTIFY_CHANGE_SECURITY     = 0x00000100;
1162     }
1163 }
1164 
1165 
1166