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