5691656cb249a395f3ab52f22a1b84ed5f37774d
[mono.git] / mcs / class / referencesource / System.Web / FileChangesMonitor.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="FileChangesMonitor.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Web {
8     using System.Collections;
9     using System.Collections.Specialized;
10     using System.Diagnostics.CodeAnalysis;
11     using System.Globalization;
12     using System.IO;
13     using System.Runtime.InteropServices;
14     using System.Security;
15     using System.Security.Permissions;
16     using System.Text;
17     using System.Threading;
18     using System.Web.Configuration;
19     using System.Web.Hosting;
20     using System.Web.Util;
21     using Microsoft.Win32;
22
23
24     // Type of the callback to the subscriber of a file change event in FileChangesMonitor.StartMonitoringFile
25     delegate void FileChangeEventHandler(Object sender, FileChangeEvent e); 
26
27     // The type of file change that occurred.
28     enum FileAction {
29         Dispose = -2,
30         Error = -1,
31         Overwhelming = 0,
32         Added = 1,
33         Removed = 2,
34         Modified = 3,
35         RenamedOldName = 4,
36         RenamedNewName = 5
37     }
38
39     // Event data for a file change notification
40     sealed class FileChangeEvent : EventArgs {
41         internal FileAction   Action;       // the action
42         internal string       FileName;     // the file that caused the action
43
44         internal FileChangeEvent(FileAction action, string fileName) {
45             this.Action = action;
46             this.FileName = fileName;
47         }
48     }
49
50     // Contains information about the target of a file change notification
51     sealed class FileMonitorTarget {
52         internal readonly FileChangeEventHandler    Callback;   // the callback
53         internal readonly string                    Alias;      // the filename used to name the file
54         internal readonly DateTime                  UtcStartMonitoring;// time we started monitoring
55         int                                         _refs;      // number of uses of callbacks
56
57         internal FileMonitorTarget(FileChangeEventHandler callback, string alias) {
58             Callback = callback;
59             Alias = alias;
60             UtcStartMonitoring = DateTime.UtcNow;
61             _refs = 1;
62         }
63
64         internal int AddRef() {
65             _refs++;
66             return _refs;
67         }
68
69         internal int Release() {
70             _refs--;
71             return _refs;
72         }
73
74 #if DBG
75         internal string DebugDescription(string indent) {
76             StringBuilder   sb = new StringBuilder(200);
77             string          i2 = indent + "    ";
78
79             sb.Append(indent + "FileMonitorTarget\n");
80             sb.Append(i2 + "       Callback: " + Callback.Target + "(HC=" + Callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")\n");
81             sb.Append(i2 + "          Alias: " + Alias + "\n");
82             sb.Append(i2 + "StartMonitoring: " + Debug.FormatUtcDate(UtcStartMonitoring) + "\n");
83             sb.Append(i2 + "          _refs: " + _refs + "\n");
84
85             return sb.ToString();
86         }
87 #endif
88     }
89
90 #if !FEATURE_PAL // FEATURE_PAL does not enable access control
91     sealed class FileSecurity {
92         const int DACL_INFORMATION = 
93                 UnsafeNativeMethods.DACL_SECURITY_INFORMATION |
94                 UnsafeNativeMethods.GROUP_SECURITY_INFORMATION |
95                 UnsafeNativeMethods.OWNER_SECURITY_INFORMATION;
96
97         static Hashtable    s_interned;
98         static byte[]       s_nullDacl;
99
100         class DaclComparer : IEqualityComparer {
101             // Compares two objects. An implementation of this method must return a
102             // value less than zero if x is less than y, zero if x is equal to y, or a
103             // value greater than zero if x is greater than y.
104             //
105
106             private int Compare(byte[] a, byte[] b) {
107                 int result = a.Length - b.Length;
108                 for (int i = 0; result == 0 && i < a.Length ; i++) {
109                     result = a[i] - b[i];
110                 }
111
112                 return result;
113             }
114
115             bool IEqualityComparer.Equals(Object x, Object y) {
116                 if (x == null && y == null) {
117                     return true;
118                 }
119
120                 if (x == null || y == null) {
121                     return false;
122                 }
123
124                 byte[] a = x as byte[];
125                 byte[] b = y as byte[];
126                 
127                 if (a == null || b == null) {
128                     return false;
129                 }
130
131                 return Compare(a, b) == 0;
132             }
133
134             int IEqualityComparer.GetHashCode(Object obj) {
135                 byte[] a = (byte[]) obj;
136
137                 HashCodeCombiner combiner = new HashCodeCombiner();
138                 foreach (byte b in a) {
139                     combiner.AddObject(b);
140                 }
141
142                 return combiner.CombinedHash32;
143             }
144         }
145
146         static FileSecurity() {
147             s_interned = new Hashtable(0, 1.0f, new DaclComparer());
148             s_nullDacl = new byte[0];
149          }
150
151         [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke",
152                          Justification="Microsoft: Call to GetLastWin32Error() does follow P/Invoke call that is outside the if/else block.")]
153         static internal byte[] GetDacl(string filename) {
154             // DevDiv #322858 - allow skipping DACL step for perf gain
155             if (HostingEnvironment.FcnSkipReadAndCacheDacls) {
156                 return s_nullDacl;
157             }
158
159             // DevDiv #246973
160             // Perf: Start with initial buffer size to minimize File IO
161             //
162             int lengthNeeded = 512;
163             byte[] dacl = new byte[lengthNeeded];
164             int fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, dacl, dacl.Length, ref lengthNeeded);
165
166             if (fOK != 0) {
167                 // If no size is needed, return a non-null marker
168                 if (lengthNeeded == 0) {
169                    Debug.Trace("GetDacl", "Returning null dacl");
170                    return s_nullDacl;
171                 }
172                 
173                 // Shrink the buffer to fit the whole data
174                 Array.Resize(ref dacl, lengthNeeded);
175             }
176             else {
177                 int hr = HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
178                 
179                 // Check if need to redo the call with larger buffer
180                 if (hr != HResults.E_INSUFFICIENT_BUFFER) {
181                     Debug.Trace("GetDacl", "Error in first call to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
182                     return null;
183                 }
184
185                 // The buffer wasn't large enough.  Try again
186                 dacl = new byte[lengthNeeded];
187                 fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, dacl, dacl.Length, ref lengthNeeded);
188                 if (fOK == 0) {
189 #if DBG
190                     hr = HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
191                     Debug.Trace("GetDacl", "Error in second to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
192 #endif
193
194                     return null;
195                 }
196             }
197
198             byte[] interned = (byte[]) s_interned[dacl];
199             if (interned == null) {
200                 lock (s_interned.SyncRoot) {
201                     interned = (byte[]) s_interned[dacl];
202                     if (interned == null) {
203                         Debug.Trace("GetDacl", "Interning new dacl, length " + dacl.Length);
204                         interned = dacl;
205                         s_interned[interned] = interned;
206                     }
207                 }
208             }
209
210             Debug.Trace("GetDacl", "Returning dacl, length " + dacl.Length);
211             return interned;
212         }
213     }
214
215     // holds information about a single file and the targets of change notification
216     sealed class FileMonitor {
217         internal readonly DirectoryMonitor  DirectoryMonitor;   // the parent
218         internal readonly HybridDictionary  Aliases;            // aliases for this file
219         string                              _fileNameLong;      // long file name - if null, represents any file in this directory
220         string                              _fileNameShort;     // short file name, may be null
221         HybridDictionary                    _targets;           // targets of notification
222         bool                                _exists;            // does the file exist?
223         FileAttributesData                  _fad;               // file attributes
224         byte[]                              _dacl;              // dacl
225         FileAction                          _lastAction;        // last action that ocurred on this file
226         DateTime                            _utcLastCompletion; // date of the last RDCW completion
227
228         internal FileMonitor(
229                 DirectoryMonitor dirMon, string fileNameLong, string fileNameShort, 
230                 bool exists, FileAttributesData fad, byte[] dacl) {
231
232             DirectoryMonitor = dirMon;
233             _fileNameLong = fileNameLong;
234             _fileNameShort = fileNameShort;
235             _exists = exists;
236             _fad = fad;
237             _dacl = dacl;
238             _targets = new HybridDictionary();
239             Aliases = new HybridDictionary(true);
240         }
241
242         internal string FileNameLong    {get {return _fileNameLong;}}
243         internal string FileNameShort   {get {return _fileNameShort;}}
244         internal bool   Exists          {get {return _exists;}}
245         internal bool   IsDirectory     {get {return (FileNameLong == null);}}
246         internal FileAction LastAction {
247             get {return _lastAction;}
248             set {_lastAction = value;}
249         }
250
251         internal DateTime UtcLastCompletion {
252             get {return _utcLastCompletion;}
253             set {_utcLastCompletion = value;}
254         }
255
256         // Returns the attributes of a file, updating them if the file has changed.
257         internal FileAttributesData Attributes {
258             get {return _fad;}
259         }
260
261         internal byte[] Dacl {
262             get {return _dacl;}
263         }
264
265         internal void ResetCachedAttributes() {
266             _fad = null;
267             _dacl = null;
268         }
269
270         internal void UpdateCachedAttributes() {
271             string path = Path.Combine(DirectoryMonitor.Directory, FileNameLong);
272             FileAttributesData.GetFileAttributes(path, out _fad);
273             _dacl = FileSecurity.GetDacl(path);
274         }
275
276         // Set new file information when a file comes into existence
277         internal void MakeExist(FindFileData ffd, byte[] dacl) {
278             _fileNameLong = ffd.FileNameLong;
279             _fileNameShort = ffd.FileNameShort;
280             _fad = ffd.FileAttributesData;
281             _dacl = dacl;
282             _exists = true;
283         }
284
285         // Remove a file from existence
286         internal void MakeExtinct() {
287             _fad = null;
288             _dacl = null;
289             _exists = false;
290         }
291
292         internal void RemoveFileNameShort() {
293             _fileNameShort = null;
294         }
295
296         internal ICollection Targets {
297             get {return _targets.Values;}
298         }
299
300          // Add delegate for this file.
301         internal void AddTarget(FileChangeEventHandler callback, string alias, bool newAlias) {
302             FileMonitorTarget target = (FileMonitorTarget)_targets[callback.Target];
303             if (target != null) {
304                 target.AddRef();
305             }
306             else {
307 #if DBG
308                 // Needs the lock to sync with DebugDescription
309                 lock (_targets) {
310 #endif                
311                     _targets.Add(callback.Target, new FileMonitorTarget(callback, alias));
312 #if DBG
313                 }
314 #endif
315             }
316
317             if (newAlias) {
318                 Aliases[alias] = alias;
319             }
320         }
321
322         
323         // Remove delegate for this file given the target object.
324         internal int RemoveTarget(object callbackTarget) {
325             FileMonitorTarget target = (FileMonitorTarget)_targets[callbackTarget];
326 #if DBG            
327             if (FileChangesMonitor.s_enableRemoveTargetAssert) {
328                 Debug.Assert(target != null, "removing file monitor target that was never added or already been removed");
329             }
330 #endif
331             if (target != null && target.Release() == 0) {
332 #if DBG
333                 // Needs the lock to sync with DebugDescription
334                 lock (_targets) {
335 #endif                
336                     _targets.Remove(callbackTarget);
337 #if DBG
338                 }
339 #endif
340             }
341
342             return _targets.Count;
343         }
344
345 #if DBG
346         internal string DebugDescription(string indent) {
347             StringBuilder   sb = new StringBuilder(200);
348             string          i2 = indent + "    ";
349             string          i3 = i2 + "    ";
350             DictionaryEntryTypeComparer detcomparer = new DictionaryEntryTypeComparer();
351
352             sb.Append(indent + "System.Web.FileMonitor: ");
353             if (FileNameLong != null) {
354                 sb.Append(FileNameLong);
355                 if (FileNameShort != null) {
356                     sb.Append("; ShortFileName=" + FileNameShort);
357                 }
358
359                 sb.Append("; FileExists="); sb.Append(_exists);                
360             }
361             else {
362                 sb.Append("<ANY>");
363             }
364             sb.Append("\n");
365             sb.Append(i2 + "LastAction="); sb.Append(_lastAction);
366             sb.Append("; LastCompletion="); sb.Append(Debug.FormatUtcDate(_utcLastCompletion));
367             sb.Append("\n");
368
369             if (_fad != null) {
370                 sb.Append(_fad.DebugDescription(i2));
371             }
372             else {
373                 sb.Append(i2 + "FileAttributesData = <null>\n");
374             }
375
376             DictionaryEntry[] delegateEntries;
377
378             lock (_targets) {
379                 sb.Append(i2 + _targets.Count + " delegates...\n");
380
381                 delegateEntries = new DictionaryEntry[_targets.Count];
382                 _targets.CopyTo(delegateEntries, 0);
383             }
384             
385             Array.Sort(delegateEntries, detcomparer);
386             
387             foreach (DictionaryEntry d in delegateEntries) {
388                 sb.Append(i3 + "Delegate " + d.Key.GetType() + "(HC=" + d.Key.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")\n");
389             }
390
391             return sb.ToString();
392         }
393 #endif
394
395     }
396
397     // Change notifications delegate from native code.
398     delegate void NativeFileChangeNotification(FileAction action, [In, MarshalAs(UnmanagedType.LPWStr)] string fileName, long ticks);
399
400     // 
401     // Wraps N/Direct calls to native code that does completion port
402     // based ReadDirectoryChangesW().
403     // This needs to be a separate object so that a DirectoryMonitory
404     // can start monitoring while the old _rootCallback has not been
405     // disposed.
406     //
407     sealed class DirMonCompletion : IDisposable {
408         static int _activeDirMonCompletions = 0;            // private counter used via reflection by FCN check-in suite
409
410         DirectoryMonitor    _dirMon;                        // directory monitor
411         IntPtr              _ndirMonCompletionPtr;          // pointer to native dir mon as int (used only to construct HandleRef)
412         HandleRef           _ndirMonCompletionHandle;       // handleref of a pointer to native dir mon as int
413         GCHandle            _rootCallback;                  // roots this callback to prevent collection
414         int                 _disposed;                      // set to 1 when we call DirMonClose
415         object              _ndirMonCompletionHandleLock;
416
417         internal static int ActiveDirMonCompletions { get { return _activeDirMonCompletions; } }
418
419         internal DirMonCompletion(DirectoryMonitor dirMon, string dir, bool watchSubtree, uint notifyFilter) {
420             Debug.Trace("FileChangesMonitor", "DirMonCompletion::ctor " + dir + " " + watchSubtree.ToString() + " " + notifyFilter.ToString(NumberFormatInfo.InvariantInfo));
421
422             int                             hr;
423             NativeFileChangeNotification    myCallback;
424
425             _dirMon = dirMon;
426             myCallback = new NativeFileChangeNotification(this.OnFileChange);
427             _ndirMonCompletionHandleLock = new object();
428             try {
429             }
430             finally {
431                 // protected from ThreadAbortEx
432                 lock(_ndirMonCompletionHandleLock) {
433                     // Dev10 927846: The managed DirMonCompletion.ctor calls DirMonOpen to create and initialize the native DirMonCompletion.
434                     // If this succeeds, the managed DirMonCompletion.ctor creates a GCHandle to root itself so the target of the callback
435                     // stays alive.  When the native DirMonCompletion is freed it invokes the managed callback with ACTION_DISPOSE to
436                     // release the GCHandle.  In order for the native DirMonCOmpletion to be freed, either DirMonOpen must fail or
437                     // the managed DirMonCompletion.Dispose must be called and it must invoke DirMonClose.  Waiting until the native
438                     // DirMonCompletion.dtor is called to release the GCHandle ensures that the directory handle has been closed,
439                     // the i/o completions have finished and there are no other threads calling the managed callback.  This is because
440                     // we AddRef when we initiate i/o and we Release when the i/o completion finishes.
441                     
442                     // If I don't do this, myCallback will be collected by GC since its only reference is
443                     // from the native code.
444                     _rootCallback = GCHandle.Alloc(myCallback);
445
446                     hr = UnsafeNativeMethods.DirMonOpen(dir, HttpRuntime.AppDomainAppId, watchSubtree, notifyFilter, dirMon.FcnMode, myCallback, out _ndirMonCompletionPtr);
447                     if (hr != HResults.S_OK) {
448                         _rootCallback.Free();
449                         throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
450                     }
451                     
452                     _ndirMonCompletionHandle = new HandleRef(this, _ndirMonCompletionPtr);
453                     Interlocked.Increment(ref _activeDirMonCompletions);
454                 }
455             }
456         }
457
458         ~DirMonCompletion() {
459             Dispose(false);
460         }
461
462         void IDisposable.Dispose() {
463             Dispose(true);
464             System.GC.SuppressFinalize(this);
465         }
466
467         void Dispose(bool disposing) {
468             Debug.Trace("FileChangesMonitor", "DirMonCompletion::Dispose");
469             // this is here because callbacks from native code can cause
470             // this to be invoked from multiple threads concurrently, and
471             // I don't want contention on _ndirMonCompletionHandleLock,
472             // but also need to ensure that DirMonOpen returns and the
473             // _ndirMonCompletionHandle is set before DirMonClose is called.
474             if (Interlocked.Exchange(ref _disposed, 1) == 0) {
475
476                 // Dev10 927846: There is a small window during which the .ctor has
477                 // not returned from DirMonOpen yet but because we already started
478                 // monitoring, we might receive a change notification which could 
479                 // potentially Dispose the instance, so we need to block until 
480                 // DirMonOpen returns and _ndirMonCompletionHandler is set
481                 lock(_ndirMonCompletionHandleLock) {
482                     // Dev11 - 364642: if Dispose is called while finalizing for AD unload then
483                     // the native DirMonCompletion won't be able to call back into the appdomain.
484                     // But it does not need to because _rootCallback is already reclaimed as part of AD unload
485                     bool fNeedToSendFileActionDispose = !AppDomain.CurrentDomain.IsFinalizingForUnload();
486                     HandleRef ndirMonCompletionHandle = _ndirMonCompletionHandle;
487                     if (ndirMonCompletionHandle.Handle != IntPtr.Zero) {
488                         _ndirMonCompletionHandle = new HandleRef(this, IntPtr.Zero);
489                         UnsafeNativeMethods.DirMonClose(ndirMonCompletionHandle, fNeedToSendFileActionDispose);
490                     }
491                 }
492             }
493         }
494
495         void OnFileChange(FileAction action, string fileName, long ticks) {
496             DateTime utcCompletion;
497             if (ticks == 0) {
498                 utcCompletion = DateTime.MinValue;
499             }
500             else {
501                 utcCompletion = DateTimeUtil.FromFileTimeToUtc(ticks);
502             }
503
504 #if DBG
505             Debug.Trace("FileChangesMonitorOnFileChange", "Action=" + action + "; Dir=" + _dirMon.Directory + "; fileName=" +  Debug.ToStringMaybeNull(fileName) + "; completion=" + Debug.FormatUtcDate(utcCompletion) + ";_ndirMonCompletionPtr=0x" + _ndirMonCompletionPtr.ToString("x"));
506 #endif
507
508             //
509             // The native DirMonCompletion sends FileAction.Dispose
510             // when there are no more outstanding calls on the 
511             // delegate. Only then can _rootCallback be freed.
512             //
513             if (action == FileAction.Dispose) {
514                 if (_rootCallback.IsAllocated) {
515                     _rootCallback.Free();
516                 }
517                 Interlocked.Decrement(ref _activeDirMonCompletions);
518             }
519             else {
520                 using (new ApplicationImpersonationContext()) {
521                     _dirMon.OnFileChange(action, fileName, utcCompletion);
522                 }
523             }
524         }
525
526 #if DBG
527         internal string DebugDescription(string indent) {
528             int hc = ((Delegate)_rootCallback.Target).Target.GetHashCode();
529             string description = indent + "_ndirMonCompletionPtr=0x" + _ndirMonCompletionPtr.ToString("x") + "; callback=0x" + hc.ToString("x", NumberFormatInfo.InvariantInfo) + "\n";
530             return description;
531         }
532 #endif
533     }
534
535     sealed class NotificationQueueItem {
536         internal readonly FileChangeEventHandler Callback;
537         internal readonly string                 Filename;
538         internal readonly FileAction             Action;
539
540         internal NotificationQueueItem(FileChangeEventHandler callback, FileAction action, string filename) {
541             Callback = callback;
542             Action = action;
543             Filename = filename;
544         }
545     }
546
547     //
548     // Monitor changes in a single directory.
549     //
550     sealed class DirectoryMonitor : IDisposable {
551
552         static Queue            s_notificationQueue = new Queue();
553         static WorkItemCallback s_notificationCallback = new WorkItemCallback(FireNotifications);
554         static int              s_inNotificationThread;    
555         static int              s_notificationBufferSizeIncreased = 0;
556
557         internal readonly string    Directory;                      // directory being monitored
558         Hashtable                   _fileMons;                      // fileName -> FileMonitor
559         int                         _cShortNames;                   // number of file monitors that are added with their short name
560         FileMonitor                 _anyFileMon;                    // special file monitor to watch for any changes in directory
561         bool                        _watchSubtree;                  // watch subtree?
562         uint                        _notifyFilter;                  // the notify filter for the call to ReadDirectoryChangesW
563         bool                        _ignoreSubdirChange;            // when a subdirectory is deleted or renamed, ignore the notification if we're not monitoring it
564         DirMonCompletion            _dirMonCompletion;              // dirmon completion
565         bool                        _isDirMonAppPathInternal;       // special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
566
567         // FcnMode to pass to native code
568         internal int FcnMode {
569             get;
570             set; 
571         }
572
573         // constructor for special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
574         internal DirectoryMonitor(string appPathInternal, int fcnMode): this(appPathInternal, true, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, fcnMode) {
575             _isDirMonAppPathInternal = true;
576         }
577         
578         internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, int fcnMode): this(dir, watchSubtree, notifyFilter, false, fcnMode) {
579         }
580
581         internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, bool ignoreSubdirChange, int fcnMode) {
582             Directory = dir;
583             _fileMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
584             _watchSubtree = watchSubtree;
585             _notifyFilter = notifyFilter;
586             _ignoreSubdirChange = ignoreSubdirChange;
587             FcnMode = fcnMode;
588         }
589
590         void IDisposable.Dispose() {
591             if (_dirMonCompletion != null) {
592                 ((IDisposable)_dirMonCompletion).Dispose();
593                 _dirMonCompletion = null;
594             }
595
596             //
597             // Remove aliases to this object in FileChangesMonitor so that
598             // it is not rooted.
599             //
600             if (_anyFileMon != null) {
601                 HttpRuntime.FileChangesMonitor.RemoveAliases(_anyFileMon);
602                 _anyFileMon = null;
603             }
604
605             foreach (DictionaryEntry e in _fileMons) {
606                 string key = (string) e.Key;
607                 FileMonitor fileMon = (FileMonitor) e.Value;
608                 if (fileMon.FileNameLong == key) {
609                     HttpRuntime.FileChangesMonitor.RemoveAliases(fileMon);
610                 }
611             }
612
613             _fileMons.Clear();
614             _cShortNames = 0;
615         }
616
617         internal bool IsMonitoring() {
618             return GetFileMonitorsCount() > 0;
619         }
620
621         void StartMonitoring() {
622             if (_dirMonCompletion == null) {
623                 _dirMonCompletion = new DirMonCompletion(this, Directory, _watchSubtree, _notifyFilter);
624             }
625         }
626
627         internal void StopMonitoring() {
628             lock (this) {
629                 ((IDisposable)this).Dispose();
630             }    
631         }
632
633         FileMonitor FindFileMonitor(string file) {
634             FileMonitor fileMon;
635
636             if (file == null) {
637                 fileMon = _anyFileMon;
638             }
639             else {
640                 fileMon = (FileMonitor)_fileMons[file];
641             }
642
643             return fileMon;
644         }
645
646         FileMonitor AddFileMonitor(string file) {
647             string path;
648             FileMonitor fileMon;
649             FindFileData ffd = null;
650             int hr;
651
652             if (String.IsNullOrEmpty(file)) {
653                 // add as the <ANY> file monitor
654                 fileMon = new FileMonitor(this, null, null, true, null, null);
655                 _anyFileMon = fileMon;
656             }
657             else {
658                 // Get the long and short name of the file
659                 path = Path.Combine(Directory, file);
660                 if (_isDirMonAppPathInternal) {
661                     hr = FindFileData.FindFile(path, Directory, out ffd);
662                 }
663                 else {
664                     hr = FindFileData.FindFile(path, out ffd);
665                 }
666                 if (hr == HResults.S_OK) {
667                     // Unless this is FileChangesMonitor._dirMonAppPathInternal,
668                     // don't monitor changes to a directory - this will not pickup changes to files in the directory.
669                     if (!_isDirMonAppPathInternal
670                         && (ffd.FileAttributesData.FileAttributes & FileAttributes.Directory) != 0) {
671                         throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, path);
672                     }
673
674                     byte[] dacl = FileSecurity.GetDacl(path);
675                     fileMon = new FileMonitor(this, ffd.FileNameLong, ffd.FileNameShort, true, ffd.FileAttributesData, dacl);
676                     _fileMons.Add(ffd.FileNameLong, fileMon);
677
678                     // Update short name aliases to this file
679                     UpdateFileNameShort(fileMon, null, ffd.FileNameShort);
680                 }
681                 else if (hr == HResults.E_PATHNOTFOUND || hr == HResults.E_FILENOTFOUND) {
682                     // Don't allow possible short file names to be added as non-existant,
683                     // because it is impossible to track them if they are indeed a short name since
684                     // short file names may change.
685                     
686                     // FEATURE_PAL 
687
688
689
690                     if (file.IndexOf('~') != -1) {
691                         throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, path);
692                     }
693
694                     // Add as non-existent file
695                     fileMon = new FileMonitor(this, file, null, false, null, null);
696                     _fileMons.Add(file, fileMon);
697                 }
698                 else {
699                     throw FileChangesMonitor.CreateFileMonitoringException(hr, path);
700                 }
701             }
702
703             return fileMon;
704         }
705
706         //
707         // Update short names of a file
708         //
709         void UpdateFileNameShort(FileMonitor fileMon, string oldFileNameShort, string newFileNameShort) {
710             if (oldFileNameShort != null) {
711                 FileMonitor oldFileMonShort = (FileMonitor)_fileMons[oldFileNameShort];
712                 if (oldFileMonShort != null) {
713                     // The old filemonitor no longer has this short file name.
714                     // Update the monitor and _fileMons
715                     if (oldFileMonShort != fileMon) {
716                         oldFileMonShort.RemoveFileNameShort();
717                     }
718
719                     
720                     _fileMons.Remove(oldFileNameShort);
721                     _cShortNames--;
722                 }
723             }
724
725             if (newFileNameShort != null) {
726                 // Add the new short file name.
727                 _fileMons.Add(newFileNameShort, fileMon);
728                 _cShortNames++;
729             }
730         }
731
732         void RemoveFileMonitor(FileMonitor fileMon) {
733             if (fileMon == _anyFileMon) {
734                 _anyFileMon = null;
735             }
736             else {
737                 _fileMons.Remove(fileMon.FileNameLong);
738                 if (fileMon.FileNameShort != null) {
739                     _fileMons.Remove(fileMon.FileNameShort);
740                     _cShortNames--;
741                 }
742             }
743
744             HttpRuntime.FileChangesMonitor.RemoveAliases(fileMon);
745         }
746
747         int GetFileMonitorsCount() {
748             int c = _fileMons.Count - _cShortNames;
749             if (_anyFileMon != null) {
750                 c++;
751             }
752
753             return c;
754         }
755
756         // The 4.0 CAS changes made the AppDomain homogenous, so we need to assert
757         // FileIOPermission.  Currently this is only exposed publicly via CacheDependency, which
758         // already does a PathDiscover check for public callers.
759         [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
760         internal FileMonitor StartMonitoringFileWithAssert(string file, FileChangeEventHandler callback, string alias) {
761             FileMonitor fileMon = null;
762             bool firstFileMonAdded = false;
763
764             lock (this) {
765                 // Find existing file monitor
766                 fileMon = FindFileMonitor(file);
767                 if (fileMon == null) {
768                     // Add a new monitor
769                     fileMon = AddFileMonitor(file);
770                     if (GetFileMonitorsCount() == 1) {
771                         firstFileMonAdded = true;
772                     }
773                 }
774
775                 // Add callback to the file monitor
776                 fileMon.AddTarget(callback, alias, true);
777
778                 // Start directory monitoring when the first file gets added
779                 if (firstFileMonAdded) {
780                     StartMonitoring();
781                 }
782             }
783
784             return fileMon;
785         }
786
787         //
788         // Request to stop monitoring a file.
789         //
790         internal void StopMonitoringFile(string file, object target) {
791             FileMonitor fileMon;
792             int numTargets;
793
794             lock (this) {
795                 // Find existing file monitor
796                 fileMon = FindFileMonitor(file);
797                 if (fileMon != null) {
798                     numTargets = fileMon.RemoveTarget(target);
799                     if (numTargets == 0) {
800                         RemoveFileMonitor(fileMon);
801
802                         // last target for the file monitor gone 
803                         // -- remove the file monitor
804                         if (GetFileMonitorsCount() == 0) {
805                             ((IDisposable)this).Dispose();
806                         }
807                     }
808                 }
809             }
810
811 #if DBG
812             if (fileMon != null) {
813                 Debug.Dump("FileChangesMonitor", HttpRuntime.FileChangesMonitor);
814             }
815 #endif
816         }
817
818
819         internal bool GetFileAttributes(string file, out FileAttributesData fad) {
820             FileMonitor fileMon = null;
821             fad = null;
822
823             lock (this) {
824                 // Find existing file monitor
825                 fileMon = FindFileMonitor(file);
826                 if (fileMon != null) {
827                     // Get the attributes
828                     fad = fileMon.Attributes;
829                     return true;
830                 }
831             }
832
833             return false;
834         }
835
836         //
837         // Notes about file attributes:
838         // 
839         // CreationTime is the time a file entry is added to a directory. 
840         //     If file q1 is copied to q2, q2's creation time is updated if it is new to the directory,
841         //         else q2's old time is used.
842         // 
843         //     If a file is deleted, then added, its creation time is preserved from before the delete.
844         //     
845         // LastWriteTime is the time a file was last written.    
846         //     If file q1 is copied to q2, q2's lastWrite time is the same as q1.
847         //     Note that this implies that the LastWriteTime can be older than the LastCreationTime,
848         //     and that a copy of a file can result in the LastWriteTime being earlier than
849         //     its previous value.
850         // 
851         // LastAccessTime is the time a file was last accessed, such as opened or written to.
852         //     Note that if the attributes of a file are changed, its LastAccessTime is not necessarily updated.
853         //     
854         // If the FileSize, CreationTime, or LastWriteTime have changed, then we know that the 
855         //     file has changed in a significant way, and that the LastAccessTime will be greater than
856         //     or equal to that time.
857         //     
858         // If the FileSize, CreationTime, or LastWriteTime have not changed, then the file's
859         //     attributes may have changed without changing the LastAccessTime.
860         //
861         
862         // Confirm that the changes occurred after we started monitoring,
863         // to handle the case where:
864         //
865         //     1. User creates a file.
866         //     2. User starts to monitor the file.
867         //     3. Change notification is made of the original creation of the file.
868         // 
869         // Note that we can only approximate when the last change occurred by
870         // examining the LastAccessTime. The LastAccessTime will change if the 
871         // contents of a file (but not necessarily its attributes) change.
872         // The drawback to using the LastAccessTime is that it will also be
873         // updated when a file is read.
874         //
875         // Note that we cannot make this confirmation when only the file's attributes
876         // or ACLs change, because changes to attributes and ACLs won't change the LastAccessTime.
877         // 
878         bool IsChangeAfterStartMonitoring(FileAttributesData fad, FileMonitorTarget target, DateTime utcCompletion) {
879             // If the LastAccessTime is more than 60 seconds before we
880             // started monitoring, then the change likely did not update
881             // the LastAccessTime correctly.
882             if (fad.UtcLastAccessTime.AddSeconds(60) < target.UtcStartMonitoring) {
883 #if DBG
884                Debug.Trace("FileChangesMonitorIsChangeAfterStart", "LastAccessTime is more than 60 seconds before monitoring started.");
885 #endif
886                 return true;
887             }
888
889             // Check if the notification of the change came after
890             // we started monitoring.
891             if (utcCompletion > target.UtcStartMonitoring) {
892 #if DBG
893                Debug.Trace("FileChangesMonitorIsChangeAfterStart", "Notification came after we started monitoring.");
894 #endif
895                 return true;
896             }
897
898             // Make sure that the LastAccessTime is valid.
899             // It must be more recent than the LastWriteTime.
900             if (fad.UtcLastAccessTime < fad.UtcLastWriteTime) {
901 #if DBG
902                Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastWriteTime is greater then UtcLastAccessTime.");
903 #endif
904                 return true;
905             }
906
907             // If the LastAccessTime occurs exactly at midnight,
908             // then the system is FAT32 and LastAccessTime is unusable.
909             if (fad.UtcLastAccessTime.TimeOfDay == TimeSpan.Zero) {
910 #if DBG
911                Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastAccessTime is midnight -- FAT32 likely.");
912 #endif
913                  return true;
914             }
915
916             // Finally, compare LastAccessTime to the time we started monitoring.
917             // If the time of the last access was before we started monitoring, then
918             // we know a change did not occur to the file contents.
919             if (fad.UtcLastAccessTime >= target.UtcStartMonitoring) {
920 #if DBG
921                Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastAccessTime is greater than UtcStartMonitoring.");
922 #endif
923                 return true;
924             }
925
926 #if DBG
927                Debug.Trace("FileChangesMonitorIsChangeAfterStart", "Change is before start of monitoring.  Data:\n FileAttributesData: \nUtcCreationTime: "
928                + fad.UtcCreationTime + " UtcLastAccessTime: " + fad.UtcLastAccessTime + " UtcLastWriteTime: " + fad.UtcLastWriteTime + "\n FileMonitorTarget:\n UtcStartMonitoring: "
929                + target.UtcStartMonitoring + "\nUtcCompletion: " + utcCompletion);
930 #endif
931             return false;
932          }
933
934         // If this is a special dirmon that monitors all files and subdirectories 
935         // beneath the vroot (enabled via FCNMode registry key), then
936         // we need to special case how we lookup the FileMonitor.  For example, nobody has called
937         // StartMonitorFile for specific files in the App_LocalResources directory,
938         // so we need to see if fileName is in App_LocalResources and then get the FileMonitor for
939         // the directory.
940         private bool GetFileMonitorForSpecialDirectory(string fileName, ref FileMonitor fileMon) {
941
942             // fileName should not be in short form (8.3 format)...it was converted to long form in
943             // DirMonCompletion::ProcessOneFileNotification
944             
945             // first search for match within s_dirsToMonitor
946             for (int i = 0; i < FileChangesMonitor.s_dirsToMonitor.Length; i++) {
947                 if (StringUtil.StringStartsWithIgnoreCase(fileName, FileChangesMonitor.s_dirsToMonitor[i])) {
948                     fileMon = (FileMonitor)_fileMons[FileChangesMonitor.s_dirsToMonitor[i]];
949                     return fileMon != null;
950                 }
951             }
952
953             // if we did not find a match in s_dirsToMonitor, look for LocalResourcesDirectoryName anywhere within fileName
954             int indexStart = fileName.IndexOf(HttpRuntime.LocalResourcesDirectoryName, StringComparison.OrdinalIgnoreCase);
955             if (indexStart > -1) {
956                 int dirNameLength = indexStart + HttpRuntime.LocalResourcesDirectoryName.Length;
957
958                 // fileName should either end with LocalResourcesDirectoryName or include a trailing slash and more characters
959                 if (fileName.Length == dirNameLength || fileName[dirNameLength] == Path.DirectorySeparatorChar) {
960                     string dirName = fileName.Substring(0, dirNameLength);
961                     fileMon = (FileMonitor)_fileMons[dirName];
962                     return fileMon != null;
963                 }
964             }
965
966             return false;
967         }
968
969
970         //
971         // Delegate callback from native code.
972         //
973         internal void OnFileChange(FileAction action, string fileName, DateTime utcCompletion) {
974             //
975             // Use try/catch to prevent runtime exceptions from propagating 
976             // into native code.
977             //
978             try {
979                 FileMonitor             fileMon = null;
980                 ArrayList               targets = null;
981                 int                     i, n;
982                 FileMonitorTarget       target;
983                 ICollection             col;
984                 string                  key;
985                 FileAttributesData      fadOld = null;
986                 FileAttributesData      fadNew = null;
987                 byte[]                  daclOld = null;
988                 byte[]                  daclNew = null;
989                 FileAction              lastAction = FileAction.Error;
990                 DateTime                utcLastCompletion = DateTime.MinValue;
991                 bool                    isSpecialDirectoryChange = false;
992
993 #if DBG
994                 string                  reasonIgnore = string.Empty;
995                 string                  reasonFire = string.Empty;
996 #endif
997
998                 // We've already stopped monitoring, but a change completion was
999                 // posted afterwards. Ignore it.
1000                 if (_dirMonCompletion == null) {
1001                     return;
1002                 }
1003
1004                 lock (this) {
1005                     if (_fileMons.Count > 0) {
1006                         if (action == FileAction.Error || action == FileAction.Overwhelming) {
1007                             // Overwhelming change -- notify all file monitors
1008                             Debug.Assert(fileName == null, "fileName == null");
1009                             Debug.Assert(action != FileAction.Overwhelming, "action != FileAction.Overwhelming");
1010
1011                             if (action == FileAction.Overwhelming) {
1012                                 //increase file notification buffer size, but only once per app instance
1013                                 if (Interlocked.Increment(ref s_notificationBufferSizeIncreased) == 1) {
1014                                     UnsafeNativeMethods.GrowFileNotificationBuffer( HttpRuntime.AppDomainAppId, _watchSubtree );
1015                                 }
1016                             }
1017
1018                             // Get targets for all files
1019                             targets = new ArrayList();    
1020                             foreach (DictionaryEntry d in _fileMons) {
1021                                 key = (string) d.Key;
1022                                 fileMon = (FileMonitor) d.Value;
1023                                 if (fileMon.FileNameLong == key) {
1024                                     fileMon.ResetCachedAttributes();
1025                                     fileMon.LastAction = action;
1026                                     fileMon.UtcLastCompletion = utcCompletion;
1027                                     col = fileMon.Targets;
1028                                     targets.AddRange(col);
1029                                 }
1030                             }
1031
1032                             fileMon = null;
1033                         }
1034                         else {
1035                             Debug.Assert((int) action >= 1 && fileName != null && fileName.Length > 0,
1036                                         "(int) action >= 1 && fileName != null && fileName.Length > 0");
1037
1038                             // Find the file monitor
1039                             fileMon = (FileMonitor)_fileMons[fileName];
1040
1041                             if (_isDirMonAppPathInternal && fileMon == null) {
1042                                 isSpecialDirectoryChange = GetFileMonitorForSpecialDirectory(fileName, ref fileMon);
1043                             }
1044                             
1045                             if (fileMon != null) {
1046                                 // Get the targets
1047                                 col = fileMon.Targets;
1048                                 targets = new ArrayList(col);
1049
1050                                 fadOld = fileMon.Attributes;
1051                                 daclOld = fileMon.Dacl;
1052                                 lastAction = fileMon.LastAction;
1053                                 utcLastCompletion = fileMon.UtcLastCompletion;
1054                                 fileMon.LastAction = action;
1055                                 fileMon.UtcLastCompletion = utcCompletion;
1056
1057                                 if (action == FileAction.Removed || action == FileAction.RenamedOldName) {
1058                                     // File not longer exists.
1059                                     fileMon.MakeExtinct();
1060                                 }
1061                                 else if (fileMon.Exists) {
1062                                     // We only need to update the attributes if this is 
1063                                     // a different completion, as we retreive the attributes
1064                                     // after the completion is received.
1065                                     if (utcLastCompletion != utcCompletion) {
1066                                         fileMon.UpdateCachedAttributes();
1067                                     }
1068                                 }
1069                                 else {
1070                                     // File now exists - update short name and attributes.
1071                                     FindFileData ffd = null;
1072                                     string path = Path.Combine(Directory, fileMon.FileNameLong);
1073                                     int hr;
1074                                     if (_isDirMonAppPathInternal) {
1075                                         hr = FindFileData.FindFile(path, Directory, out ffd);
1076                                     }
1077                                     else {
1078                                         hr = FindFileData.FindFile(path, out ffd);
1079                                     }
1080                                     if (hr == HResults.S_OK) {
1081                                         Debug.Assert(StringUtil.EqualsIgnoreCase(fileMon.FileNameLong, ffd.FileNameLong),
1082                                                     "StringUtil.EqualsIgnoreCase(fileMon.FileNameLong, ffd.FileNameLong)");
1083
1084                                         string oldFileNameShort = fileMon.FileNameShort;
1085                                         byte[] dacl = FileSecurity.GetDacl(path);
1086                                         fileMon.MakeExist(ffd, dacl);
1087                                         UpdateFileNameShort(fileMon, oldFileNameShort, ffd.FileNameShort);
1088                                     }
1089                                 }
1090
1091                                 fadNew = fileMon.Attributes;
1092                                 daclNew = fileMon.Dacl;
1093                             }
1094                         }
1095                     }
1096
1097                     // Notify the delegate waiting for any changes
1098                     if (_anyFileMon != null) {
1099                         col = _anyFileMon.Targets;
1100                         if (targets != null) {
1101                             targets.AddRange(col);
1102                         }
1103                         else {
1104                             targets = new ArrayList(col);
1105                         }
1106                     }
1107
1108                     if (action == FileAction.Error) {
1109                         // Stop monitoring.
1110                         ((IDisposable)this).Dispose();
1111                     }
1112                 }
1113
1114                 // Ignore Modified action for directories (VSWhidbey 295597)
1115                 bool ignoreThisChangeNotification = false;
1116
1117                 if (!isSpecialDirectoryChange && fileName != null && action == FileAction.Modified) {
1118                     // check if the file is a directory (reuse attributes if already obtained)
1119                     FileAttributesData fad = fadNew;
1120
1121                     if (fad == null) {
1122                         string path = Path.Combine(Directory, fileName);
1123                         FileAttributesData.GetFileAttributes(path, out fad);
1124                     }
1125
1126                     if (fad != null && ((fad.FileAttributes & FileAttributes.Directory) != 0)) {
1127                         // ignore if directory
1128                         ignoreThisChangeNotification = true;
1129                     }
1130                 }
1131
1132                 // Dev10 440497: Don't unload AppDomain when a folder is deleted or renamed, unless we're monitoring files in it
1133                 if (_ignoreSubdirChange && (action == FileAction.Removed || action == FileAction.RenamedOldName) && fileName != null) {
1134                     string fullPath = Path.Combine(Directory, fileName);
1135                     if (!HttpRuntime.FileChangesMonitor.IsDirNameMonitored(fullPath, fileName)) {
1136 #if DBG
1137                         Debug.Trace("FileChangesMonitorIgnoreSubdirChange", 
1138                                     "*** Ignoring SubDirChange " + DateTime.Now.ToString("hh:mm:ss.fff", CultureInfo.InvariantCulture) 
1139                                     + ": fullPath=" + fullPath + ", action=" + action.ToString());
1140 #endif
1141                         ignoreThisChangeNotification = true;
1142                     }
1143 #if DBG
1144                     else {
1145                         Debug.Trace("FileChangesMonitorIgnoreSubdirChange", 
1146                                     "*** SubDirChange " + DateTime.Now.ToString("hh:mm:ss.fff", CultureInfo.InvariantCulture) 
1147                                     + ": fullPath=" + fullPath + ", action=" + action.ToString());
1148                     }
1149 #endif
1150                 }
1151
1152                 // Fire the event
1153                 if (targets != null && !ignoreThisChangeNotification) {
1154                     Debug.Dump("FileChangesMonitor", HttpRuntime.FileChangesMonitor);
1155
1156                     lock (s_notificationQueue.SyncRoot) {
1157                         for (i = 0, n = targets.Count; i < n; i++) {
1158                             //
1159                             // Determine whether the change is significant, and if so, add it 
1160                             // to the notification queue.
1161                             //
1162                             // - A change is significant if action is other than Added or Modified
1163                             // - A change is significant if the action is Added and it occurred after
1164                             //   the target started monitoring.
1165                             // - If the action is Modified:
1166                             // -- A change is significant if the file contents were modified
1167                             //    and it occurred after the target started monitoring.
1168                             // -- A change is significant if the DACL changed. We cannot check if
1169                             //    the change was made after the target started monitoring in this case,
1170                             //    as the LastAccess time may not be updated.
1171                             //
1172                             target = (FileMonitorTarget)targets[i];
1173                             bool isSignificantChange;
1174                             if ((action != FileAction.Added && action != FileAction.Modified) || fadNew == null) {
1175
1176                                 // Any change other than Added or Modified is significant.
1177                                 // If we have no attributes to examine, the change is significant.
1178                                 isSignificantChange = true;
1179
1180 #if DBG
1181                                 reasonFire = "(action != FileAction.Added && action != FileAction.Modified) || fadNew == null";
1182 #endif
1183                             }
1184                             else if (action == FileAction.Added) {
1185                                 // Added actions are significant if they occur after we started monitoring.
1186                                 isSignificantChange = IsChangeAfterStartMonitoring(fadNew, target, utcCompletion);
1187
1188 #if DBG
1189                                 reasonIgnore = "change occurred before started monitoring";
1190                                 reasonFire = "file added after start of monitoring";
1191 #endif
1192
1193                             }
1194                             else {
1195                                 Debug.Assert(action == FileAction.Modified, "action == FileAction.Modified");
1196                                 if (utcCompletion == utcLastCompletion) {
1197                                     // File attributes and ACLs will not have changed if the completion is the same
1198                                     // as the last, since we get the attributes after all changes in the completion
1199                                     // have occurred. Therefore if the previous change was Modified, there
1200                                     // is no change that we can detect.
1201                                     //
1202                                     // Notepad fires such spurious notifications when a file is saved.
1203                                     // 
1204                                     isSignificantChange = (lastAction != FileAction.Modified);
1205
1206 #if DBG
1207                                     reasonIgnore = "spurious FileAction.Modified";
1208                                     reasonFire = "spurious completion where action != modified";
1209 #endif
1210
1211                                 }
1212                                 else if (fadOld == null) {
1213                                     // There were no attributes before this notification, 
1214                                     // so assume the change is significant. We cannot check for
1215                                     // whether the change was after the start of monitoring,
1216                                     // because we don't know if the content changed, or just
1217                                     // DACL, in which case the LastAccessTime will not be updated.
1218                                     isSignificantChange = true;
1219
1220 #if DBG
1221                                     reasonFire = "no attributes before this notification";
1222 #endif
1223                                 }
1224                                 else if (daclOld == null || daclOld != daclNew) {
1225                                     // The change is significant if the DACL changed. 
1226                                     // We cannot check if the change is after the start of monitoring,
1227                                     // as a change in the DACL does not necessarily update the
1228                                     // LastAccessTime of a file.
1229                                     // If we cannot access the DACL, then we must assume
1230                                     // that it is what has changed.
1231                                     isSignificantChange = true;
1232
1233 #if DBG
1234                                     if (daclOld == null) {
1235                                         reasonFire = "unable to access ACL";
1236                                     }
1237                                     else {
1238                                         reasonFire = "ACL changed";
1239                                     }
1240 #endif
1241
1242                                 }
1243                                 else {
1244                                     // The file content was modified. We cannot guarantee that the
1245                                     // LastWriteTime or FileSize changed when the file changed, as 
1246                                     // copying a file preserves the LastWriteTime, and the "touch"
1247                                     // command can reset the LastWriteTime of many files to the same
1248                                     // time.
1249                                     //
1250                                     // If the file content is modified, we can determine if the file
1251                                     // was not changed after the start of monitoring by looking at 
1252                                     // the LastAccess time.
1253                                     isSignificantChange = IsChangeAfterStartMonitoring(fadNew, target, utcCompletion);
1254
1255 #if DBG
1256                                     reasonIgnore = "change occurred before started monitoring";
1257                                     reasonFire = "file content modified after start of monitoring";
1258 #endif
1259
1260                                 }
1261                             }
1262
1263                             if (isSignificantChange) {
1264 #if DBG
1265                                 Debug.Trace("FileChangesMonitorCallback", "Firing change event, reason=" + reasonFire + 
1266                                     "\n\tArgs: Action=" + action +     ";     Completion=" + Debug.FormatUtcDate(utcCompletion) + "; fileName=" + fileName + 
1267                                     "\n\t  LastAction=" + lastAction + "; LastCompletion=" + Debug.FormatUtcDate(utcLastCompletion) + 
1268                                     "\nfadOld=" + ((fadOld != null) ? fadOld.DebugDescription("\t") : "<null>") +
1269                                     "\nfileMon=" + ((fileMon != null) ? fileMon.DebugDescription("\t") : "<null>") + 
1270                                     "\n" + target.DebugDescription("\t"));
1271 #endif
1272
1273                                 s_notificationQueue.Enqueue(new NotificationQueueItem(target.Callback, action, target.Alias));
1274                             }
1275 #if DBG
1276                             else {
1277                                 Debug.Trace("FileChangesMonitorCallback", "Ignoring change event, reason=" + reasonIgnore +
1278                                     "\n\tArgs: Action=" + action +     ";     Completion=" + Debug.FormatUtcDate(utcCompletion) + "; fileName=" + fileName + 
1279                                     "\n\t  LastAction=" + lastAction + "; LastCompletion=" + Debug.FormatUtcDate(utcLastCompletion) + 
1280                                     "\nfadOld=" + ((fadOld != null) ? fadOld.DebugDescription("\t") : "<null>") +
1281                                     "\nfileMon=" + ((fileMon != null) ? fileMon.DebugDescription("\t") : "<null>") + 
1282                                     "\n" + target.DebugDescription("\t"));
1283
1284                             }
1285 #endif
1286                         }
1287                     }
1288
1289                     if (s_notificationQueue.Count > 0 && s_inNotificationThread == 0 && Interlocked.Exchange(ref s_inNotificationThread, 1) == 0) {
1290                         WorkItem.PostInternal(s_notificationCallback);
1291                     }
1292                 }
1293             }
1294             catch (Exception ex) {
1295                 Debug.Trace(Debug.TAG_INTERNAL, 
1296                             "Exception thrown processing file change notification" +
1297                             " action=" + action.ToString() +
1298                             " fileName" + fileName);
1299
1300                 Debug.TraceException(Debug.TAG_INTERNAL, ex);
1301             }
1302         }
1303
1304         // Fire notifications on a separate thread from that which received the notifications,
1305         // so that we don't block notification collection.
1306         static void FireNotifications() {
1307             try {
1308                 // Outer loop: test whether we need to fire notifications and grab the lock
1309                 for (;;) {
1310                     // Inner loop: fire notifications until the queue is emptied
1311                     for (;;) {
1312                         // Remove an item from the queue.
1313                         NotificationQueueItem nqi = null;
1314                         lock (s_notificationQueue.SyncRoot) {
1315                             if (s_notificationQueue.Count > 0) {
1316                                 nqi = (NotificationQueueItem) s_notificationQueue.Dequeue();
1317                             }
1318                         }
1319
1320                         if (nqi == null)
1321                             break;
1322
1323                         try {
1324                             Debug.Trace("FileChangesMonitorFireNotification", "Firing change event" + 
1325                                 "\n\tArgs: Action=" + nqi.Action + "; fileName=" + nqi.Filename + "; Target=" + nqi.Callback.Target + "(HC=" + nqi.Callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
1326
1327                             // Call the callback
1328                             nqi.Callback(null, new FileChangeEvent(nqi.Action, nqi.Filename)); 
1329                         }
1330                         catch (Exception ex) {
1331                             Debug.Trace(Debug.TAG_INTERNAL, 
1332                                         "Exception thrown in file change callback" +
1333                                         " action=" + nqi.Action.ToString() +
1334                                         " fileName" + nqi.Filename);
1335
1336                             Debug.TraceException(Debug.TAG_INTERNAL, ex);
1337                         }
1338                     }
1339
1340                     // Release the lock
1341                     Interlocked.Exchange(ref s_inNotificationThread, 0);
1342
1343                     // We need to test again to avoid ---- where a thread that receives notifications adds to the
1344                     // queue, but does not spawn a thread because s_inNotificationThread = 1
1345                     if (s_notificationQueue.Count == 0 || Interlocked.Exchange(ref s_inNotificationThread, 1) != 0)
1346                         break;
1347                 }
1348             }
1349             catch {
1350                 Interlocked.Exchange(ref s_inNotificationThread, 0);
1351             }
1352         }
1353
1354 #if DBG
1355         internal string DebugDescription(string indent) {
1356             StringBuilder   sb = new StringBuilder(200);
1357             string          i2 = indent + "    ";
1358             DictionaryEntryCaseInsensitiveComparer  decomparer = new DictionaryEntryCaseInsensitiveComparer();
1359             
1360             lock (this) {
1361                 DictionaryEntry[] fileEntries = new DictionaryEntry[_fileMons.Count];
1362                 _fileMons.CopyTo(fileEntries, 0);
1363                 Array.Sort(fileEntries, decomparer);
1364                 
1365                 sb.Append(indent + "System.Web.DirectoryMonitor: " + Directory + "\n");
1366                 if (_dirMonCompletion != null) {
1367                     sb.Append(i2 + "_dirMonCompletion " + _dirMonCompletion.DebugDescription(String.Empty));
1368                 }
1369                 else {
1370                     sb.Append(i2 + "_dirMonCompletion = <null>\n");
1371                 }
1372
1373                 sb.Append(i2 + GetFileMonitorsCount() + " file monitors...\n");
1374                 if (_anyFileMon != null) {
1375                     sb.Append(_anyFileMon.DebugDescription(i2));
1376                 }
1377
1378                 foreach (DictionaryEntry d in fileEntries) {
1379                     FileMonitor fileMon = (FileMonitor)d.Value;
1380                     if (fileMon.FileNameShort == (string)d.Key)
1381                         continue;
1382
1383                     sb.Append(fileMon.DebugDescription(i2));
1384                 }
1385             }
1386
1387             return sb.ToString();
1388         }
1389 #endif
1390     }
1391 #endif // !FEATURE_PAL
1392
1393     //
1394     // Manager for directory monitors.                       
1395     // Provides file change notification services in ASP.NET 
1396     //
1397     sealed class FileChangesMonitor {
1398 #if !FEATURE_PAL // FEATURE_PAL does not enable file change notification
1399         internal static string[] s_dirsToMonitor = new string[] {
1400             HttpRuntime.BinDirectoryName,
1401             HttpRuntime.ResourcesDirectoryName,
1402             HttpRuntime.CodeDirectoryName,
1403             HttpRuntime.WebRefDirectoryName,
1404             HttpRuntime.BrowsersDirectoryName
1405         };
1406
1407         internal const int MAX_PATH = 260;
1408
1409         #pragma warning disable 0649
1410         ReadWriteSpinLock       _lockDispose;                       // spinlock for coordinating dispose
1411         #pragma warning restore 0649
1412
1413         bool                    _disposed;                          // have we disposed?
1414         Hashtable               _aliases;                           // alias -> FileMonitor
1415         Hashtable               _dirs;                              // dir -> DirectoryMonitor
1416         DirectoryMonitor        _dirMonSubdirs;                     // subdirs monitor for renames
1417         Hashtable               _subDirDirMons;                     // Hashtable of DirectoryMonitor used in ListenToSubdirectoryChanges
1418         ArrayList               _dirMonSpecialDirs;                 // top level dirs we monitor
1419         FileChangeEventHandler  _callbackRenameOrCriticaldirChange; // event handler for renames and bindir
1420         int                     _activeCallbackCount;               // number of callbacks currently executing
1421         DirectoryMonitor        _dirMonAppPathInternal;             // watches all files and subdirectories (at any level) beneath HttpRuntime.AppDomainAppPathInternal
1422         String                  _appPathInternal;                   // HttpRuntime.AppDomainAppPathInternal
1423         int                     _FCNMode;                           // from registry, controls how we monitor directories
1424
1425 #if DBG
1426         internal static bool    s_enableRemoveTargetAssert;
1427 #endif
1428
1429         // Dev10 927283: We were appending to HttpRuntime._shutdownMessage in DirectoryMonitor.OnFileChange when
1430         // we received overwhelming changes and errors, but not all overwhelming file change notifications result
1431         // in a shutdown.  The fix is to only append to _shutdownMessage when the domain is being shutdown.
1432         internal static string GenerateErrorMessage(FileAction action, String fileName = null) {
1433             string message = null;
1434             if (action == FileAction.Overwhelming) {
1435                 message = "Overwhelming Change Notification in ";
1436             }
1437             else if (action == FileAction.Error) {
1438                 message = "File Change Notification Error in ";
1439             }
1440             else {
1441                 return null;
1442             }
1443             return (fileName != null) ? message + Path.GetDirectoryName(fileName) : message;
1444         }
1445
1446         internal static HttpException CreateFileMonitoringException(int hr, string path) {
1447             string  message;
1448             bool    logEvent = false;
1449
1450             switch (hr) {
1451                 case HResults.E_FILENOTFOUND:
1452                 case HResults.E_PATHNOTFOUND:
1453                     message = SR.Directory_does_not_exist_for_monitoring;
1454                     break;
1455
1456                 case HResults.E_ACCESSDENIED:
1457                     message = SR.Access_denied_for_monitoring;
1458                     logEvent = true;
1459                     break;
1460
1461                 case HResults.E_INVALIDARG:
1462                     message = SR.Invalid_file_name_for_monitoring;
1463                     break;
1464
1465                 case HResults.ERROR_TOO_MANY_CMDS:
1466                     message = SR.NetBios_command_limit_reached;
1467                     logEvent = true;
1468                     break;
1469
1470                 default:
1471                     message = SR.Failed_to_start_monitoring;
1472                     break;
1473             }
1474
1475
1476             if (logEvent) {
1477                 // Need to raise an eventlog too.
1478                 UnsafeNativeMethods.RaiseFileMonitoringEventlogEvent(
1479                     SR.GetString(message, HttpRuntime.GetSafePath(path)) + 
1480                     "\n\r" + 
1481                     SR.GetString(SR.App_Virtual_Path, HttpRuntime.AppDomainAppVirtualPath),
1482                     path, HttpRuntime.AppDomainAppVirtualPath, hr);
1483             }
1484             
1485             return new HttpException(SR.GetString(message, HttpRuntime.GetSafePath(path)), hr);
1486         }
1487
1488         internal static string GetFullPath(string alias) {
1489             // Assert PathDiscovery before call to Path.GetFullPath
1490             try {
1491                 new FileIOPermission(FileIOPermissionAccess.PathDiscovery, alias).Assert();
1492             }
1493             catch {
1494                 throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
1495             }
1496
1497             string path = Path.GetFullPath(alias);
1498             path = FileUtil.RemoveTrailingDirectoryBackSlash(path);
1499
1500             return path;
1501         }
1502
1503         private bool IsBeneathAppPathInternal(string fullPathName) {
1504             if (_appPathInternal != null
1505                 && fullPathName.Length > _appPathInternal.Length+1
1506                 && fullPathName.IndexOf(_appPathInternal, StringComparison.OrdinalIgnoreCase) > -1 
1507                 && fullPathName[_appPathInternal.Length] == Path.DirectorySeparatorChar) {
1508                 return true;
1509             }
1510             return false;
1511         }
1512
1513         private bool IsFCNDisabled { get { return _FCNMode == 1; } }
1514
1515         internal FileChangesMonitor(FcnMode mode) {
1516             // Possible values for DWORD FCNMode:
1517             //       does not exist == default behavior (create DirectoryMonitor for each subdir)
1518             //              0 or >2 == default behavior (create DirectoryMonitor for each subdir)
1519             //                    1 == disable File Change Notifications (FCN)
1520             //                    2 == create 1 DirectoryMonitor for AppPathInternal and watch subtrees
1521             switch (mode) {
1522                 case FcnMode.NotSet:
1523                     // If the mode is not set, we use the registry key's value
1524                     UnsafeNativeMethods.GetDirMonConfiguration(out _FCNMode);
1525                     break;
1526                 case FcnMode.Disabled:
1527                     _FCNMode = 1;
1528                     break;
1529                 case FcnMode.Single:
1530                     _FCNMode = 2;
1531                     break;
1532                 case FcnMode.Default:
1533                 default:
1534                     _FCNMode = 0;
1535                     break;
1536             }
1537
1538             if (IsFCNDisabled) {
1539                 return;
1540             }
1541
1542             _aliases = Hashtable.Synchronized(new Hashtable(StringComparer.OrdinalIgnoreCase));
1543             _dirs    = new Hashtable(StringComparer.OrdinalIgnoreCase);
1544             _subDirDirMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
1545
1546             if (_FCNMode == 2 && HttpRuntime.AppDomainAppPathInternal != null) {
1547                 _appPathInternal = GetFullPath(HttpRuntime.AppDomainAppPathInternal);
1548                 _dirMonAppPathInternal = new DirectoryMonitor(_appPathInternal, _FCNMode);
1549             }
1550
1551 #if DBG
1552             if ((int)Misc.GetAspNetRegValue(null /*subKey*/, "FCMRemoveTargetAssert", 0) > 0) {
1553                 s_enableRemoveTargetAssert = true;
1554             }
1555 #endif
1556
1557         }
1558
1559         internal bool IsDirNameMonitored(string fullPath, string dirName) {
1560             // is it one of the not-so-special directories we're monitoring?
1561             if (_dirs.ContainsKey(fullPath)) {
1562                 return true;
1563             }
1564             // is it one of the special directories (bin, App_Code, etc) or a subfolder?
1565             foreach (string specialDirName in s_dirsToMonitor) {
1566                 if (StringUtil.StringStartsWithIgnoreCase(dirName, specialDirName)) {
1567                     // a special directory?
1568                     if (dirName.Length == specialDirName.Length) {
1569                         return true;
1570                     }
1571                     // a subfolder?
1572                     else if (dirName.Length > specialDirName.Length && dirName[specialDirName.Length] == Path.DirectorySeparatorChar) {
1573                         return true;
1574                     }
1575                 }
1576             }
1577             // Dev10 
1578             if (dirName.IndexOf(HttpRuntime.LocalResourcesDirectoryName, StringComparison.OrdinalIgnoreCase) > -1) {
1579                 return true;
1580             }
1581             // we're not monitoring it
1582             return false;
1583         }
1584
1585         //
1586         // Find the directory monitor. If not found, maybe add it.
1587         // If the directory is not actively monitoring, ensure that
1588         // it still represents an accessible directory.
1589         //
1590         DirectoryMonitor FindDirectoryMonitor(string dir, bool addIfNotFound, bool throwOnError) {
1591             DirectoryMonitor dirMon;
1592             FileAttributesData fad = null;
1593             int hr;
1594
1595             dirMon = (DirectoryMonitor)_dirs[dir];
1596             if (dirMon != null) {
1597                 if (!dirMon.IsMonitoring()) {
1598                     hr = FileAttributesData.GetFileAttributes(dir, out fad);
1599                     if (hr != HResults.S_OK || (fad.FileAttributes & FileAttributes.Directory) == 0) {
1600                         dirMon = null;
1601                     }
1602                 }
1603             }
1604
1605             if (dirMon != null || !addIfNotFound) {
1606                 return dirMon;
1607             }
1608
1609             lock (_dirs.SyncRoot) {
1610                 // Check again, this time under synchronization.
1611                 dirMon = (DirectoryMonitor)_dirs[dir];
1612                 if (dirMon != null) {
1613                     if (!dirMon.IsMonitoring()) {
1614                         // Fail if it's not a directory or inaccessible.
1615                         hr = FileAttributesData.GetFileAttributes(dir, out fad);
1616                         if (hr == HResults.S_OK && (fad.FileAttributes & FileAttributes.Directory) == 0) {
1617                             // Fail if it's not a directory.
1618                             hr = HResults.E_INVALIDARG;
1619                         }
1620
1621                         if (hr != HResults.S_OK) {
1622                             // Not accessible or a dir, so stop monitoring and remove.
1623                             _dirs.Remove(dir);
1624                             dirMon.StopMonitoring();
1625                             if (addIfNotFound && throwOnError) {
1626                                 throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
1627                             }
1628
1629                             return null;
1630                         }
1631                     }
1632                 }
1633                 else if (addIfNotFound) {
1634                     // Fail if it's not a directory or inaccessible.
1635                     hr = FileAttributesData.GetFileAttributes(dir, out fad);
1636                     if (hr == HResults.S_OK && (fad.FileAttributes & FileAttributes.Directory) == 0) {
1637                         hr = HResults.E_INVALIDARG;
1638                     }
1639
1640                     if (hr == HResults.S_OK) {
1641                         // Add a new directory monitor.
1642                         dirMon = new DirectoryMonitor(dir, false, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, _FCNMode);
1643                         _dirs.Add(dir, dirMon);
1644                     }
1645                     else if (throwOnError) {
1646                         throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
1647                     }
1648                 }
1649             }
1650
1651             return dirMon;
1652         }
1653
1654         // Remove the aliases of a file monitor.
1655         internal void RemoveAliases(FileMonitor fileMon) {
1656             if (IsFCNDisabled) {
1657                 return;
1658             }
1659              
1660             foreach (DictionaryEntry entry in fileMon.Aliases) {
1661                 if (_aliases[entry.Key] == fileMon) {
1662                     _aliases.Remove(entry.Key);
1663                 }
1664             }
1665         }
1666
1667         //
1668         // Request to monitor a file, which may or may not exist.
1669         //
1670         internal DateTime StartMonitoringFile(string alias, FileChangeEventHandler callback) {
1671             Debug.Trace("FileChangesMonitor", "StartMonitoringFile\n" + "\tArgs: File=" + alias + "; Callback=" + callback.Target + "(HC=" + callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
1672
1673             FileMonitor         fileMon;
1674             DirectoryMonitor    dirMon;
1675             string              fullPathName, dir, file;
1676             bool                addAlias = false;
1677
1678             if (alias == null) {
1679                 throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
1680             }
1681
1682             if (IsFCNDisabled) {
1683                 fullPathName = GetFullPath(alias);
1684                 FindFileData ffd = null;
1685                 int hr = FindFileData.FindFile(fullPathName, out ffd);
1686                 if (hr == HResults.S_OK) {
1687                     return ffd.FileAttributesData.UtcLastWriteTime;
1688                 }
1689                 else {
1690                     return DateTime.MinValue;
1691                 }
1692             }
1693
1694             using (new ApplicationImpersonationContext()) {
1695                 _lockDispose.AcquireReaderLock();
1696                 try{
1697                     // Don't start monitoring if disposed.
1698                     if (_disposed) {
1699                         return DateTime.MinValue;
1700                     }
1701
1702                     fileMon = (FileMonitor)_aliases[alias];
1703                     if (fileMon != null) {
1704                         // Used the cached directory monitor and file name.
1705                         dirMon = fileMon.DirectoryMonitor;
1706                         file = fileMon.FileNameLong;
1707                     }
1708                     else {
1709                         addAlias = true;
1710
1711                         if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
1712                             throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
1713                         }
1714
1715                         //
1716                         // Get the directory and file name, and lookup 
1717                         // the directory monitor.
1718                         //
1719                         fullPathName = GetFullPath(alias);
1720                         
1721                         if (IsBeneathAppPathInternal(fullPathName)) {
1722                             dirMon = _dirMonAppPathInternal;
1723                             file = fullPathName.Substring(_appPathInternal.Length+1);
1724                         }
1725                         else {
1726                             dir = UrlPath.GetDirectoryOrRootName(fullPathName);
1727                             file = Path.GetFileName(fullPathName);
1728                             if (String.IsNullOrEmpty(file)) {
1729                                 // not a file
1730                                 throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
1731                             }
1732                             dirMon = FindDirectoryMonitor(dir, true /*addIfNotFound*/, true /*throwOnError*/);
1733                         }
1734                     }
1735
1736                     fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
1737                     if (addAlias) {
1738                         _aliases[alias] = fileMon;
1739                     }
1740                 }
1741                 finally {
1742                     _lockDispose.ReleaseReaderLock();
1743                 }
1744
1745                 FileAttributesData fad;
1746                 fileMon.DirectoryMonitor.GetFileAttributes(file, out fad);
1747
1748                 Debug.Dump("FileChangesMonitor", this);
1749
1750                 if (fad != null) {
1751                     return fad.UtcLastWriteTime;
1752                 }
1753                 else {
1754                     return DateTime.MinValue;
1755                 }
1756             }
1757         }
1758
1759         //
1760         // Request to monitor a path, which may be file, directory, or non-existent
1761         // file.
1762         //
1763         internal DateTime StartMonitoringPath(string alias, FileChangeEventHandler callback, out FileAttributesData fad) {
1764             Debug.Trace("FileChangesMonitor", "StartMonitoringPath\n" + "\tArgs: File=" + alias + "; Callback=" + callback.Target + "(HC=" + callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
1765
1766             FileMonitor         fileMon = null;
1767             DirectoryMonitor    dirMon = null;
1768             string              fullPathName, dir, file = null;
1769             bool                addAlias = false;
1770
1771             fad = null;
1772
1773             if (alias == null) {
1774                 throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
1775             }
1776             
1777             if (IsFCNDisabled) {
1778                 fullPathName = GetFullPath(alias);
1779                 FindFileData ffd = null;
1780                 int hr = FindFileData.FindFile(fullPathName, out ffd);
1781                 if (hr == HResults.S_OK) {
1782                     fad = ffd.FileAttributesData;
1783                     return ffd.FileAttributesData.UtcLastWriteTime;
1784                 }
1785                 else {
1786                     return DateTime.MinValue;
1787                 }
1788             }
1789
1790             using (new ApplicationImpersonationContext()) {
1791                 _lockDispose.AcquireReaderLock();
1792                 try{
1793                     if (_disposed) {
1794                         return DateTime.MinValue;
1795                     }
1796
1797                     // do/while loop once to make breaking out easy
1798                     do {
1799                         fileMon = (FileMonitor)_aliases[alias];
1800                         if (fileMon != null) {
1801                             // Used the cached directory monitor and file name.
1802                             file = fileMon.FileNameLong;
1803                             fileMon = fileMon.DirectoryMonitor.StartMonitoringFileWithAssert(file, callback, alias);
1804                             continue;
1805                         }
1806
1807                         addAlias = true;
1808
1809                         if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
1810                             throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
1811                         }
1812
1813                         fullPathName = GetFullPath(alias);
1814
1815                         // see if the path is beneath HttpRuntime.AppDomainAppPathInternal
1816                         if (IsBeneathAppPathInternal(fullPathName)) {
1817                             dirMon = _dirMonAppPathInternal;
1818                             file = fullPathName.Substring(_appPathInternal.Length+1);
1819                             fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
1820                             continue;
1821                         }
1822
1823                         // try treating the path as a directory
1824                         dirMon = FindDirectoryMonitor(fullPathName, false, false);
1825                         if (dirMon != null) {
1826                             fileMon = dirMon.StartMonitoringFileWithAssert(null, callback, alias);
1827                             continue;
1828                         }
1829
1830                         // try treaing the path as a file
1831                         dir = UrlPath.GetDirectoryOrRootName(fullPathName);
1832                         file = Path.GetFileName(fullPathName);
1833                         if (!String.IsNullOrEmpty(file)) {
1834                             dirMon = FindDirectoryMonitor(dir, false, false);
1835                             if (dirMon != null) {
1836                                 // try to add it - a file is the common case,
1837                                 // and we avoid hitting the disk twice
1838                                 try {
1839                                     fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
1840                                 }
1841                                 catch {
1842                                 }
1843
1844                                 if (fileMon != null) {
1845                                     continue;
1846                                 }
1847                             }
1848                         }
1849
1850                         // We aren't monitoring this path or its parent directory yet. 
1851                         // Hit the disk to determine if it's a directory or file.
1852                         dirMon = FindDirectoryMonitor(fullPathName, true, false);
1853                         if (dirMon != null) {
1854                             // It's a directory, so monitor all changes in it
1855                             file = null;
1856                         }
1857                         else {
1858                             // It's not a directory, so treat as file
1859                             if (String.IsNullOrEmpty(file)) {
1860                                 throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
1861                             }
1862
1863                             dirMon = FindDirectoryMonitor(dir, true, true);
1864                         }
1865
1866                         fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
1867                     } while (false);
1868
1869                     if (!fileMon.IsDirectory) {
1870                         fileMon.DirectoryMonitor.GetFileAttributes(file, out fad);
1871                     }
1872
1873                     if (addAlias) {
1874                         _aliases[alias] = fileMon;
1875                     }
1876                 }
1877                 finally {
1878                     _lockDispose.ReleaseReaderLock();
1879                 }
1880
1881                 Debug.Dump("FileChangesMonitor", this);
1882
1883                 if (fad != null) {
1884                     return fad.UtcLastWriteTime;
1885                 }
1886                 else {
1887                     return DateTime.MinValue;
1888                 }
1889             }
1890         }
1891
1892         //
1893         // Request to monitor the bin directory and directory renames anywhere under app
1894         //
1895
1896         internal void StartMonitoringDirectoryRenamesAndBinDirectory(string dir, FileChangeEventHandler callback) {
1897             Debug.Trace("FileChangesMonitor", "StartMonitoringDirectoryRenamesAndBinDirectory\n" + "\tArgs: File=" + dir + "; Callback=" + callback.Target + "(HC=" + callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
1898
1899             if (String.IsNullOrEmpty(dir)) {
1900                 throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
1901             }
1902
1903             if (IsFCNDisabled) {
1904                 return;
1905             }
1906
1907 #if DBG
1908             Debug.Assert(_dirs.Count == 0, "This function must be called before monitoring other directories, otherwise monitoring of UNC directories will be unreliable on Windows2000 Server.");
1909 #endif
1910             using (new ApplicationImpersonationContext()) {
1911                 _lockDispose.AcquireReaderLock();
1912                 try {
1913                     if (_disposed) {
1914                         return;
1915                     }
1916
1917                     _callbackRenameOrCriticaldirChange = callback;
1918
1919                     string dirRoot = GetFullPath(dir);
1920
1921                     // Monitor bin directory and app directory (for renames only) separately
1922                     // to avoid overwhelming changes when the user writes to a subdirectory
1923                     // of the app directory.
1924
1925                     _dirMonSubdirs = new DirectoryMonitor(dirRoot, true, UnsafeNativeMethods.RDCW_FILTER_DIR_RENAMES, true, _FCNMode);
1926                     try {
1927                         _dirMonSubdirs.StartMonitoringFileWithAssert(null, new FileChangeEventHandler(this.OnSubdirChange), dirRoot);
1928                     }
1929                     catch {
1930                         ((IDisposable)_dirMonSubdirs).Dispose();
1931                         _dirMonSubdirs = null;
1932                         throw;
1933                     }
1934
1935                     _dirMonSpecialDirs = new ArrayList();
1936                     for (int i=0; i<s_dirsToMonitor.Length; i++) {
1937                         _dirMonSpecialDirs.Add(ListenToSubdirectoryChanges(dirRoot, s_dirsToMonitor[i]));
1938                     }
1939                 }
1940                 finally {
1941                     _lockDispose.ReleaseReaderLock();
1942                 }
1943             }
1944         }
1945
1946         //
1947         // Monitor a directory that causes an appdomain shutdown when it changes
1948         //
1949         internal void StartListeningToLocalResourcesDirectory(VirtualPath virtualDir) {
1950             Debug.Trace("FileChangesMonitor", "StartListeningToVirtualSubdirectory\n" + "\tArgs: virtualDir=" + virtualDir);
1951
1952             if (IsFCNDisabled) {
1953                 return;
1954             }
1955
1956             // In some situation (not well understood yet), we get here with either
1957             // _callbackRenameOrCriticaldirChange or _dirMonSpecialDirs being null (VSWhidbey #215040).
1958             // When that happens, just return.
1959             //Debug.Assert(_callbackRenameOrCriticaldirChange != null);
1960             //Debug.Assert(_dirMonSpecialDirs != null);
1961             if (_callbackRenameOrCriticaldirChange == null || _dirMonSpecialDirs == null)
1962                 return;
1963
1964             using (new ApplicationImpersonationContext()) {
1965                 _lockDispose.AcquireReaderLock();
1966                 try {
1967                     if (_disposed) {
1968                         return;
1969                     }
1970
1971                     // Get the physical path, and split it into the parent dir and the dir name
1972                     string dir = virtualDir.MapPath();
1973                     dir = FileUtil.RemoveTrailingDirectoryBackSlash(dir);
1974                     string name = Path.GetFileName(dir);
1975                     dir = Path.GetDirectoryName(dir);
1976
1977                     // If the physical parent directory doesn't exist, don't do anything.
1978                     // This could happen when using a non-file system based VirtualPathProvider
1979                     if (!Directory.Exists(dir))
1980                         return;
1981
1982                     _dirMonSpecialDirs.Add(ListenToSubdirectoryChanges(dir, name));
1983                 }
1984                 finally {
1985                     _lockDispose.ReleaseReaderLock();
1986                 }
1987             }
1988         }
1989
1990         DirectoryMonitor ListenToSubdirectoryChanges(string dirRoot, string dirToListenTo) {
1991
1992             string dirRootSubDir;
1993             DirectoryMonitor dirMonSubDir;
1994
1995             if (StringUtil.StringEndsWith(dirRoot, '\\')) {
1996                 dirRootSubDir = dirRoot + dirToListenTo;
1997             }
1998             else {
1999                 dirRootSubDir = dirRoot + "\\" + dirToListenTo;
2000             }
2001
2002             if (IsBeneathAppPathInternal(dirRootSubDir)) {
2003                 dirMonSubDir = _dirMonAppPathInternal;
2004
2005                 dirToListenTo = dirRootSubDir.Substring(_appPathInternal.Length+1);
2006                 Debug.Trace("ListenToSubDir", dirRoot + " " + dirToListenTo);
2007                 dirMonSubDir.StartMonitoringFileWithAssert(dirToListenTo, new FileChangeEventHandler(this.OnCriticaldirChange), dirRootSubDir);
2008             }
2009             else if (Directory.Exists(dirRootSubDir)) {
2010                 dirMonSubDir = new DirectoryMonitor(dirRootSubDir, true, UnsafeNativeMethods.RDCW_FILTER_FILE_CHANGES, _FCNMode);
2011                 try {
2012                     dirMonSubDir.StartMonitoringFileWithAssert(null, new FileChangeEventHandler(this.OnCriticaldirChange), dirRootSubDir);
2013                 }
2014                 catch {
2015                     ((IDisposable)dirMonSubDir).Dispose();
2016                     dirMonSubDir = null;
2017                     throw;
2018                 }
2019             }
2020             else {
2021                 dirMonSubDir = (DirectoryMonitor)_subDirDirMons[dirRoot];
2022                 if (dirMonSubDir == null) {
2023                     dirMonSubDir = new DirectoryMonitor(dirRoot, false, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, _FCNMode);
2024                     _subDirDirMons[dirRoot] = dirMonSubDir;
2025                 }
2026
2027                 try {
2028                     dirMonSubDir.StartMonitoringFileWithAssert(dirToListenTo, new FileChangeEventHandler(this.OnCriticaldirChange), dirRootSubDir);
2029                 }
2030                 catch {
2031                     ((IDisposable)dirMonSubDir).Dispose();
2032                     dirMonSubDir = null;
2033                     throw;
2034                 }
2035             }
2036
2037             return dirMonSubDir;
2038         }
2039
2040         void OnSubdirChange(Object sender, FileChangeEvent e) {
2041             try {
2042                 Interlocked.Increment(ref _activeCallbackCount);
2043
2044                 if (_disposed) {
2045                     return;
2046                 }
2047
2048                 Debug.Trace("FileChangesMonitor", "OnSubdirChange\n" + "\tArgs: Action=" + e.Action + "; fileName=" + e.FileName);
2049                 FileChangeEventHandler handler = _callbackRenameOrCriticaldirChange;
2050                 if (    handler != null &&
2051                         (e.Action == FileAction.Error || e.Action == FileAction.Overwhelming || e.Action == FileAction.RenamedOldName || e.Action == FileAction.Removed)) {
2052                     Debug.Trace("FileChangesMonitor", "Firing subdir change event\n" + "\tArgs: Action=" + e.Action + "; fileName=" + e.FileName + "; Target=" + handler.Target + "(HC=" + handler.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
2053                     
2054                     HttpRuntime.SetShutdownMessage(
2055                         SR.GetString(SR.Directory_rename_notification, e.FileName));
2056                     
2057                     handler(this, e);
2058                 }
2059             }
2060             finally {
2061                 Interlocked.Decrement(ref _activeCallbackCount);
2062             }
2063         }
2064
2065         void OnCriticaldirChange(Object sender, FileChangeEvent e) {
2066             try {
2067                 Interlocked.Increment(ref _activeCallbackCount);
2068
2069                 if (_disposed) {
2070                     return;
2071                 }
2072
2073                 Debug.Trace("FileChangesMonitor", "OnCriticaldirChange\n" + "\tArgs: Action=" + e.Action + "; fileName=" + e.FileName);
2074                 HttpRuntime.SetShutdownMessage(SR.GetString(SR.Change_notification_critical_dir));
2075                 FileChangeEventHandler handler = _callbackRenameOrCriticaldirChange;
2076                 if (handler != null) {
2077                     handler(this, e);
2078                 }
2079             }
2080             finally {
2081                 Interlocked.Decrement(ref _activeCallbackCount);
2082             }
2083         }
2084
2085         //
2086         // Request to stop monitoring a file.
2087         //
2088         internal void StopMonitoringFile(string alias, object target) {
2089             Debug.Trace("FileChangesMonitor", "StopMonitoringFile\n" + "File=" + alias + "; Callback=" + target);
2090
2091             if (IsFCNDisabled) {
2092                 return;
2093             }
2094
2095             FileMonitor         fileMon;
2096             DirectoryMonitor    dirMon = null;
2097             string              fullPathName, file = null, dir;
2098
2099             if (alias == null) {
2100                 throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
2101             }
2102
2103             using (new ApplicationImpersonationContext()) {
2104                 _lockDispose.AcquireReaderLock();
2105                 try {
2106                     if (_disposed) {
2107                         return;
2108                     }
2109
2110                     fileMon = (FileMonitor)_aliases[alias];
2111                     if (fileMon != null && !fileMon.IsDirectory) {
2112                         // Used the cached directory monitor and file name
2113                         dirMon = fileMon.DirectoryMonitor;
2114                         file = fileMon.FileNameLong;
2115                     }
2116                     else {
2117                         if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
2118                             throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
2119                         }
2120
2121                         // Lookup the directory monitor
2122                         fullPathName = GetFullPath(alias);
2123                         dir = UrlPath.GetDirectoryOrRootName(fullPathName);
2124                         file = Path.GetFileName(fullPathName);
2125                         if (String.IsNullOrEmpty(file)) {
2126                             // not a file
2127                             throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
2128                         }
2129
2130                         dirMon = FindDirectoryMonitor(dir, false, false);
2131                     }
2132
2133                     if (dirMon != null) {
2134                         dirMon.StopMonitoringFile(file, target);
2135                     }
2136                 }
2137                 finally {
2138                     _lockDispose.ReleaseReaderLock();
2139                 }
2140             }
2141         }
2142
2143         //
2144         // Request to stop monitoring a file.
2145         // 
2146         internal void StopMonitoringPath(String alias, object target) {
2147             Debug.Trace("FileChangesMonitor", "StopMonitoringFile\n" + "File=" + alias + "; Callback=" + target);
2148
2149             if (IsFCNDisabled) {
2150                 return;
2151             }
2152
2153             FileMonitor         fileMon;
2154             DirectoryMonitor    dirMon = null;
2155             string              fullPathName, file = null, dir;
2156
2157             if (alias == null) {
2158                 throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
2159             }
2160
2161             using (new ApplicationImpersonationContext()) {
2162                 _lockDispose.AcquireReaderLock();
2163                 try {
2164                     if (_disposed) {
2165                         return;
2166                     }
2167
2168                     fileMon = (FileMonitor)_aliases[alias];
2169                     if (fileMon != null) {
2170                         // Used the cached directory monitor and file name.
2171                         dirMon = fileMon.DirectoryMonitor;
2172                         file = fileMon.FileNameLong;
2173                     }
2174                     else {
2175                         if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
2176                             throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
2177                         }
2178
2179                         // try treating the path as a directory
2180                         fullPathName = GetFullPath(alias);
2181                         dirMon = FindDirectoryMonitor(fullPathName, false, false);
2182                         if (dirMon == null) {
2183                             // try treaing the path as a file
2184                             dir = UrlPath.GetDirectoryOrRootName(fullPathName);
2185                             file = Path.GetFileName(fullPathName);
2186                             if (!String.IsNullOrEmpty(file)) {
2187                                 dirMon = FindDirectoryMonitor(dir, false, false);
2188                             }
2189                         }
2190                     }
2191
2192                     if (dirMon != null) {
2193                         dirMon.StopMonitoringFile(file, target);
2194                     }
2195                 }
2196                 finally {
2197                     _lockDispose.ReleaseReaderLock();
2198                 }
2199             }
2200         }
2201
2202          //
2203          // Returns the last modified time of the file. If the 
2204          // file does not exist, returns DateTime.MinValue.
2205          //
2206          internal FileAttributesData GetFileAttributes(string alias) {
2207              FileMonitor        fileMon;
2208              DirectoryMonitor   dirMon = null;
2209              string             fullPathName, file = null, dir;
2210              FileAttributesData fad = null;
2211
2212              if (alias == null) {
2213                  throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
2214              }
2215
2216              if (IsFCNDisabled) {
2217                  if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
2218                      throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
2219                  }
2220
2221                  fullPathName = GetFullPath(alias);
2222                  FindFileData ffd = null;
2223                  int hr = FindFileData.FindFile(fullPathName, out ffd);
2224                  if (hr == HResults.S_OK) {
2225                      return ffd.FileAttributesData;
2226                  }
2227                  else {
2228                      return null;
2229                  }   
2230              }
2231
2232              using (new ApplicationImpersonationContext()) {
2233                  _lockDispose.AcquireReaderLock();
2234                 try {
2235                     if (!_disposed) {
2236                         fileMon = (FileMonitor)_aliases[alias];
2237                         if (fileMon != null && !fileMon.IsDirectory) {
2238                             // Used the cached directory monitor and file name.
2239                             dirMon = fileMon.DirectoryMonitor;
2240                             file = fileMon.FileNameLong;
2241                         }
2242                         else {
2243                             if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
2244                                 throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
2245                             }
2246
2247                             // Lookup the directory monitor
2248                             fullPathName = GetFullPath(alias);
2249                             dir = UrlPath.GetDirectoryOrRootName(fullPathName);
2250                             file = Path.GetFileName(fullPathName);
2251                             if (!String.IsNullOrEmpty(file)) {
2252                                 dirMon = FindDirectoryMonitor(dir, false, false);
2253                             }
2254                         }
2255                     }
2256                  }
2257                  finally {
2258                      _lockDispose.ReleaseReaderLock();
2259                  }
2260
2261                  // If we're not monitoring the file, get the attributes.
2262                  if (dirMon == null || !dirMon.GetFileAttributes(file, out fad)) {
2263                      FileAttributesData.GetFileAttributes(alias, out fad);
2264                  }
2265
2266                  return fad;
2267              }
2268         }
2269
2270         //
2271         // Request to stop monitoring everything -- release all native resources
2272         //
2273         internal void Stop() {
2274             Debug.Trace("FileChangesMonitor", "Stop!");
2275
2276              if (IsFCNDisabled) {
2277                  return;
2278              }
2279
2280             using (new ApplicationImpersonationContext()) {
2281                 _lockDispose.AcquireWriterLock();
2282                 try {
2283                     _disposed = true;
2284                 }
2285                 finally {
2286                     _lockDispose.ReleaseWriterLock();
2287                 }
2288
2289                 // wait for executing callbacks to complete
2290                 while(_activeCallbackCount != 0) {
2291                     Thread.Sleep(250);
2292                 }
2293
2294                 if (_dirMonSubdirs != null) {
2295                     _dirMonSubdirs.StopMonitoring();
2296                     _dirMonSubdirs = null;
2297                 }
2298
2299                 if (_dirMonSpecialDirs != null) {
2300                     foreach (DirectoryMonitor dirMon in _dirMonSpecialDirs) {
2301                         if (dirMon != null) {
2302                             dirMon.StopMonitoring();
2303                         }
2304                     }
2305
2306                     _dirMonSpecialDirs = null;
2307                 }
2308
2309                 _callbackRenameOrCriticaldirChange = null;
2310
2311                 if (_dirs != null) {
2312                     IDictionaryEnumerator e = _dirs.GetEnumerator();
2313                     while (e.MoveNext()) {
2314                         DirectoryMonitor dirMon = (DirectoryMonitor)e.Value;
2315                         dirMon.StopMonitoring();
2316                     }
2317                 }
2318
2319                 _dirs.Clear();
2320                 _aliases.Clear();
2321
2322                 // Don't allow the AppDomain to unload while we have
2323                 // active DirMonCompletions
2324                 while (DirMonCompletion.ActiveDirMonCompletions != 0) {
2325                     Thread.Sleep(10);
2326                 }
2327             }
2328
2329             Debug.Dump("FileChangesMonitor", this);
2330         }
2331
2332 #if DBG
2333         internal string DebugDescription(string indent) {
2334             StringBuilder   sb = new StringBuilder(200);
2335             string          i2 = indent + "    ";
2336             DictionaryEntryCaseInsensitiveComparer  decomparer = new DictionaryEntryCaseInsensitiveComparer();
2337
2338             sb.Append(indent + "System.Web.FileChangesMonitor\n");
2339             if (_dirMonSubdirs != null) {
2340                 sb.Append(indent + "_dirMonSubdirs\n");
2341                 sb.Append(_dirMonSubdirs.DebugDescription(i2));
2342             }
2343
2344             if (_dirMonSpecialDirs != null) {
2345                 for (int i=0; i<s_dirsToMonitor.Length; i++) {
2346                     if (_dirMonSpecialDirs[i] != null) {
2347                         sb.Append(indent + "_dirMon" + s_dirsToMonitor[i] + "\n");
2348                         sb.Append(((DirectoryMonitor)_dirMonSpecialDirs[i]).DebugDescription(i2));
2349                     }
2350                 }
2351             }
2352
2353             sb.Append(indent + "_dirs " + _dirs.Count + " directory monitors...\n");
2354
2355             DictionaryEntry[] dirEntries = new DictionaryEntry[_dirs.Count];
2356             _dirs.CopyTo(dirEntries, 0);
2357             Array.Sort(dirEntries, decomparer);
2358             
2359             foreach (DictionaryEntry d in dirEntries) {
2360                 DirectoryMonitor dirMon = (DirectoryMonitor)d.Value;
2361                 sb.Append(dirMon.DebugDescription(i2));
2362             }
2363
2364             return sb.ToString();
2365         }
2366 #endif
2367
2368 #else // !FEATURE_PAL stubbing
2369
2370         internal static string[] s_dirsToMonitor = new string[] {
2371         };
2372
2373         internal DateTime StartMonitoringFile(string alias, FileChangeEventHandler callback)
2374         {
2375             return DateTime.Now;
2376         }
2377         
2378         internal DateTime StartMonitoringPath(string alias, FileChangeEventHandler callback)
2379         {
2380             return DateTime.Now;
2381         }
2382
2383         internal void StopMonitoringPath(String alias, object target) 
2384         {
2385         }
2386
2387         internal void StartMonitoringDirectoryRenamesAndBinDirectory(string dir, FileChangeEventHandler callback) 
2388         {
2389         }
2390         
2391         internal void Stop() 
2392         {
2393         }                
2394
2395 #endif // !FEATURE_PAL
2396     }
2397
2398 #if DBG
2399     internal sealed class DictionaryEntryCaseInsensitiveComparer : IComparer {
2400         IComparer _cicomparer = StringComparer.OrdinalIgnoreCase;
2401
2402         internal DictionaryEntryCaseInsensitiveComparer() {}
2403         
2404         int IComparer.Compare(object x, object y) {
2405             string a = (string) ((DictionaryEntry) x).Key;
2406             string b = (string) ((DictionaryEntry) y).Key;
2407
2408             if (a != null && b != null) {
2409                 return _cicomparer.Compare(a, b);
2410             }
2411             else {
2412                 return InvariantComparer.Default.Compare(a, b);            
2413             }
2414         }
2415     }
2416 #endif
2417
2418 #if DBG
2419     internal sealed class DictionaryEntryTypeComparer : IComparer {
2420         IComparer _cicomparer = StringComparer.OrdinalIgnoreCase;
2421
2422         internal DictionaryEntryTypeComparer() {}
2423
2424         int IComparer.Compare(object x, object y) {
2425             object a = ((DictionaryEntry) x).Key;
2426             object b = ((DictionaryEntry) y).Key;
2427
2428             string i = null, j = null;
2429             if (a != null) {
2430                 i = a.GetType().ToString();
2431             }
2432
2433             if (b != null) {
2434                 j = b.GetType().ToString();
2435             }
2436
2437             if (i != null && j != null) {
2438                 return _cicomparer.Compare(i, j);
2439             }
2440             else {
2441                 return InvariantComparer.Default.Compare(i, j);            
2442             }
2443         }
2444     }
2445 #endif
2446 }